~bcurtiswx/ubuntu/precise/empathy/3.4.2.1-0ubuntu1

« back to all changes in this revision

Viewing changes to libempathy-gtk/gossip-chat-view.c

  • Committer: Bazaar Package Importer
  • Author(s): Sjoerd Simons
  • Date: 2007-05-20 15:31:42 UTC
  • Revision ID: james.westby@ubuntu.com-20070520153142-r3auwguxdgxhktqb
Tags: upstream-0.4
ImportĀ upstreamĀ versionĀ 0.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 
2
/*
 
3
 * Copyright (C) 2002-2007 Imendio AB
 
4
 *
 
5
 * This program is free software; you can redistribute it and/or
 
6
 * modify it under the terms of the GNU General Public License as
 
7
 * published by the Free Software Foundation; either version 2 of the
 
8
 * License, or (at your option) any later version.
 
9
 *
 
10
 * This program is distributed in the hope that it will be useful,
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
13
 * General Public License for more details.
 
14
 *
 
15
 * You should have received a copy of the GNU General Public
 
16
 * License along with this program; if not, write to the
 
17
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 
18
 * Boston, MA 02111-1307, USA.
 
19
 * 
 
20
 * Authors: Mikael Hallendal <micke@imendio.com>
 
21
 *          Richard Hult <richard@imendio.com>
 
22
 *          Martyn Russell <martyn@imendio.com>
 
23
 */
 
24
 
 
25
#include "config.h"
 
26
 
 
27
#include <sys/types.h>
 
28
#include <string.h>
 
29
#include <time.h>
 
30
 
 
31
#include <glib/gi18n.h>
 
32
#include <gtk/gtkbutton.h>
 
33
#include <gtk/gtkimage.h>
 
34
#include <gtk/gtkmenu.h>
 
35
#include <gtk/gtkmenuitem.h>
 
36
#include <gtk/gtkimagemenuitem.h>
 
37
#include <gtk/gtkstock.h>
 
38
#include <gtk/gtkscrolledwindow.h>
 
39
#include <gtk/gtksizegroup.h>
 
40
#include <glade/glade.h>
 
41
 
 
42
#include <libmissioncontrol/mc-account.h>
 
43
 
 
44
#include <libempathy/gossip-utils.h>
 
45
#include <libempathy/gossip-debug.h>
 
46
#include <libempathy/gossip-conf.h>
 
47
 
 
48
#include "gossip-chat-view.h"
 
49
#include "gossip-chat.h"
 
50
#include "gossip-preferences.h"
 
51
#include "gossip-theme-manager.h"
 
52
#include "gossip-ui-utils.h"
 
53
 
 
54
#define DEBUG_DOMAIN "ChatView"
 
55
 
 
56
/* Number of seconds between timestamps when using normal mode, 5 minutes. */
 
57
#define TIMESTAMP_INTERVAL 300
 
58
 
 
59
#define MAX_LINES 800
 
60
 
 
61
#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHAT_VIEW, GossipChatViewPriv))
 
62
 
 
63
typedef enum {
 
64
        BLOCK_TYPE_NONE,
 
65
        BLOCK_TYPE_SELF,
 
66
        BLOCK_TYPE_OTHER,
 
67
        BLOCK_TYPE_EVENT,
 
68
        BLOCK_TYPE_TIME,
 
69
        BLOCK_TYPE_INVITE
 
70
} BlockType;
 
71
 
 
72
struct _GossipChatViewPriv {
 
73
        GtkTextBuffer *buffer;
 
74
 
 
75
        gboolean       irc_style;
 
76
        time_t         last_timestamp;
 
77
        BlockType      last_block_type;
 
78
 
 
79
        gboolean       allow_scrolling;
 
80
        gboolean       is_group_chat;
 
81
 
 
82
        GtkTextMark   *find_mark_previous;
 
83
        GtkTextMark   *find_mark_next;
 
84
        gboolean       find_wrapped;
 
85
        gboolean       find_last_direction;
 
86
 
 
87
        /* This is for the group chat so we know if the "other" last contact
 
88
         * changed, so we know whether to insert a header or not.
 
89
         */
 
90
        GossipContact *last_contact;
 
91
 
 
92
        guint          notify_system_fonts_id;
 
93
        guint          notify_show_avatars_id;
 
94
};
 
95
 
 
96
typedef struct {
 
97
        GossipSmiley  smiley;
 
98
        const gchar  *pattern;
 
99
} GossipSmileyPattern;
 
100
 
 
101
static const GossipSmileyPattern smileys[] = {
 
102
        /* Forward smileys. */
 
103
        { GOSSIP_SMILEY_NORMAL,       ":)"  },
 
104
        { GOSSIP_SMILEY_WINK,         ";)"  },
 
105
        { GOSSIP_SMILEY_WINK,         ";-)" },
 
106
        { GOSSIP_SMILEY_BIGEYE,       "=)"  },
 
107
        { GOSSIP_SMILEY_NOSE,         ":-)" },
 
108
        { GOSSIP_SMILEY_CRY,          ":'(" },
 
109
        { GOSSIP_SMILEY_SAD,          ":("  },
 
110
        { GOSSIP_SMILEY_SAD,          ":-(" },
 
111
        { GOSSIP_SMILEY_SCEPTICAL,    ":/"  },
 
112
        { GOSSIP_SMILEY_SCEPTICAL,    ":\\" },
 
113
        { GOSSIP_SMILEY_BIGSMILE,     ":D"  },
 
114
        { GOSSIP_SMILEY_BIGSMILE,     ":-D" },
 
115
        { GOSSIP_SMILEY_INDIFFERENT,  ":|"  },
 
116
        { GOSSIP_SMILEY_TOUNGE,       ":p"  },
 
117
        { GOSSIP_SMILEY_TOUNGE,       ":-p" },
 
118
        { GOSSIP_SMILEY_TOUNGE,       ":P"  },
 
119
        { GOSSIP_SMILEY_TOUNGE,       ":-P" },
 
120
        { GOSSIP_SMILEY_TOUNGE,       ";p"  },
 
121
        { GOSSIP_SMILEY_TOUNGE,       ";-p" },
 
122
        { GOSSIP_SMILEY_TOUNGE,       ";P"  },
 
123
        { GOSSIP_SMILEY_TOUNGE,       ";-P" },
 
124
        { GOSSIP_SMILEY_SHOCKED,      ":o"  },
 
125
        { GOSSIP_SMILEY_SHOCKED,      ":-o" },
 
126
        { GOSSIP_SMILEY_SHOCKED,      ":O"  },
 
127
        { GOSSIP_SMILEY_SHOCKED,      ":-O" },
 
128
        { GOSSIP_SMILEY_COOL,         "8)"  },
 
129
        { GOSSIP_SMILEY_COOL,         "B)"  },
 
130
        { GOSSIP_SMILEY_SORRY,        "*|"  },
 
131
        { GOSSIP_SMILEY_KISS,         ":*"  },
 
132
        { GOSSIP_SMILEY_SHUTUP,       ":#"  },
 
133
        { GOSSIP_SMILEY_SHUTUP,       ":-#" },
 
134
        { GOSSIP_SMILEY_YAWN,         "|O"  },
 
135
        { GOSSIP_SMILEY_CONFUSED,     ":S"  },
 
136
        { GOSSIP_SMILEY_CONFUSED,     ":s"  },
 
137
        { GOSSIP_SMILEY_ANGEL,        "<)"  },
 
138
        { GOSSIP_SMILEY_OOOH,         ":x"  },
 
139
        { GOSSIP_SMILEY_LOOKAWAY,     "*)"  },
 
140
        { GOSSIP_SMILEY_LOOKAWAY,     "*-)" },
 
141
        { GOSSIP_SMILEY_BLUSH,        "*S"  },
 
142
        { GOSSIP_SMILEY_BLUSH,        "*s"  },
 
143
        { GOSSIP_SMILEY_BLUSH,        "*$"  },
 
144
        { GOSSIP_SMILEY_COOLBIGSMILE, "8D"  },
 
145
        { GOSSIP_SMILEY_ANGRY,        ":@"  },
 
146
        { GOSSIP_SMILEY_BOSS,         "@)"  },
 
147
        { GOSSIP_SMILEY_MONKEY,       "#)"  },
 
148
        { GOSSIP_SMILEY_SILLY,        "O)"  },
 
149
        { GOSSIP_SMILEY_SICK,         "+o(" },
 
150
 
 
151
        /* Backward smileys. */
 
152
        { GOSSIP_SMILEY_NORMAL,       "(:"  },
 
153
        { GOSSIP_SMILEY_WINK,         "(;"  },
 
154
        { GOSSIP_SMILEY_WINK,         "(-;" },
 
155
        { GOSSIP_SMILEY_BIGEYE,       "(="  },
 
156
        { GOSSIP_SMILEY_NOSE,         "(-:" },
 
157
        { GOSSIP_SMILEY_CRY,          ")':" },
 
158
        { GOSSIP_SMILEY_SAD,          "):"  },
 
159
        { GOSSIP_SMILEY_SAD,          ")-:" },
 
160
        { GOSSIP_SMILEY_SCEPTICAL,    "/:"  },
 
161
        { GOSSIP_SMILEY_SCEPTICAL,    "//:" },
 
162
        { GOSSIP_SMILEY_INDIFFERENT,  "|:"  },
 
163
        { GOSSIP_SMILEY_TOUNGE,       "d:"  },
 
164
        { GOSSIP_SMILEY_TOUNGE,       "d-:" },
 
165
        { GOSSIP_SMILEY_TOUNGE,       "d;"  },
 
166
        { GOSSIP_SMILEY_TOUNGE,       "d-;" },
 
167
        { GOSSIP_SMILEY_SHOCKED,      "o:"  },
 
168
        { GOSSIP_SMILEY_SHOCKED,      "O:"  },
 
169
        { GOSSIP_SMILEY_COOL,         "(8"  },
 
170
        { GOSSIP_SMILEY_COOL,         "(B"  },
 
171
        { GOSSIP_SMILEY_SORRY,        "|*"  },
 
172
        { GOSSIP_SMILEY_KISS,         "*:"  },
 
173
        { GOSSIP_SMILEY_SHUTUP,       "#:"  },
 
174
        { GOSSIP_SMILEY_SHUTUP,       "#-:" },
 
175
        { GOSSIP_SMILEY_YAWN,         "O|"  },
 
176
        { GOSSIP_SMILEY_CONFUSED,     "S:"  },
 
177
        { GOSSIP_SMILEY_CONFUSED,     "s:"  },
 
178
        { GOSSIP_SMILEY_ANGEL,        "(>"  },
 
179
        { GOSSIP_SMILEY_OOOH,         "x:"  },
 
180
        { GOSSIP_SMILEY_LOOKAWAY,     "(*"  },
 
181
        { GOSSIP_SMILEY_LOOKAWAY,     "(-*" },
 
182
        { GOSSIP_SMILEY_BLUSH,        "S*"  },
 
183
        { GOSSIP_SMILEY_BLUSH,        "s*"  },
 
184
        { GOSSIP_SMILEY_BLUSH,        "$*"  },
 
185
        { GOSSIP_SMILEY_ANGRY,        "@:"  },
 
186
        { GOSSIP_SMILEY_BOSS,         "(@"  },
 
187
        { GOSSIP_SMILEY_MONKEY,       "#)"  },
 
188
        { GOSSIP_SMILEY_SILLY,        "(O"  },
 
189
        { GOSSIP_SMILEY_SICK,         ")o+" }
 
190
};
 
191
 
 
192
static void     gossip_chat_view_class_init          (GossipChatViewClass      *klass);
 
193
static void     gossip_chat_view_init                (GossipChatView           *view);
 
194
static void     chat_view_finalize                   (GObject                  *object);
 
195
static gboolean chat_view_drag_motion                (GtkWidget                *widget,
 
196
                                                      GdkDragContext           *context,
 
197
                                                      gint                      x,
 
198
                                                      gint                      y,
 
199
                                                      guint                     time);
 
200
static void     chat_view_size_allocate              (GtkWidget                *widget,
 
201
                                                      GtkAllocation            *alloc);
 
202
static void     chat_view_setup_tags                 (GossipChatView           *view);
 
203
static void     chat_view_system_font_update         (GossipChatView           *view);
 
204
static void     chat_view_notify_system_font_cb      (GossipConf               *conf,
 
205
                                                      const gchar              *key,
 
206
                                                      gpointer                  user_data);
 
207
static void     chat_view_notify_show_avatars_cb     (GossipConf               *conf,
 
208
                                                      const gchar              *key,
 
209
                                                      gpointer                  user_data);
 
210
static void     chat_view_populate_popup             (GossipChatView           *view,
 
211
                                                      GtkMenu                  *menu,
 
212
                                                      gpointer                  user_data);
 
213
static gboolean chat_view_event_cb                   (GossipChatView           *view,
 
214
                                                      GdkEventMotion           *event,
 
215
                                                      GtkTextTag               *tag);
 
216
static gboolean chat_view_url_event_cb               (GtkTextTag               *tag,
 
217
                                                      GObject                  *object,
 
218
                                                      GdkEvent                 *event,
 
219
                                                      GtkTextIter              *iter,
 
220
                                                      GtkTextBuffer            *buffer);
 
221
static void     chat_view_open_address_cb            (GtkMenuItem              *menuitem,
 
222
                                                      const gchar              *url);
 
223
static void     chat_view_copy_address_cb            (GtkMenuItem              *menuitem,
 
224
                                                      const gchar              *url);
 
225
static void     chat_view_clear_view_cb              (GtkMenuItem              *menuitem,
 
226
                                                      GossipChatView           *view);
 
227
static void     chat_view_insert_text_with_emoticons (GtkTextBuffer            *buf,
 
228
                                                      GtkTextIter              *iter,
 
229
                                                      const gchar              *str);
 
230
static gboolean chat_view_is_scrolled_down           (GossipChatView           *view);
 
231
static void     chat_view_theme_changed_cb           (GossipThemeManager       *manager,
 
232
                                                      GossipChatView           *view);
 
233
static void     chat_view_maybe_append_date_and_time (GossipChatView           *view,
 
234
                                                      GossipMessage            *msg);
 
235
static void     chat_view_append_spacing             (GossipChatView           *view);
 
236
static void     chat_view_append_text                (GossipChatView           *view,
 
237
                                                      const gchar              *body,
 
238
                                                      const gchar              *tag);
 
239
static void     chat_view_maybe_append_fancy_header  (GossipChatView           *view,
 
240
                                                      GossipMessage            *msg);
 
241
static void     chat_view_append_irc_action          (GossipChatView           *view,
 
242
                                                      GossipMessage            *msg);
 
243
static void     chat_view_append_fancy_action        (GossipChatView           *view,
 
244
                                                      GossipMessage            *msg);
 
245
static void     chat_view_append_irc_message         (GossipChatView           *view,
 
246
                                                      GossipMessage            *msg);
 
247
static void     chat_view_append_fancy_message       (GossipChatView           *view,
 
248
                                                      GossipMessage            *msg);
 
249
static GdkPixbuf *chat_view_pad_to_size              (GdkPixbuf                *pixbuf,
 
250
                                                      gint                      width,
 
251
                                                      gint                      height,
 
252
                                                      gint                      extra_padding_right);
 
253
 
 
254
G_DEFINE_TYPE (GossipChatView, gossip_chat_view, GTK_TYPE_TEXT_VIEW);
 
255
 
 
256
static void
 
257
gossip_chat_view_class_init (GossipChatViewClass *klass)
 
258
{
 
259
        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
 
260
        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
261
 
 
262
        object_class->finalize = chat_view_finalize;
 
263
        widget_class->size_allocate = chat_view_size_allocate;
 
264
        widget_class->drag_motion = chat_view_drag_motion; 
 
265
 
 
266
        g_type_class_add_private (object_class, sizeof (GossipChatViewPriv));
 
267
}
 
268
 
 
269
static void
 
270
gossip_chat_view_init (GossipChatView *view)
 
271
{
 
272
        GossipChatViewPriv *priv;
 
273
        gboolean            show_avatars;
 
274
 
 
275
        priv = GET_PRIV (view);
 
276
 
 
277
        priv->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
 
278
 
 
279
        priv->last_block_type = BLOCK_TYPE_NONE;
 
280
        priv->last_timestamp = 0;
 
281
 
 
282
        priv->allow_scrolling = TRUE;
 
283
 
 
284
        priv->is_group_chat = FALSE;
 
285
 
 
286
        g_object_set (view,
 
287
                      "wrap-mode", GTK_WRAP_WORD_CHAR,
 
288
                      "editable", FALSE,
 
289
                      "cursor-visible", FALSE,
 
290
                      NULL);
 
291
 
 
292
        priv->notify_system_fonts_id =
 
293
                gossip_conf_notify_add (gossip_conf_get (),
 
294
                                         "/desktop/gnome/interface/document_font_name",
 
295
                                         chat_view_notify_system_font_cb,
 
296
                                         view);
 
297
        chat_view_system_font_update (view);
 
298
 
 
299
        priv->notify_show_avatars_id =
 
300
                gossip_conf_notify_add (gossip_conf_get (),
 
301
                                         GOSSIP_PREFS_UI_SHOW_AVATARS,
 
302
                                         chat_view_notify_show_avatars_cb,
 
303
                                         view);
 
304
 
 
305
        chat_view_setup_tags (view);
 
306
 
 
307
        gossip_theme_manager_apply_saved (gossip_theme_manager_get (), view);
 
308
 
 
309
        show_avatars = FALSE;
 
310
        gossip_conf_get_bool (gossip_conf_get (),
 
311
                               GOSSIP_PREFS_UI_SHOW_AVATARS,
 
312
                               &show_avatars);
 
313
 
 
314
        gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
 
315
                                                  view, show_avatars);
 
316
 
 
317
        g_signal_connect (view,
 
318
                          "populate-popup",
 
319
                          G_CALLBACK (chat_view_populate_popup),
 
320
                          NULL);
 
321
 
 
322
        g_signal_connect_object (gossip_theme_manager_get (),
 
323
                                 "theme-changed",
 
324
                                 G_CALLBACK (chat_view_theme_changed_cb),
 
325
                                 view,
 
326
                                 0);
 
327
}
 
328
 
 
329
static void
 
330
chat_view_finalize (GObject *object)
 
331
{
 
332
        GossipChatView     *view;
 
333
        GossipChatViewPriv *priv;
 
334
 
 
335
        view = GOSSIP_CHAT_VIEW (object);
 
336
        priv = GET_PRIV (view);
 
337
 
 
338
        gossip_debug (DEBUG_DOMAIN, "finalize: %p", object);
 
339
 
 
340
        gossip_conf_notify_remove (gossip_conf_get (), priv->notify_system_fonts_id);
 
341
        gossip_conf_notify_remove (gossip_conf_get (), priv->notify_show_avatars_id);
 
342
 
 
343
        if (priv->last_contact) {
 
344
                g_object_unref (priv->last_contact);
 
345
        }
 
346
 
 
347
        G_OBJECT_CLASS (gossip_chat_view_parent_class)->finalize (object);
 
348
}
 
349
 
 
350
static gboolean
 
351
chat_view_drag_motion (GtkWidget        *widget,
 
352
                       GdkDragContext   *context,
 
353
                       gint              x,
 
354
                       gint              y,
 
355
                       guint             time)
 
356
{
 
357
        /* Don't handle drag motion, since we don't want the view to scroll as
 
358
         * the result of dragging something across it.
 
359
         */
 
360
 
 
361
        return FALSE;
 
362
}
 
363
 
 
364
static void
 
365
chat_view_size_allocate (GtkWidget     *widget,
 
366
                         GtkAllocation *alloc)
 
367
{
 
368
        gboolean down;
 
369
 
 
370
        down = chat_view_is_scrolled_down (GOSSIP_CHAT_VIEW (widget));
 
371
 
 
372
        GTK_WIDGET_CLASS (gossip_chat_view_parent_class)->size_allocate (widget, alloc);
 
373
 
 
374
        if (down) {
 
375
                gossip_chat_view_scroll_down (GOSSIP_CHAT_VIEW (widget));
 
376
        }
 
377
}
 
378
 
 
379
static void
 
380
chat_view_setup_tags (GossipChatView *view)
 
381
{
 
382
        GossipChatViewPriv *priv;
 
383
        GtkTextTag         *tag;
 
384
 
 
385
        priv = GET_PRIV (view);
 
386
 
 
387
        gtk_text_buffer_create_tag (priv->buffer,
 
388
                                    "cut",
 
389
                                    NULL);
 
390
 
 
391
        /* FIXME: Move to the theme and come up with something that looks a bit
 
392
         * nicer.
 
393
         */
 
394
        gtk_text_buffer_create_tag (priv->buffer,
 
395
                                    "highlight",
 
396
                                    "background", "yellow",
 
397
                                    NULL);
 
398
 
 
399
        tag = gtk_text_buffer_create_tag (priv->buffer,
 
400
                                          "link",
 
401
                                          NULL);
 
402
 
 
403
        g_signal_connect (tag,
 
404
                          "event",
 
405
                          G_CALLBACK (chat_view_url_event_cb),
 
406
                          priv->buffer);
 
407
 
 
408
        g_signal_connect (view,
 
409
                          "motion-notify-event",
 
410
                          G_CALLBACK (chat_view_event_cb),
 
411
                          tag);
 
412
}
 
413
 
 
414
static void
 
415
chat_view_system_font_update (GossipChatView *view)
 
416
{
 
417
        PangoFontDescription *font_description = NULL;
 
418
        gchar                *font_name;
 
419
 
 
420
        if (gossip_conf_get_string (gossip_conf_get (),
 
421
                                     "/desktop/gnome/interface/document_font_name",
 
422
                                     &font_name) && font_name) {
 
423
                font_description = pango_font_description_from_string (font_name);
 
424
                g_free (font_name);
 
425
        } else {
 
426
                font_description = NULL;
 
427
        }
 
428
 
 
429
        gtk_widget_modify_font (GTK_WIDGET (view), font_description);
 
430
 
 
431
        if (font_description) {
 
432
                pango_font_description_free (font_description);
 
433
        }
 
434
}
 
435
 
 
436
static void
 
437
chat_view_notify_system_font_cb (GossipConf  *conf,
 
438
                                 const gchar *key,
 
439
                                 gpointer     user_data)
 
440
{
 
441
        GossipChatView *view;
 
442
        gboolean        show_avatars = FALSE;
 
443
 
 
444
        view = user_data;
 
445
 
 
446
        chat_view_system_font_update (view);
 
447
 
 
448
        /* Ugly, again, to adjust the vertical position of the nick... Will fix
 
449
         * this when reworking the theme manager so that view register
 
450
         * themselves with it instead of the other way around.
 
451
         */
 
452
        gossip_conf_get_bool (conf,
 
453
                               GOSSIP_PREFS_UI_SHOW_AVATARS,
 
454
                               &show_avatars);
 
455
 
 
456
        gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
 
457
                                                  view, show_avatars);
 
458
}
 
459
 
 
460
static void
 
461
chat_view_notify_show_avatars_cb (GossipConf  *conf,
 
462
                                  const gchar *key,
 
463
                                  gpointer     user_data)
 
464
{
 
465
        GossipChatView     *view;
 
466
        GossipChatViewPriv *priv;
 
467
        gboolean            show_avatars = FALSE;
 
468
 
 
469
        view = user_data;
 
470
        priv = GET_PRIV (view);
 
471
 
 
472
        gossip_conf_get_bool (conf, key, &show_avatars);
 
473
 
 
474
        gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
 
475
                                                  view, show_avatars);
 
476
}
 
477
 
 
478
static void
 
479
chat_view_populate_popup (GossipChatView *view,
 
480
                          GtkMenu        *menu,
 
481
                          gpointer        user_data)
 
482
{
 
483
        GossipChatViewPriv *priv;
 
484
        GtkTextTagTable    *table;
 
485
        GtkTextTag         *tag;
 
486
        gint                x, y;
 
487
        GtkTextIter         iter, start, end;
 
488
        GtkWidget          *item;
 
489
        gchar              *str = NULL;
 
490
 
 
491
        priv = GET_PRIV (view);
 
492
 
 
493
        /* Clear menu item */
 
494
        if (gtk_text_buffer_get_char_count (priv->buffer) > 0) {
 
495
                item = gtk_menu_item_new ();
 
496
                gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
 
497
                gtk_widget_show (item);
 
498
 
 
499
                item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
 
500
                gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
 
501
                gtk_widget_show (item);
 
502
 
 
503
                g_signal_connect (item,
 
504
                                  "activate",
 
505
                                  G_CALLBACK (chat_view_clear_view_cb),
 
506
                                  view);
 
507
        }
 
508
 
 
509
        /* Link context menu items */
 
510
        table = gtk_text_buffer_get_tag_table (priv->buffer);
 
511
        tag = gtk_text_tag_table_lookup (table, "link");
 
512
 
 
513
        gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
 
514
 
 
515
        gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
 
516
                                               GTK_TEXT_WINDOW_WIDGET,
 
517
                                               x, y,
 
518
                                               &x, &y);
 
519
 
 
520
        gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
 
521
 
 
522
        start = end = iter;
 
523
 
 
524
        if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
 
525
            gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
 
526
                str = gtk_text_buffer_get_text (priv->buffer,
 
527
                                                &start, &end, FALSE);
 
528
        }
 
529
 
 
530
        if (G_STR_EMPTY (str)) {
 
531
                g_free (str);
 
532
                return;
 
533
        }
 
534
 
 
535
        /* NOTE: Set data just to get the string freed when not needed. */
 
536
        g_object_set_data_full (G_OBJECT (menu),
 
537
                                "url", str,
 
538
                                (GDestroyNotify) g_free);
 
539
 
 
540
        item = gtk_menu_item_new ();
 
541
        gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
 
542
        gtk_widget_show (item);
 
543
 
 
544
        item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
 
545
        g_signal_connect (item,
 
546
                          "activate",
 
547
                          G_CALLBACK (chat_view_copy_address_cb),
 
548
                          str);
 
549
        gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
 
550
        gtk_widget_show (item);
 
551
 
 
552
        item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
 
553
        g_signal_connect (item,
 
554
                          "activate",
 
555
                          G_CALLBACK (chat_view_open_address_cb),
 
556
                          str);
 
557
        gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
 
558
        gtk_widget_show (item);
 
559
}
 
560
 
 
561
static gboolean
 
562
chat_view_event_cb (GossipChatView *view,
 
563
                    GdkEventMotion *event,
 
564
                    GtkTextTag     *tag)
 
565
{
 
566
        static GdkCursor  *hand = NULL;
 
567
        static GdkCursor  *beam = NULL;
 
568
        GtkTextWindowType  type;
 
569
        GtkTextIter        iter;
 
570
        GdkWindow         *win;
 
571
        gint               x, y, buf_x, buf_y;
 
572
 
 
573
        type = gtk_text_view_get_window_type (GTK_TEXT_VIEW (view),
 
574
                                              event->window);
 
575
 
 
576
        if (type != GTK_TEXT_WINDOW_TEXT) {
 
577
                return FALSE;
 
578
        }
 
579
 
 
580
        /* Get where the pointer really is. */
 
581
        win = gtk_text_view_get_window (GTK_TEXT_VIEW (view), type);
 
582
        if (!win) {
 
583
                return FALSE;
 
584
        }
 
585
 
 
586
        gdk_window_get_pointer (win, &x, &y, NULL);
 
587
 
 
588
        /* Get the iter where the cursor is at */
 
589
        gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view), type,
 
590
                                               x, y,
 
591
                                               &buf_x, &buf_y);
 
592
 
 
593
        gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view),
 
594
                                            &iter,
 
595
                                            buf_x, buf_y);
 
596
 
 
597
        if (gtk_text_iter_has_tag (&iter, tag)) {
 
598
                if (!hand) {
 
599
                        hand = gdk_cursor_new (GDK_HAND2);
 
600
                        beam = gdk_cursor_new (GDK_XTERM);
 
601
                }
 
602
                gdk_window_set_cursor (win, hand);
 
603
        } else {
 
604
                if (!beam) {
 
605
                        beam = gdk_cursor_new (GDK_XTERM);
 
606
                }
 
607
                gdk_window_set_cursor (win, beam);
 
608
        }
 
609
 
 
610
        return FALSE;
 
611
}
 
612
 
 
613
static gboolean
 
614
chat_view_url_event_cb (GtkTextTag    *tag,
 
615
                        GObject       *object,
 
616
                        GdkEvent      *event,
 
617
                        GtkTextIter   *iter,
 
618
                        GtkTextBuffer *buffer)
 
619
{
 
620
        GtkTextIter  start, end;
 
621
        gchar       *str;
 
622
 
 
623
        /* If the link is being selected, don't do anything. */
 
624
        gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
 
625
        if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) {
 
626
                return FALSE;
 
627
        }
 
628
 
 
629
        if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) {
 
630
                start = end = *iter;
 
631
 
 
632
                if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
 
633
                    gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
 
634
                        str = gtk_text_buffer_get_text (buffer,
 
635
                                                        &start,
 
636
                                                        &end,
 
637
                                                        FALSE);
 
638
 
 
639
                        gossip_url_show (str);
 
640
                        g_free (str);
 
641
                }
 
642
        }
 
643
 
 
644
        return FALSE;
 
645
}
 
646
 
 
647
static void
 
648
chat_view_open_address_cb (GtkMenuItem *menuitem, const gchar *url)
 
649
{
 
650
        gossip_url_show (url);
 
651
}
 
652
 
 
653
static void
 
654
chat_view_copy_address_cb (GtkMenuItem *menuitem, const gchar *url)
 
655
{
 
656
        GtkClipboard *clipboard;
 
657
 
 
658
        clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
 
659
        gtk_clipboard_set_text (clipboard, url, -1);
 
660
 
 
661
        clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
 
662
        gtk_clipboard_set_text (clipboard, url, -1);
 
663
}
 
664
 
 
665
static void
 
666
chat_view_clear_view_cb (GtkMenuItem *menuitem, GossipChatView *view)
 
667
{
 
668
        gossip_chat_view_clear (view);
 
669
}
 
670
 
 
671
static void
 
672
chat_view_insert_text_with_emoticons (GtkTextBuffer *buf,
 
673
                                      GtkTextIter   *iter,
 
674
                                      const gchar   *str)
 
675
{
 
676
        const gchar *p;
 
677
        gunichar     c, prev_c;
 
678
        gint         i;
 
679
        gint         match;
 
680
        gint         submatch;
 
681
        gboolean     use_smileys = FALSE;
 
682
 
 
683
        gossip_conf_get_bool (gossip_conf_get (),
 
684
                               GOSSIP_PREFS_CHAT_SHOW_SMILEYS,
 
685
                               &use_smileys);
 
686
 
 
687
        if (!use_smileys) {
 
688
                gtk_text_buffer_insert (buf, iter, str, -1);
 
689
                return;
 
690
        }
 
691
 
 
692
        while (*str) {
 
693
                gint         smileys_index[G_N_ELEMENTS (smileys)];
 
694
                GdkPixbuf   *pixbuf;
 
695
                gint         len;
 
696
                const gchar *start;
 
697
 
 
698
                memset (smileys_index, 0, sizeof (smileys_index));
 
699
 
 
700
                match = -1;
 
701
                submatch = -1;
 
702
                p = str;
 
703
                prev_c = 0;
 
704
 
 
705
                while (*p) {
 
706
                        c = g_utf8_get_char (p);
 
707
 
 
708
                        if (match != -1 && g_unichar_isspace (c)) {
 
709
                                break;
 
710
                        } else {
 
711
                                match = -1;
 
712
                        }
 
713
 
 
714
                        if (submatch != -1 || prev_c == 0 || g_unichar_isspace (prev_c)) {
 
715
                                submatch = -1;
 
716
 
 
717
                                for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
 
718
                                        /* Only try to match if we already have
 
719
                                         * a beginning match for the pattern, or
 
720
                                         * if it's the first character in the
 
721
                                         * pattern, if it's not in the middle of
 
722
                                         * a word.
 
723
                                         */
 
724
                                        if (((smileys_index[i] == 0 && (prev_c == 0 || g_unichar_isspace (prev_c))) ||
 
725
                                             smileys_index[i] > 0) &&
 
726
                                            smileys[i].pattern[smileys_index[i]] == c) {
 
727
                                                submatch = i;
 
728
 
 
729
                                                smileys_index[i]++;
 
730
                                                if (!smileys[i].pattern[smileys_index[i]]) {
 
731
                                                        match = i;
 
732
                                                }
 
733
                                        } else {
 
734
                                                smileys_index[i] = 0;
 
735
                                        }
 
736
                                }
 
737
                        }
 
738
 
 
739
                        prev_c = c;
 
740
                        p = g_utf8_next_char (p);
 
741
                }
 
742
 
 
743
                if (match == -1) {
 
744
                        gtk_text_buffer_insert (buf, iter, str, -1);
 
745
                        return;
 
746
                }
 
747
 
 
748
                start = p - strlen (smileys[match].pattern);
 
749
 
 
750
                if (start > str) {
 
751
                        len = start - str;
 
752
                        gtk_text_buffer_insert (buf, iter, str, len);
 
753
                }
 
754
 
 
755
                pixbuf = gossip_chat_view_get_smiley_image (smileys[match].smiley);
 
756
                gtk_text_buffer_insert_pixbuf (buf, iter, pixbuf);
 
757
 
 
758
                gtk_text_buffer_insert (buf, iter, " ", 1);
 
759
 
 
760
                str = g_utf8_find_next_char (p, NULL);
 
761
        }
 
762
}
 
763
 
 
764
static gboolean
 
765
chat_view_is_scrolled_down (GossipChatView *view)
 
766
{
 
767
        GtkWidget *sw;
 
768
 
 
769
        sw = gtk_widget_get_parent (GTK_WIDGET (view));
 
770
        if (GTK_IS_SCROLLED_WINDOW (sw)) {
 
771
                GtkAdjustment *vadj;
 
772
 
 
773
                vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw));
 
774
 
 
775
                if (vadj->value + vadj->page_size / 2 < vadj->upper - vadj->page_size) {
 
776
                        return FALSE;
 
777
                }
 
778
        }
 
779
 
 
780
        return TRUE;
 
781
}
 
782
 
 
783
static void
 
784
chat_view_maybe_trim_buffer (GossipChatView *view)
 
785
{
 
786
        GossipChatViewPriv *priv;
 
787
        GtkTextIter         top, bottom;
 
788
        gint                line;
 
789
        gint                remove;
 
790
        GtkTextTagTable    *table;
 
791
        GtkTextTag         *tag;
 
792
 
 
793
        priv = GET_PRIV (view);
 
794
 
 
795
        gtk_text_buffer_get_end_iter (priv->buffer, &bottom);
 
796
        line = gtk_text_iter_get_line (&bottom);
 
797
        if (line < MAX_LINES) {
 
798
                return;
 
799
        }
 
800
 
 
801
        remove = line - MAX_LINES;
 
802
        gtk_text_buffer_get_start_iter (priv->buffer, &top);
 
803
 
 
804
        bottom = top;
 
805
        if (!gtk_text_iter_forward_lines (&bottom, remove)) {
 
806
                return;
 
807
        }
 
808
 
 
809
        /* Track backwords to a place where we can safely cut, we don't do it in
 
810
         * the middle of a tag.
 
811
         */
 
812
        table = gtk_text_buffer_get_tag_table (priv->buffer);
 
813
        tag = gtk_text_tag_table_lookup (table, "cut");
 
814
        if (!tag) {
 
815
                return;
 
816
        }
 
817
 
 
818
        if (!gtk_text_iter_forward_to_tag_toggle (&bottom, tag)) {
 
819
                return;
 
820
        }
 
821
 
 
822
        if (!gtk_text_iter_equal (&top, &bottom)) {
 
823
                gtk_text_buffer_delete (priv->buffer, &top, &bottom);
 
824
        }
 
825
}
 
826
 
 
827
static void
 
828
chat_view_maybe_append_date_and_time (GossipChatView *view,
 
829
                                      GossipMessage  *msg)
 
830
{
 
831
        GossipChatViewPriv *priv;
 
832
        const gchar        *tag;
 
833
        time_t              timestamp;
 
834
        GDate              *date, *last_date;
 
835
        GtkTextIter         iter;
 
836
        gboolean            append_date, append_time;
 
837
        GString            *str;
 
838
 
 
839
        priv = GET_PRIV (view);
 
840
 
 
841
        if (priv->irc_style) {
 
842
                tag = "irc-time";
 
843
        } else {
 
844
                tag = "fancy-time";
 
845
        }
 
846
 
 
847
        if (priv->last_block_type == BLOCK_TYPE_TIME) {
 
848
                return;
 
849
        }
 
850
 
 
851
        str = g_string_new (NULL);
 
852
 
 
853
        timestamp = 0;
 
854
        if (msg) {
 
855
                timestamp = gossip_message_get_timestamp (msg);
 
856
        }
 
857
 
 
858
        if (timestamp <= 0) {
 
859
                timestamp = gossip_time_get_current ();
 
860
        }
 
861
 
 
862
        date = g_date_new ();
 
863
        g_date_set_time (date, timestamp);
 
864
 
 
865
        last_date = g_date_new ();
 
866
        g_date_set_time (last_date, priv->last_timestamp);
 
867
 
 
868
        append_date = FALSE;
 
869
        append_time = FALSE;
 
870
 
 
871
        if (g_date_compare (date, last_date) > 0) {
 
872
                append_date = TRUE;
 
873
                append_time = TRUE;
 
874
        }
 
875
 
 
876
        if (priv->last_timestamp + TIMESTAMP_INTERVAL < timestamp) {
 
877
                append_time = TRUE;
 
878
        }
 
879
 
 
880
        if (append_time || append_date) {
 
881
                chat_view_append_spacing (view);
 
882
 
 
883
                g_string_append (str, "- ");
 
884
        }
 
885
 
 
886
        if (append_date) {
 
887
                gchar buf[256];
 
888
 
 
889
                g_date_strftime (buf, 256, _("%A %d %B %Y"), date);
 
890
                g_string_append (str, buf);
 
891
 
 
892
                if (append_time) {
 
893
                        g_string_append (str, ", ");
 
894
                }
 
895
        }
 
896
 
 
897
        g_date_free (date);
 
898
        g_date_free (last_date);
 
899
 
 
900
        if (append_time) {
 
901
                gchar *tmp;
 
902
 
 
903
                tmp = gossip_time_to_string_local (timestamp, GOSSIP_TIME_FORMAT_DISPLAY_SHORT);
 
904
                g_string_append (str, tmp);
 
905
                g_free (tmp);
 
906
        }
 
907
 
 
908
        if (append_time || append_date) {
 
909
                g_string_append (str, " -\n");
 
910
 
 
911
                gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
912
                gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
 
913
                                                          &iter,
 
914
                                                          str->str, -1,
 
915
                                                          tag,
 
916
                                                          NULL);
 
917
 
 
918
                priv->last_block_type = BLOCK_TYPE_TIME;
 
919
                priv->last_timestamp = timestamp;
 
920
        }
 
921
 
 
922
        g_string_free (str, TRUE);
 
923
}
 
924
 
 
925
static void
 
926
chat_view_append_spacing (GossipChatView *view)
 
927
{
 
928
        GossipChatViewPriv *priv;
 
929
        const gchar        *tag;
 
930
        GtkTextIter         iter;
 
931
 
 
932
        priv = GET_PRIV (view);
 
933
 
 
934
        if (priv->irc_style) {
 
935
                tag = "irc-spacing";
 
936
        } else {
 
937
                tag = "fancy-spacing";
 
938
        }
 
939
 
 
940
        gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
941
        gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
 
942
                                                  &iter,
 
943
                                                  "\n",
 
944
                                                  -1,
 
945
                                                  "cut",
 
946
                                                  tag,
 
947
                                                  NULL);
 
948
}
 
949
 
 
950
static void
 
951
chat_view_append_text (GossipChatView *view,
 
952
                       const gchar    *body,
 
953
                       const gchar    *tag)
 
954
{
 
955
        GossipChatViewPriv *priv;
 
956
        GtkTextIter         start_iter, end_iter;
 
957
        GtkTextMark        *mark;
 
958
        GtkTextIter         iter;
 
959
        gint                num_matches, i;
 
960
        GArray             *start, *end;
 
961
        const gchar        *link_tag;
 
962
 
 
963
        priv = GET_PRIV (view);
 
964
 
 
965
        if (priv->irc_style) {
 
966
                link_tag = "irc-link";
 
967
        } else {
 
968
                link_tag = "fancy-link";
 
969
        }
 
970
 
 
971
        gtk_text_buffer_get_end_iter (priv->buffer, &start_iter);
 
972
        mark = gtk_text_buffer_create_mark (priv->buffer, NULL, &start_iter, TRUE);
 
973
 
 
974
        start = g_array_new (FALSE, FALSE, sizeof (gint));
 
975
        end = g_array_new (FALSE, FALSE, sizeof (gint));
 
976
 
 
977
        num_matches = gossip_regex_match (GOSSIP_REGEX_ALL, body, start, end);
 
978
 
 
979
        if (num_matches == 0) {
 
980
                gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
981
                chat_view_insert_text_with_emoticons (priv->buffer, &iter, body);
 
982
        } else {
 
983
                gint   last = 0;
 
984
                gint   s = 0, e = 0;
 
985
                gchar *tmp;
 
986
 
 
987
                for (i = 0; i < num_matches; i++) {
 
988
                        s = g_array_index (start, gint, i);
 
989
                        e = g_array_index (end, gint, i);
 
990
 
 
991
                        if (s > last) {
 
992
                                tmp = gossip_substring (body, last, s);
 
993
 
 
994
                                gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
995
                                chat_view_insert_text_with_emoticons (priv->buffer,
 
996
                                                                      &iter,
 
997
                                                                      tmp);
 
998
                                g_free (tmp);
 
999
                        }
 
1000
 
 
1001
                        tmp = gossip_substring (body, s, e);
 
1002
 
 
1003
                        gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1004
                        gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
 
1005
                                                                  &iter,
 
1006
                                                                  tmp,
 
1007
                                                                  -1,
 
1008
                                                                  link_tag,
 
1009
                                                                  "link",
 
1010
                                                                  NULL);
 
1011
 
 
1012
                        g_free (tmp);
 
1013
 
 
1014
                        last = e;
 
1015
                }
 
1016
 
 
1017
                if (e < strlen (body)) {
 
1018
                        tmp = gossip_substring (body, e, strlen (body));
 
1019
 
 
1020
                        gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1021
                        chat_view_insert_text_with_emoticons (priv->buffer,
 
1022
                                                              &iter,
 
1023
                                                              tmp);
 
1024
                        g_free (tmp);
 
1025
                }
 
1026
        }
 
1027
 
 
1028
        g_array_free (start, TRUE);
 
1029
        g_array_free (end, TRUE);
 
1030
 
 
1031
        gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1032
        gtk_text_buffer_insert (priv->buffer, &iter, "\n", 1);
 
1033
 
 
1034
        /* Apply the style to the inserted text. */
 
1035
        gtk_text_buffer_get_iter_at_mark (priv->buffer, &start_iter, mark);
 
1036
        gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
 
1037
 
 
1038
        gtk_text_buffer_apply_tag_by_name (priv->buffer,
 
1039
                                           tag,
 
1040
                                           &start_iter,
 
1041
                                           &end_iter);
 
1042
 
 
1043
        gtk_text_buffer_delete_mark (priv->buffer, mark);
 
1044
}
 
1045
 
 
1046
static void
 
1047
chat_view_maybe_append_fancy_header (GossipChatView *view,
 
1048
                                     GossipMessage  *msg)
 
1049
{
 
1050
        GossipChatViewPriv *priv;
 
1051
        GossipContact      *sender;
 
1052
        GossipContact      *my_contact;
 
1053
        const gchar        *name;
 
1054
        gboolean            header;
 
1055
        GtkTextIter         iter;
 
1056
        gchar              *tmp;
 
1057
        const gchar        *tag;
 
1058
        const gchar        *avatar_tag;
 
1059
        const gchar        *line_top_tag;
 
1060
        const gchar        *line_bottom_tag;
 
1061
        gboolean            from_self;
 
1062
        GdkPixbuf          *pixbuf = NULL;
 
1063
        GdkPixbuf          *avatar = NULL;
 
1064
 
 
1065
        priv = GET_PRIV (view);
 
1066
 
 
1067
        sender = gossip_message_get_sender (msg);
 
1068
        my_contact = gossip_contact_get_user (sender);
 
1069
        name = gossip_contact_get_name (sender);
 
1070
        from_self = gossip_contact_equal (sender, my_contact);
 
1071
 
 
1072
        gossip_debug (DEBUG_DOMAIN, "Maybe add fancy header");
 
1073
 
 
1074
        if (from_self) {
 
1075
                tag = "fancy-header-self";
 
1076
                line_top_tag = "fancy-line-top-self";
 
1077
                line_bottom_tag = "fancy-line-bottom-self";
 
1078
        } else {
 
1079
                tag = "fancy-header-other";
 
1080
                line_top_tag = "fancy-line-top-other";
 
1081
                line_bottom_tag = "fancy-line-bottom-other";
 
1082
        }
 
1083
 
 
1084
        header = FALSE;
 
1085
 
 
1086
        /* Only insert a header if the previously inserted block is not the same
 
1087
         * as this one. This catches all the different cases:
 
1088
         */
 
1089
        if (priv->last_block_type != BLOCK_TYPE_SELF &&
 
1090
            priv->last_block_type != BLOCK_TYPE_OTHER) {
 
1091
                header = TRUE;
 
1092
        }
 
1093
        else if (from_self && priv->last_block_type == BLOCK_TYPE_OTHER) {
 
1094
                header = TRUE;
 
1095
        }
 
1096
        else if (!from_self && priv->last_block_type == BLOCK_TYPE_SELF) {
 
1097
                header = TRUE;
 
1098
        }
 
1099
        else if (!from_self &&
 
1100
                 (!priv->last_contact ||
 
1101
                  !gossip_contact_equal (sender, priv->last_contact))) {
 
1102
                header = TRUE;
 
1103
        }
 
1104
 
 
1105
        if (!header) {
 
1106
                return;
 
1107
        }
 
1108
 
 
1109
        chat_view_append_spacing (view);
 
1110
 
 
1111
        gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1112
        gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
 
1113
                                                  &iter,
 
1114
                                                  "\n",
 
1115
                                                  -1,
 
1116
                                                  line_top_tag,
 
1117
                                                  NULL);
 
1118
 
 
1119
        /* FIXME: we should have a cash of avatar pixbufs */
 
1120
        pixbuf = gossip_pixbuf_avatar_from_contact_scaled (sender, 32, 32);
 
1121
        if (pixbuf) {
 
1122
                avatar = chat_view_pad_to_size (pixbuf, 32, 32, 6);
 
1123
                g_object_unref (pixbuf);
 
1124
        }
 
1125
 
 
1126
        if (avatar) {
 
1127
                GtkTextIter start;
 
1128
 
 
1129
                gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1130
                gtk_text_buffer_insert_pixbuf (priv->buffer, &iter, avatar);
 
1131
 
 
1132
                gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1133
                start = iter;
 
1134
                gtk_text_iter_backward_char (&start);
 
1135
 
 
1136
                if (from_self) {
 
1137
                        gtk_text_buffer_apply_tag_by_name (priv->buffer,
 
1138
                                                           "fancy-avatar-self",
 
1139
                                                           &start, &iter);
 
1140
                        avatar_tag = "fancy-header-self-avatar";
 
1141
                } else {
 
1142
                        gtk_text_buffer_apply_tag_by_name (priv->buffer,
 
1143
                                                           "fancy-avatar-other",
 
1144
                                                           &start, &iter);
 
1145
                        avatar_tag = "fancy-header-other-avatar";
 
1146
                }
 
1147
 
 
1148
                g_object_unref (avatar);
 
1149
        } else {
 
1150
                avatar_tag = NULL;
 
1151
        }
 
1152
 
 
1153
        tmp = g_strdup_printf ("%s\n", name);
 
1154
 
 
1155
        gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1156
        gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
 
1157
                                                  &iter,
 
1158
                                                  tmp,
 
1159
                                                  -1,
 
1160
                                                  tag,
 
1161
                                                  avatar_tag,
 
1162
                                                  NULL);
 
1163
        g_free (tmp);
 
1164
 
 
1165
        gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1166
        gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
 
1167
                                                  &iter,
 
1168
                                                  "\n",
 
1169
                                                  -1,
 
1170
                                                  line_bottom_tag,
 
1171
                                                  NULL);
 
1172
}
 
1173
 
 
1174
static void
 
1175
chat_view_append_irc_action (GossipChatView *view,
 
1176
                             GossipMessage  *msg)
 
1177
{
 
1178
        GossipChatViewPriv *priv;
 
1179
        GossipContact      *my_contact;
 
1180
        GossipContact      *sender;
 
1181
        const gchar        *name;
 
1182
        GtkTextIter         iter;
 
1183
        const gchar        *body;
 
1184
        gchar              *tmp;
 
1185
        const gchar        *tag;
 
1186
 
 
1187
        priv = GET_PRIV (view);
 
1188
 
 
1189
        gossip_debug (DEBUG_DOMAIN, "Add IRC action");
 
1190
 
 
1191
        sender = gossip_message_get_sender (msg);
 
1192
        my_contact = gossip_contact_get_user (sender);
 
1193
        name = gossip_contact_get_name (sender);
 
1194
 
 
1195
        /* Skip the "/me ". */
 
1196
        if (gossip_contact_equal (sender, my_contact)) {
 
1197
                tag = "irc-action-self";
 
1198
        } else {
 
1199
                tag = "irc-action-other";
 
1200
        }
 
1201
 
 
1202
        if (priv->last_block_type != BLOCK_TYPE_SELF &&
 
1203
            priv->last_block_type != BLOCK_TYPE_OTHER) {
 
1204
                chat_view_append_spacing (view);
 
1205
        }
 
1206
 
 
1207
        gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1208
 
 
1209
        tmp = g_strdup_printf (" * %s ", name);
 
1210
        gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
 
1211
                                                  &iter,
 
1212
                                                  tmp,
 
1213
                                                  -1,
 
1214
                                                  "cut",
 
1215
                                                  tag,
 
1216
                                                  NULL);
 
1217
        g_free (tmp);
 
1218
 
 
1219
        body = gossip_message_get_body (msg);
 
1220
        chat_view_append_text (view, body, tag);
 
1221
}
 
1222
 
 
1223
static void
 
1224
chat_view_append_fancy_action (GossipChatView *view,
 
1225
                               GossipMessage  *msg)
 
1226
{
 
1227
        GossipChatViewPriv *priv;
 
1228
        GossipContact      *sender;
 
1229
        GossipContact      *my_contact;
 
1230
        const gchar        *name;
 
1231
        const gchar        *body;
 
1232
        GtkTextIter         iter;
 
1233
        gchar              *tmp;
 
1234
        const gchar        *tag;
 
1235
        const gchar        *line_tag;
 
1236
 
 
1237
        priv = GET_PRIV (view);
 
1238
 
 
1239
        gossip_debug (DEBUG_DOMAIN, "Add fancy action");
 
1240
 
 
1241
        sender = gossip_message_get_sender (msg);
 
1242
        my_contact = gossip_contact_get_user (sender);
 
1243
        name = gossip_contact_get_name (sender);
 
1244
 
 
1245
        if (gossip_contact_equal (sender, my_contact)) {
 
1246
                tag = "fancy-action-self";
 
1247
                line_tag = "fancy-line-self";
 
1248
        } else {
 
1249
                tag = "fancy-action-other";
 
1250
                line_tag = "fancy-line-other";
 
1251
        }
 
1252
 
 
1253
        tmp = g_strdup_printf (" * %s ", name);
 
1254
        gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1255
        gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
 
1256
                                                  &iter,
 
1257
                                                  tmp,
 
1258
                                                  -1,
 
1259
                                                  tag,
 
1260
                                                  NULL);
 
1261
        g_free (tmp);
 
1262
 
 
1263
        body = gossip_message_get_body (msg);
 
1264
        chat_view_append_text (view, body, tag);
 
1265
}
 
1266
 
 
1267
static void
 
1268
chat_view_append_irc_message (GossipChatView *view,
 
1269
                              GossipMessage  *msg)
 
1270
{
 
1271
        GossipChatViewPriv *priv;
 
1272
        GossipContact      *sender;
 
1273
        GossipContact      *my_contact;
 
1274
        const gchar        *name;
 
1275
        const gchar        *body;
 
1276
        const gchar        *nick_tag;
 
1277
        const gchar        *body_tag;
 
1278
        GtkTextIter         iter;
 
1279
        gchar              *tmp;
 
1280
 
 
1281
        priv = GET_PRIV (view);
 
1282
 
 
1283
        gossip_debug (DEBUG_DOMAIN, "Add IRC message");
 
1284
 
 
1285
        body = gossip_message_get_body (msg);
 
1286
        sender = gossip_message_get_sender (msg);
 
1287
        my_contact = gossip_contact_get_user (sender);
 
1288
        name = gossip_contact_get_name (sender);
 
1289
 
 
1290
        if (gossip_contact_equal (sender, my_contact)) {
 
1291
                nick_tag = "irc-nick-self";
 
1292
                body_tag = "irc-body-self";
 
1293
        } else {
 
1294
                if (gossip_chat_should_highlight_nick (msg)) {
 
1295
                        nick_tag = "irc-nick-highlight";
 
1296
                } else {
 
1297
                        nick_tag = "irc-nick-other";
 
1298
                }
 
1299
 
 
1300
                body_tag = "irc-body-other";
 
1301
        }
 
1302
 
 
1303
        if (priv->last_block_type != BLOCK_TYPE_SELF &&
 
1304
            priv->last_block_type != BLOCK_TYPE_OTHER) {
 
1305
                chat_view_append_spacing (view);
 
1306
        }
 
1307
 
 
1308
        gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1309
 
 
1310
        /* The nickname. */
 
1311
        tmp = g_strdup_printf ("%s: ", name);
 
1312
        gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
 
1313
                                                  &iter,
 
1314
                                                  tmp,
 
1315
                                                  -1,
 
1316
                                                  "cut",
 
1317
                                                  nick_tag,
 
1318
                                                  NULL);
 
1319
        g_free (tmp);
 
1320
 
 
1321
        /* The text body. */
 
1322
        chat_view_append_text (view, body, body_tag);
 
1323
}
 
1324
 
 
1325
static void
 
1326
chat_view_append_fancy_message (GossipChatView *view,
 
1327
                                GossipMessage  *msg)
 
1328
{
 
1329
        GossipChatViewPriv *priv;
 
1330
        GossipContact      *sender;
 
1331
        GossipContact      *my_contact;
 
1332
        const gchar        *body;
 
1333
        const gchar        *tag;
 
1334
 
 
1335
        priv = GET_PRIV (view);
 
1336
 
 
1337
        sender = gossip_message_get_sender (msg);
 
1338
        my_contact = gossip_contact_get_user (sender);
 
1339
 
 
1340
        if (gossip_contact_equal (sender, my_contact)) {
 
1341
                tag = "fancy-body-self";
 
1342
        } else {
 
1343
                tag = "fancy-body-other";
 
1344
 
 
1345
                /* FIXME: Might want to support nick highlighting here... */
 
1346
        }
 
1347
 
 
1348
        body = gossip_message_get_body (msg);
 
1349
        chat_view_append_text (view, body, tag);
 
1350
}
 
1351
 
 
1352
static void
 
1353
chat_view_theme_changed_cb (GossipThemeManager *manager,
 
1354
                            GossipChatView     *view)
 
1355
{
 
1356
        GossipChatViewPriv *priv;
 
1357
        gboolean            show_avatars = FALSE;
 
1358
        gboolean            theme_rooms = FALSE;
 
1359
 
 
1360
        priv = GET_PRIV (view);
 
1361
 
 
1362
        priv->last_block_type = BLOCK_TYPE_NONE;
 
1363
 
 
1364
        gossip_conf_get_bool (gossip_conf_get (),
 
1365
                              GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
 
1366
                              &theme_rooms);
 
1367
        if (!theme_rooms && priv->is_group_chat) {
 
1368
                gossip_theme_manager_apply (manager, view, NULL);
 
1369
        } else {
 
1370
                gossip_theme_manager_apply_saved (manager, view);
 
1371
        }
 
1372
 
 
1373
        /* Needed for now to update the "rise" property of the names to get it
 
1374
         * vertically centered.
 
1375
         */
 
1376
        gossip_conf_get_bool (gossip_conf_get (),
 
1377
                               GOSSIP_PREFS_UI_SHOW_AVATARS,
 
1378
                               &show_avatars);
 
1379
        gossip_theme_manager_update_show_avatars (manager, view, show_avatars);
 
1380
}
 
1381
 
 
1382
/* Pads a pixbuf to the specified size, by centering it in a larger transparent
 
1383
 * pixbuf. Returns a new ref.
 
1384
 */
 
1385
static GdkPixbuf *
 
1386
chat_view_pad_to_size (GdkPixbuf *pixbuf,
 
1387
                       gint       width,
 
1388
                       gint       height,
 
1389
                       gint       extra_padding_right)
 
1390
{
 
1391
        gint       src_width, src_height;
 
1392
        GdkPixbuf *padded;
 
1393
        gint       x_offset, y_offset;
 
1394
 
 
1395
        src_width = gdk_pixbuf_get_width (pixbuf);
 
1396
        src_height = gdk_pixbuf_get_height (pixbuf);
 
1397
 
 
1398
        x_offset = (width - src_width) / 2;
 
1399
        y_offset = (height - src_height) / 2;
 
1400
 
 
1401
        padded = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
 
1402
                                 TRUE, /* alpha */
 
1403
                                 gdk_pixbuf_get_bits_per_sample (pixbuf),
 
1404
                                 width + extra_padding_right,
 
1405
                                 height);
 
1406
 
 
1407
        gdk_pixbuf_fill (padded, 0);
 
1408
 
 
1409
        gdk_pixbuf_copy_area (pixbuf,
 
1410
                              0, /* source coords */
 
1411
                              0,
 
1412
                              src_width,
 
1413
                              src_height,
 
1414
                              padded,
 
1415
                              x_offset, /* dest coords */
 
1416
                              y_offset);
 
1417
 
 
1418
        return padded;
 
1419
}
 
1420
 
 
1421
GossipChatView *
 
1422
gossip_chat_view_new (void)
 
1423
{
 
1424
        return g_object_new (GOSSIP_TYPE_CHAT_VIEW, NULL);
 
1425
}
 
1426
 
 
1427
/* The name is optional, if NULL, the sender for msg is used. */
 
1428
void
 
1429
gossip_chat_view_append_message (GossipChatView *view,
 
1430
                                 GossipMessage  *msg)
 
1431
{
 
1432
        GossipChatViewPriv *priv;
 
1433
        GossipContact      *sender;
 
1434
        const gchar        *body;
 
1435
        gboolean            scroll_down;
 
1436
 
 
1437
        g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
 
1438
        g_return_if_fail (GOSSIP_IS_MESSAGE (msg));
 
1439
 
 
1440
        priv = GET_PRIV (view);
 
1441
 
 
1442
        body = gossip_message_get_body (msg);
 
1443
        if (!body) {
 
1444
                return;
 
1445
        }
 
1446
 
 
1447
        scroll_down = chat_view_is_scrolled_down (view);
 
1448
 
 
1449
        chat_view_maybe_trim_buffer (view);
 
1450
        chat_view_maybe_append_date_and_time (view, msg);
 
1451
 
 
1452
        sender = gossip_message_get_sender (msg);
 
1453
 
 
1454
        if (!priv->irc_style) {
 
1455
                chat_view_maybe_append_fancy_header (view, msg);
 
1456
        }
 
1457
 
 
1458
        if (gossip_message_get_type (msg) == GOSSIP_MESSAGE_TYPE_ACTION) {
 
1459
                if (priv->irc_style) {
 
1460
                        chat_view_append_irc_action (view, msg);
 
1461
                } else {
 
1462
                        chat_view_append_fancy_action (view, msg);
 
1463
                }
 
1464
        } else {
 
1465
                if (priv->irc_style) {
 
1466
                        chat_view_append_irc_message (view, msg);
 
1467
                } else {
 
1468
                        chat_view_append_fancy_message (view, msg);
 
1469
                }
 
1470
        }
 
1471
 
 
1472
        priv->last_block_type = BLOCK_TYPE_SELF;
 
1473
 
 
1474
        /* Reset the last inserted contact, since it was from self. */
 
1475
        if (priv->last_contact) {
 
1476
                g_object_unref (priv->last_contact);
 
1477
                priv->last_contact = NULL;
 
1478
        }
 
1479
 
 
1480
        if (scroll_down) {
 
1481
                gossip_chat_view_scroll_down (view);
 
1482
        }
 
1483
}
 
1484
 
 
1485
void
 
1486
gossip_chat_view_append_event (GossipChatView *view,
 
1487
                               const gchar    *str)
 
1488
{
 
1489
        GossipChatViewPriv *priv;
 
1490
        gboolean            bottom;
 
1491
        GtkTextIter         iter;
 
1492
        gchar              *msg;
 
1493
        const gchar        *tag;
 
1494
 
 
1495
        g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
 
1496
        g_return_if_fail (!G_STR_EMPTY (str));
 
1497
 
 
1498
        priv = GET_PRIV (view);
 
1499
 
 
1500
        bottom = chat_view_is_scrolled_down (view);
 
1501
 
 
1502
        chat_view_maybe_trim_buffer (view);
 
1503
 
 
1504
        if (priv->irc_style) {
 
1505
                tag = "irc-event";
 
1506
                msg = g_strdup_printf (" - %s\n", str);
 
1507
        } else {
 
1508
                tag = "fancy-event";
 
1509
                msg = g_strdup_printf (" - %s\n", str);
 
1510
        }
 
1511
 
 
1512
        if (priv->last_block_type != BLOCK_TYPE_EVENT) {
 
1513
                /* Comment out for now. */
 
1514
                /*chat_view_append_spacing (view);*/
 
1515
        }
 
1516
 
 
1517
        chat_view_maybe_append_date_and_time (view, NULL);
 
1518
 
 
1519
        gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1520
 
 
1521
        gtk_text_buffer_insert_with_tags_by_name (priv->buffer, &iter,
 
1522
                                                  msg, -1,
 
1523
                                                  tag,
 
1524
                                                  NULL);
 
1525
        g_free (msg);
 
1526
 
 
1527
        if (bottom) {
 
1528
                gossip_chat_view_scroll_down (view);
 
1529
        }
 
1530
 
 
1531
        priv->last_block_type = BLOCK_TYPE_EVENT;
 
1532
}
 
1533
 
 
1534
void
 
1535
gossip_chat_view_append_button (GossipChatView *view,
 
1536
                                const gchar    *message,
 
1537
                                GtkWidget      *button1,
 
1538
                                GtkWidget      *button2)
 
1539
{
 
1540
        GossipChatViewPriv   *priv;
 
1541
        GtkTextChildAnchor   *anchor;
 
1542
        GtkTextIter           iter;
 
1543
        gboolean              bottom;
 
1544
        const gchar          *tag;
 
1545
 
 
1546
        g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
 
1547
        g_return_if_fail (button1 != NULL);
 
1548
 
 
1549
        priv = GET_PRIV (view);
 
1550
 
 
1551
        if (priv->irc_style) {
 
1552
                tag = "irc-invite";
 
1553
        } else {
 
1554
                tag = "fancy-invite";
 
1555
        }
 
1556
 
 
1557
        bottom = chat_view_is_scrolled_down (view);
 
1558
 
 
1559
        chat_view_maybe_append_date_and_time (view, NULL);
 
1560
 
 
1561
        if (message) {
 
1562
                chat_view_append_text (view, message, tag);
 
1563
        }
 
1564
 
 
1565
        gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1566
 
 
1567
        anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
 
1568
        gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button1, anchor);
 
1569
        gtk_widget_show (button1);
 
1570
 
 
1571
        gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
 
1572
                                                  &iter,
 
1573
                                                  " ",
 
1574
                                                  1,
 
1575
                                                  tag,
 
1576
                                                  NULL);
 
1577
 
 
1578
        if (button2) {
 
1579
                gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1580
                
 
1581
                anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
 
1582
                gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button2, anchor);
 
1583
                gtk_widget_show (button2);
 
1584
                
 
1585
                gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
 
1586
                                                          &iter,
 
1587
                                                          " ",
 
1588
                                                          1,
 
1589
                                                          tag,
 
1590
                                                          NULL);
 
1591
        }
 
1592
 
 
1593
        gtk_text_buffer_get_end_iter (priv->buffer, &iter);
 
1594
        gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
 
1595
                                                  &iter,
 
1596
                                                  "\n\n",
 
1597
                                                  2,
 
1598
                                                  tag,
 
1599
                                                  NULL);
 
1600
 
 
1601
        if (bottom) {
 
1602
                gossip_chat_view_scroll_down (view);
 
1603
        }
 
1604
 
 
1605
        priv->last_block_type = BLOCK_TYPE_INVITE;
 
1606
}
 
1607
 
 
1608
void
 
1609
gossip_chat_view_scroll (GossipChatView *view,
 
1610
                         gboolean        allow_scrolling)
 
1611
{
 
1612
        GossipChatViewPriv *priv;
 
1613
 
 
1614
        g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
 
1615
 
 
1616
        priv = GET_PRIV (view);
 
1617
 
 
1618
        priv->allow_scrolling = allow_scrolling;
 
1619
 
 
1620
        gossip_debug (DEBUG_DOMAIN, "Scrolling %s",
 
1621
                      allow_scrolling ? "enabled" : "disabled");
 
1622
}
 
1623
 
 
1624
void
 
1625
gossip_chat_view_scroll_down (GossipChatView *view)
 
1626
{
 
1627
        GossipChatViewPriv *priv;
 
1628
        GtkTextBuffer      *buffer;
 
1629
        GtkTextIter         iter;
 
1630
        GtkTextMark        *mark;
 
1631
 
 
1632
        g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
 
1633
 
 
1634
        priv = GET_PRIV (view);
 
1635
 
 
1636
        if (!priv->allow_scrolling) {
 
1637
                return;
 
1638
        }
 
1639
 
 
1640
        gossip_debug (DEBUG_DOMAIN, "Scrolling down");
 
1641
 
 
1642
        buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
 
1643
 
 
1644
        gtk_text_buffer_get_end_iter (buffer, &iter);
 
1645
        mark = gtk_text_buffer_create_mark (buffer,
 
1646
                                            NULL,
 
1647
                                            &iter,
 
1648
                                            FALSE);
 
1649
 
 
1650
        gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
 
1651
                                      mark,
 
1652
                                      0.0,
 
1653
                                      FALSE,
 
1654
                                      0,
 
1655
                                      0);
 
1656
 
 
1657
        gtk_text_buffer_delete_mark (buffer, mark);
 
1658
}
 
1659
 
 
1660
gboolean
 
1661
gossip_chat_view_get_selection_bounds (GossipChatView *view,
 
1662
                                       GtkTextIter    *start,
 
1663
                                       GtkTextIter    *end)
 
1664
{
 
1665
        GtkTextBuffer *buffer;
 
1666
 
 
1667
        g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
 
1668
 
 
1669
        buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
 
1670
 
 
1671
        return gtk_text_buffer_get_selection_bounds (buffer, start, end);
 
1672
}
 
1673
 
 
1674
void
 
1675
gossip_chat_view_clear (GossipChatView *view)
 
1676
{
 
1677
        GtkTextBuffer      *buffer;
 
1678
        GossipChatViewPriv *priv;
 
1679
 
 
1680
        g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
 
1681
 
 
1682
        buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
 
1683
        gtk_text_buffer_set_text (buffer, "", -1);
 
1684
 
 
1685
        /* We set these back to the initial values so we get
 
1686
         * timestamps when clearing the window to know when
 
1687
         * conversations start.
 
1688
         */
 
1689
        priv = GET_PRIV (view);
 
1690
 
 
1691
        priv->last_block_type = BLOCK_TYPE_NONE;
 
1692
        priv->last_timestamp = 0;
 
1693
}
 
1694
 
 
1695
gboolean
 
1696
gossip_chat_view_find_previous (GossipChatView *view,
 
1697
                                const gchar    *search_criteria,
 
1698
                                gboolean        new_search)
 
1699
{
 
1700
        GossipChatViewPriv *priv;
 
1701
        GtkTextBuffer      *buffer;
 
1702
        GtkTextIter         iter_at_mark;
 
1703
        GtkTextIter         iter_match_start;
 
1704
        GtkTextIter         iter_match_end;
 
1705
        gboolean            found;
 
1706
        gboolean            from_start = FALSE;
 
1707
 
 
1708
        g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
 
1709
        g_return_val_if_fail (search_criteria != NULL, FALSE);
 
1710
 
 
1711
        priv = GET_PRIV (view);
 
1712
 
 
1713
        buffer = priv->buffer;
 
1714
 
 
1715
        if (G_STR_EMPTY (search_criteria)) {
 
1716
                if (priv->find_mark_previous) {
 
1717
                        gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
 
1718
 
 
1719
                        gtk_text_buffer_move_mark (buffer,
 
1720
                                                   priv->find_mark_previous,
 
1721
                                                   &iter_at_mark);
 
1722
                        gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
 
1723
                                                      priv->find_mark_previous,
 
1724
                                                      0.0,
 
1725
                                                      TRUE,
 
1726
                                                      0.0,
 
1727
                                                      0.0);
 
1728
                        gtk_text_buffer_select_range (buffer,
 
1729
                                                      &iter_at_mark,
 
1730
                                                      &iter_at_mark);
 
1731
                }
 
1732
 
 
1733
                return FALSE;
 
1734
        }
 
1735
 
 
1736
        if (new_search) {
 
1737
                from_start = TRUE;
 
1738
        }
 
1739
 
 
1740
        if (priv->find_mark_previous) {
 
1741
                gtk_text_buffer_get_iter_at_mark (buffer,
 
1742
                                                  &iter_at_mark,
 
1743
                                                  priv->find_mark_previous);
 
1744
        } else {
 
1745
                gtk_text_buffer_get_end_iter (buffer, &iter_at_mark);
 
1746
                from_start = TRUE;
 
1747
        }
 
1748
 
 
1749
        priv->find_last_direction = FALSE;
 
1750
 
 
1751
        found = gossip_text_iter_backward_search (&iter_at_mark,
 
1752
                                                  search_criteria,
 
1753
                                                  &iter_match_start,
 
1754
                                                  &iter_match_end,
 
1755
                                                  NULL);
 
1756
 
 
1757
        if (!found) {
 
1758
                gboolean result = FALSE;
 
1759
 
 
1760
                if (from_start) {
 
1761
                        return result;
 
1762
                }
 
1763
 
 
1764
                /* Here we wrap around. */
 
1765
                if (!new_search && !priv->find_wrapped) {
 
1766
                        priv->find_wrapped = TRUE;
 
1767
                        result = gossip_chat_view_find_previous (view, 
 
1768
                                                                 search_criteria, 
 
1769
                                                                 FALSE);
 
1770
                        priv->find_wrapped = FALSE;
 
1771
                }
 
1772
 
 
1773
                return result;
 
1774
        }
 
1775
 
 
1776
        /* Set new mark and show on screen */
 
1777
        if (!priv->find_mark_previous) {
 
1778
                priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
 
1779
                                                                        &iter_match_start,
 
1780
                                                                        TRUE);
 
1781
        } else {
 
1782
                gtk_text_buffer_move_mark (buffer,
 
1783
                                           priv->find_mark_previous,
 
1784
                                           &iter_match_start);
 
1785
        }
 
1786
 
 
1787
        if (!priv->find_mark_next) {
 
1788
                priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
 
1789
                                                                    &iter_match_end,
 
1790
                                                                    TRUE);
 
1791
        } else {
 
1792
                gtk_text_buffer_move_mark (buffer,
 
1793
                                           priv->find_mark_next,
 
1794
                                           &iter_match_end);
 
1795
        }
 
1796
 
 
1797
        gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
 
1798
                                      priv->find_mark_previous,
 
1799
                                      0.0,
 
1800
                                      TRUE,
 
1801
                                      0.5,
 
1802
                                      0.5);
 
1803
 
 
1804
        gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
 
1805
        gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
 
1806
 
 
1807
        return TRUE;
 
1808
}
 
1809
 
 
1810
gboolean
 
1811
gossip_chat_view_find_next (GossipChatView *view,
 
1812
                            const gchar    *search_criteria,
 
1813
                            gboolean        new_search)
 
1814
{
 
1815
        GossipChatViewPriv *priv;
 
1816
        GtkTextBuffer      *buffer;
 
1817
        GtkTextIter         iter_at_mark;
 
1818
        GtkTextIter         iter_match_start;
 
1819
        GtkTextIter         iter_match_end;
 
1820
        gboolean            found;
 
1821
        gboolean            from_start = FALSE;
 
1822
 
 
1823
        g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
 
1824
        g_return_val_if_fail (search_criteria != NULL, FALSE);
 
1825
 
 
1826
        priv = GET_PRIV (view);
 
1827
 
 
1828
        buffer = priv->buffer;
 
1829
 
 
1830
        if (G_STR_EMPTY (search_criteria)) {
 
1831
                if (priv->find_mark_next) {
 
1832
                        gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
 
1833
 
 
1834
                        gtk_text_buffer_move_mark (buffer,
 
1835
                                                   priv->find_mark_next,
 
1836
                                                   &iter_at_mark);
 
1837
                        gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
 
1838
                                                      priv->find_mark_next,
 
1839
                                                      0.0,
 
1840
                                                      TRUE,
 
1841
                                                      0.0,
 
1842
                                                      0.0);
 
1843
                        gtk_text_buffer_select_range (buffer,
 
1844
                                                      &iter_at_mark,
 
1845
                                                      &iter_at_mark);
 
1846
                }
 
1847
 
 
1848
                return FALSE;
 
1849
        }
 
1850
 
 
1851
        if (new_search) {
 
1852
                from_start = TRUE;
 
1853
        }
 
1854
 
 
1855
        if (priv->find_mark_next) {
 
1856
                gtk_text_buffer_get_iter_at_mark (buffer,
 
1857
                                                  &iter_at_mark,
 
1858
                                                  priv->find_mark_next);
 
1859
        } else {
 
1860
                gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
 
1861
                from_start = TRUE;
 
1862
        }
 
1863
 
 
1864
        priv->find_last_direction = TRUE;
 
1865
 
 
1866
        found = gossip_text_iter_forward_search (&iter_at_mark,
 
1867
                                                 search_criteria,
 
1868
                                                 &iter_match_start,
 
1869
                                                 &iter_match_end,
 
1870
                                                 NULL);
 
1871
 
 
1872
        if (!found) {
 
1873
                gboolean result = FALSE;
 
1874
 
 
1875
                if (from_start) {
 
1876
                        return result;
 
1877
                }
 
1878
 
 
1879
                /* Here we wrap around. */
 
1880
                if (!new_search && !priv->find_wrapped) {
 
1881
                        priv->find_wrapped = TRUE;
 
1882
                        result = gossip_chat_view_find_next (view, 
 
1883
                                                             search_criteria, 
 
1884
                                                             FALSE);
 
1885
                        priv->find_wrapped = FALSE;
 
1886
                }
 
1887
 
 
1888
                return result;
 
1889
        }
 
1890
 
 
1891
        /* Set new mark and show on screen */
 
1892
        if (!priv->find_mark_next) {
 
1893
                priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
 
1894
                                                               &iter_match_end,
 
1895
                                                               TRUE);
 
1896
        } else {
 
1897
                gtk_text_buffer_move_mark (buffer,
 
1898
                                           priv->find_mark_next,
 
1899
                                           &iter_match_end);
 
1900
        }
 
1901
 
 
1902
        if (!priv->find_mark_previous) {
 
1903
                priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
 
1904
                                                                        &iter_match_start,
 
1905
                                                                        TRUE);
 
1906
        } else {
 
1907
                gtk_text_buffer_move_mark (buffer,
 
1908
                                           priv->find_mark_previous,
 
1909
                                           &iter_match_start);
 
1910
        }
 
1911
 
 
1912
        gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
 
1913
                                      priv->find_mark_next,
 
1914
                                      0.0,
 
1915
                                      TRUE,
 
1916
                                      0.5,
 
1917
                                      0.5);
 
1918
 
 
1919
        gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
 
1920
        gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
 
1921
 
 
1922
        return TRUE;
 
1923
}
 
1924
 
 
1925
 
 
1926
void
 
1927
gossip_chat_view_find_abilities (GossipChatView *view,
 
1928
                                 const gchar    *search_criteria,
 
1929
                                 gboolean       *can_do_previous,
 
1930
                                 gboolean       *can_do_next)
 
1931
{
 
1932
        GossipChatViewPriv *priv;
 
1933
        GtkTextBuffer      *buffer;
 
1934
        GtkTextIter         iter_at_mark;
 
1935
        GtkTextIter         iter_match_start;
 
1936
        GtkTextIter         iter_match_end;
 
1937
 
 
1938
        g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
 
1939
        g_return_if_fail (search_criteria != NULL);
 
1940
        g_return_if_fail (can_do_previous != NULL && can_do_next != NULL);
 
1941
 
 
1942
        priv = GET_PRIV (view);
 
1943
 
 
1944
        buffer = priv->buffer;
 
1945
 
 
1946
        if (can_do_previous) {
 
1947
                if (priv->find_mark_previous) {
 
1948
                        gtk_text_buffer_get_iter_at_mark (buffer,
 
1949
                                                          &iter_at_mark,
 
1950
                                                          priv->find_mark_previous);
 
1951
                } else {
 
1952
                        gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
 
1953
                }
 
1954
                
 
1955
                *can_do_previous = gossip_text_iter_backward_search (&iter_at_mark,
 
1956
                                                                     search_criteria,
 
1957
                                                                     &iter_match_start,
 
1958
                                                                     &iter_match_end,
 
1959
                                                                     NULL);
 
1960
        }
 
1961
 
 
1962
        if (can_do_next) {
 
1963
                if (priv->find_mark_next) {
 
1964
                        gtk_text_buffer_get_iter_at_mark (buffer,
 
1965
                                                          &iter_at_mark,
 
1966
                                                          priv->find_mark_next);
 
1967
                } else {
 
1968
                        gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
 
1969
                }
 
1970
                
 
1971
                *can_do_next = gossip_text_iter_forward_search (&iter_at_mark,
 
1972
                                                                search_criteria,
 
1973
                                                                &iter_match_start,
 
1974
                                                                &iter_match_end,
 
1975
                                                                NULL);
 
1976
        }
 
1977
}
 
1978
 
 
1979
void
 
1980
gossip_chat_view_highlight (GossipChatView *view,
 
1981
                            const gchar    *text)
 
1982
{
 
1983
        GtkTextBuffer *buffer;
 
1984
        GtkTextIter    iter;
 
1985
        GtkTextIter    iter_start;
 
1986
        GtkTextIter    iter_end;
 
1987
        GtkTextIter    iter_match_start;
 
1988
        GtkTextIter    iter_match_end;
 
1989
        gboolean       found;
 
1990
 
 
1991
        g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
 
1992
 
 
1993
        buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
 
1994
 
 
1995
        gtk_text_buffer_get_start_iter (buffer, &iter);
 
1996
 
 
1997
        gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
 
1998
        gtk_text_buffer_remove_tag_by_name (buffer, "highlight",
 
1999
                                            &iter_start,
 
2000
                                            &iter_end);
 
2001
 
 
2002
        if (G_STR_EMPTY (text)) {
 
2003
                return;
 
2004
        }
 
2005
 
 
2006
        while (1) {
 
2007
                found = gossip_text_iter_forward_search (&iter,
 
2008
                                                         text,
 
2009
                                                         &iter_match_start,
 
2010
                                                         &iter_match_end,
 
2011
                                                         NULL);
 
2012
 
 
2013
                if (!found) {
 
2014
                        break;
 
2015
                }
 
2016
 
 
2017
                gtk_text_buffer_apply_tag_by_name (buffer, "highlight",
 
2018
                                                   &iter_match_start,
 
2019
                                                   &iter_match_end);
 
2020
 
 
2021
                iter = iter_match_end;
 
2022
                gtk_text_iter_forward_char (&iter);
 
2023
        }
 
2024
}
 
2025
 
 
2026
void
 
2027
gossip_chat_view_copy_clipboard (GossipChatView *view)
 
2028
{
 
2029
        GtkTextBuffer *buffer;
 
2030
        GtkClipboard  *clipboard;
 
2031
 
 
2032
        g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
 
2033
 
 
2034
        buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
 
2035
        clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
 
2036
 
 
2037
        gtk_text_buffer_copy_clipboard (buffer, clipboard);
 
2038
}
 
2039
 
 
2040
gboolean
 
2041
gossip_chat_view_get_irc_style (GossipChatView *view)
 
2042
{
 
2043
        GossipChatViewPriv *priv;
 
2044
 
 
2045
        g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
 
2046
 
 
2047
        priv = GET_PRIV (view);
 
2048
 
 
2049
        return priv->irc_style;
 
2050
}
 
2051
 
 
2052
void
 
2053
gossip_chat_view_set_irc_style (GossipChatView *view,
 
2054
                                gboolean        irc_style)
 
2055
{
 
2056
        GossipChatViewPriv *priv;
 
2057
 
 
2058
        g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
 
2059
 
 
2060
        priv = GET_PRIV (view);
 
2061
 
 
2062
        priv->irc_style = irc_style;
 
2063
}
 
2064
 
 
2065
void
 
2066
gossip_chat_view_set_margin (GossipChatView *view,
 
2067
                             gint            margin)
 
2068
{
 
2069
        GossipChatViewPriv *priv;
 
2070
 
 
2071
        g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
 
2072
 
 
2073
        priv = GET_PRIV (view);
 
2074
 
 
2075
        g_object_set (view,
 
2076
                      "left-margin", margin,
 
2077
                      "right-margin", margin,
 
2078
                      NULL);
 
2079
}
 
2080
 
 
2081
GdkPixbuf *
 
2082
gossip_chat_view_get_smiley_image (GossipSmiley smiley)
 
2083
{
 
2084
        static GdkPixbuf *pixbufs[GOSSIP_SMILEY_COUNT];
 
2085
        static gboolean   inited = FALSE;
 
2086
 
 
2087
        if (!inited) {
 
2088
                gint i;
 
2089
 
 
2090
                for (i = 0; i < GOSSIP_SMILEY_COUNT; i++) {
 
2091
                        pixbufs[i] = gossip_pixbuf_from_smiley (i, GTK_ICON_SIZE_MENU);
 
2092
                }
 
2093
 
 
2094
                inited = TRUE;
 
2095
        }
 
2096
 
 
2097
        return pixbufs[smiley];
 
2098
}
 
2099
 
 
2100
const gchar *
 
2101
gossip_chat_view_get_smiley_text (GossipSmiley smiley)
 
2102
{
 
2103
        gint i;
 
2104
 
 
2105
        for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
 
2106
                if (smileys[i].smiley != smiley) {
 
2107
                        continue;
 
2108
                }
 
2109
 
 
2110
                return smileys[i].pattern;
 
2111
        }
 
2112
 
 
2113
        return NULL;
 
2114
}
 
2115
 
 
2116
GtkWidget *
 
2117
gossip_chat_view_get_smiley_menu (GCallback    callback,
 
2118
                                  gpointer     user_data,
 
2119
                                  GtkTooltips *tooltips)
 
2120
{
 
2121
        GtkWidget *menu;
 
2122
        gint       x;
 
2123
        gint       y;
 
2124
        gint       i;
 
2125
 
 
2126
        g_return_val_if_fail (callback != NULL, NULL);
 
2127
 
 
2128
        menu = gtk_menu_new ();
 
2129
 
 
2130
        for (i = 0, x = 0, y = 0; i < GOSSIP_SMILEY_COUNT; i++) {
 
2131
                GtkWidget   *item;
 
2132
                GtkWidget   *image;
 
2133
                GdkPixbuf   *pixbuf;
 
2134
                const gchar *smiley_text;
 
2135
 
 
2136
                pixbuf = gossip_chat_view_get_smiley_image (i);
 
2137
                if (!pixbuf) {
 
2138
                        continue;
 
2139
                }
 
2140
 
 
2141
                image = gtk_image_new_from_pixbuf (pixbuf);
 
2142
 
 
2143
                item = gtk_image_menu_item_new_with_label ("");
 
2144
                gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
 
2145
 
 
2146
                gtk_menu_attach (GTK_MENU (menu), item,
 
2147
                                 x, x + 1, y, y + 1);
 
2148
 
 
2149
                smiley_text = gossip_chat_view_get_smiley_text (i);
 
2150
 
 
2151
                gtk_tooltips_set_tip (tooltips,
 
2152
                                      item,
 
2153
                                      smiley_text,
 
2154
                                      NULL);
 
2155
 
 
2156
                g_object_set_data  (G_OBJECT (item), "smiley_text", (gpointer) smiley_text);
 
2157
                g_signal_connect (item, "activate", callback, user_data);
 
2158
 
 
2159
                if (x > 3) {
 
2160
                        y++;
 
2161
                        x = 0;
 
2162
                } else {
 
2163
                        x++;
 
2164
                }
 
2165
        }
 
2166
 
 
2167
        gtk_widget_show_all (menu);
 
2168
 
 
2169
        return menu;
 
2170
}
 
2171
 
 
2172
/* FIXME: Do we really need this? Better to do it internally only at setup time,
 
2173
 * we will never change it on the fly.
 
2174
 */
 
2175
void
 
2176
gossip_chat_view_set_is_group_chat (GossipChatView *view,
 
2177
                                    gboolean        is_group_chat)
 
2178
{
 
2179
        GossipChatViewPriv *priv;
 
2180
        gboolean            theme_rooms = FALSE;
 
2181
 
 
2182
        g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
 
2183
 
 
2184
        priv = GET_PRIV (view);
 
2185
 
 
2186
        priv->is_group_chat = is_group_chat;
 
2187
 
 
2188
        gossip_conf_get_bool (gossip_conf_get (),
 
2189
                              GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
 
2190
                              &theme_rooms);
 
2191
 
 
2192
        if (!theme_rooms && is_group_chat) {
 
2193
                gossip_theme_manager_apply (gossip_theme_manager_get (),
 
2194
                                            view,
 
2195
                                            NULL);
 
2196
        } else {
 
2197
                gossip_theme_manager_apply_saved (gossip_theme_manager_get (),
 
2198
                                                  view);
 
2199
        }
 
2200
}