~ubuntu-branches/ubuntu/saucy/sflphone/saucy

« back to all changes in this revision

Viewing changes to sflphone-client-gnome/src/widget/imwidget.c

  • Committer: Bazaar Package Importer
  • Author(s): Francois Marier
  • Date: 2010-12-24 16:33:55 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20101224163355-tkvvikqxbrbav6up
Tags: 0.9.11-1
* New upstream release
* Add new build dependencies on libwebkit-dev and libyaml-dev

* Bump Standards-Version up to 3.9.1
* Bump debhelper compatibility to 8
* Patch another typo in the upstream code (lintian notice)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *  Copyright (C) 2010 Savoir-Faire Linux Inc.
 
3
 *
 
4
 *  This program is free software; you can redistribute it and/or modify
 
5
 *  it under the terms of the GNU General Public License as published by
 
6
 *  the Free Software Foundation; either version 3 of the License, or
 
7
 *  (at your option) any later version.
 
8
 *
 
9
 *  This program is distributed in the hope that it will be useful,
 
10
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
 *  GNU General Public License for more details.
 
13
 *
 
14
 *  You should have received a copy of the GNU General Public License
 
15
 *  along with this program; if not, write to the Free Software
 
16
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
17
 *
 
18
 *  Additional permission under GNU GPL version 3 section 7:
 
19
 *
 
20
 *  If you modify this program, or any covered work, by linking or
 
21
 *  combining it with the OpenSSL project's OpenSSL library (or a
 
22
 *  modified version of that library), containing parts covered by the
 
23
 *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
 
24
 *  grants you additional permission to convey the resulting work.
 
25
 *  Corresponding Source for a non-source form of such a combination
 
26
 *  shall include the source code for the parts of OpenSSL used as well
 
27
 *  as that of the covered work.
 
28
 */
 
29
 
 
30
#include <imwindow.h>
 
31
#include "imwidget.h"
 
32
#include <icons/icon_factory.h>
 
33
#include <contacts/calltab.h>
 
34
#include <contacts/conferencelist.h>
 
35
#include <JavaScriptCore/JavaScript.h>
 
36
#include <gdk/gdkkeysyms.h>
 
37
 
 
38
#define WEBKIT_DIR "file://" DATA_DIR "/webkit/"
 
39
 
 
40
static void
 
41
on_frame_loading_done (GObject *gobject UNUSED, GParamSpec *pspec UNUSED, gpointer user_data)
 
42
{
 
43
    IMWidget *im = IM_WIDGET (user_data);
 
44
    callable_obj_t *call;
 
45
    conference_obj_t *conf;
 
46
 
 
47
    if (im->first_message && im->first_message_from) {
 
48
        switch (webkit_web_frame_get_load_status (WEBKIT_WEB_FRAME (im->web_frame))) {
 
49
            case WEBKIT_LOAD_PROVISIONAL:
 
50
            case WEBKIT_LOAD_COMMITTED:
 
51
                break;
 
52
            case WEBKIT_LOAD_FINISHED:
 
53
                call = calllist_get (current_calls, im->call_id);
 
54
                conf = conferencelist_get (im->call_id);
 
55
 
 
56
                if (call)
 
57
                    im_widget_add_message (im, im->first_message_from, im->first_message, 0);
 
58
 
 
59
                if (conf)
 
60
                    im_widget_add_message (im, im->first_message_from, im->first_message, 0);
 
61
 
 
62
                g_free (im->first_message);
 
63
                g_free (im->first_message_from);
 
64
                im->first_message = NULL;
 
65
                im->first_message_from = NULL;
 
66
                DEBUG ("JavaScrip loading frame finished");
 
67
                break;
 
68
            case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
 
69
                // case WEBKIT_LOAD_FAILED: // only available in webkit-1.0-2
 
70
                break;
 
71
        }
 
72
    }
 
73
 
 
74
}
 
75
 
 
76
gchar *
 
77
escape_single_quotes (const gchar *message)
 
78
{
 
79
    gchar **ptr_token;
 
80
    gchar *string = "";
 
81
 
 
82
    DEBUG ("message: %s", message);
 
83
 
 
84
    if ( (ptr_token = g_strsplit (message, "'", 0))) {
 
85
        DEBUG ("SPLITTING");
 
86
        string = g_strjoinv ("\\'", ptr_token);
 
87
    }
 
88
 
 
89
    return string;
 
90
}
 
91
 
 
92
void
 
93
im_widget_add_message (IMWidget *im, const gchar *from, const gchar *message, gint level)
 
94
{
 
95
 
 
96
    if (im) {
 
97
 
 
98
        /* Compute the date the message was sent */
 
99
        gchar *msgtime = im_widget_add_message_time ();
 
100
 
 
101
        /* Check for the message level */
 
102
        gchar *css_class = (level == MESSAGE_LEVEL_ERROR) ? "error" : "";
 
103
 
 
104
        gchar *message_escaped = escape_single_quotes (message);
 
105
 
 
106
        /* Prepare and execute the Javascript code */
 
107
        gchar *script = g_strdup_printf ("add_message('%s', '%s', '%s', '%s');", message_escaped, from, css_class, msgtime);
 
108
        webkit_web_view_execute_script (WEBKIT_WEB_VIEW (im->web_view), script);
 
109
 
 
110
        /* Mark it as used */
 
111
        im->containText = TRUE;
 
112
 
 
113
        /* Cleanup */
 
114
        g_free (script);
 
115
        g_free (message_escaped);
 
116
 
 
117
    }
 
118
}
 
119
 
 
120
static gboolean
 
121
web_view_nav_requested_cb (
 
122
    WebKitWebView             *web_view UNUSED,
 
123
    WebKitWebFrame            *frame UNUSED,
 
124
    WebKitNetworkRequest      *request,
 
125
    WebKitWebNavigationAction *navigation_action UNUSED,
 
126
    WebKitWebPolicyDecision   *policy_decision,
 
127
    gpointer                   user_data UNUSED)
 
128
{
 
129
    const gchar *uri = webkit_network_request_get_uri (request);
 
130
 
 
131
    /* Always allow files we are serving ourselves. */
 
132
    if (!strncmp (uri, WEBKIT_DIR, sizeof (WEBKIT_DIR) - 1)) {
 
133
        webkit_web_policy_decision_use (policy_decision);
 
134
    } else {
 
135
        /* Running a system command to open the URL in the user's default browser */
 
136
        gchar *cmd = g_strdup_printf ("x-www-browser %s", uri);
 
137
 
 
138
        if (system (cmd) == -1)
 
139
            ERROR ("Error executing command %s", cmd);
 
140
 
 
141
        webkit_web_policy_decision_ignore (policy_decision);
 
142
        g_free (cmd);
 
143
    }
 
144
 
 
145
    return TRUE;
 
146
}
 
147
 
 
148
static gboolean
 
149
on_Textview_changed (GtkWidget *widget UNUSED, GdkEventKey *event, gpointer user_data)
 
150
{
 
151
 
 
152
    GtkTextIter start, end;
 
153
    /* Get all the text in the buffer */
 
154
    IMWidget *im =  user_data;
 
155
 
 
156
    GtkTextBuffer *buffer =  gtk_text_view_get_buffer (GTK_TEXT_VIEW (im->textarea));
 
157
 
 
158
    /* Catch the keyboard events */
 
159
    if (event->type == GDK_KEY_PRESS) {
 
160
 
 
161
        switch (event->keyval) {
 
162
            case GDK_Return:
 
163
            case GDK_KP_Enter:
 
164
 
 
165
                /* We want to send the message on pressing ENTER */
 
166
                if (gtk_text_buffer_get_char_count (buffer) != 0) {
 
167
                    /* Fetch the string text */
 
168
                    gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end);
 
169
                    gchar *message = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
 
170
 
 
171
                    /* Display our own message in the chat window */
 
172
                    im_widget_add_message (im, "Me", message, MESSAGE_LEVEL_NORMAL);
 
173
 
 
174
                    /* Send the message to the peer */
 
175
                    im_widget_send_message (im->call_id, message);
 
176
 
 
177
                    /* Empty the buffer */
 
178
                    gtk_text_buffer_delete (GTK_TEXT_BUFFER (buffer), &start, &end);
 
179
 
 
180
                }
 
181
 
 
182
                return TRUE;
 
183
        }
 
184
    }
 
185
 
 
186
    return FALSE;
 
187
}
 
188
 
 
189
gchar*
 
190
im_widget_add_message_time ()
 
191
{
 
192
 
 
193
    time_t now;
 
194
    unsigned char str[100];
 
195
 
 
196
    /* Compute the current time */
 
197
    (void) time (&now);
 
198
    struct tm* ptr;
 
199
    ptr = localtime (&now);
 
200
 
 
201
    /* Get the time of the message. Format: HH:MM::SS */
 
202
    strftime ( (char *) str, 100, "%R", (const struct tm *) ptr);
 
203
    gchar *res = g_strdup ( (gchar *) str);
 
204
 
 
205
    /* Return the new value */
 
206
    return res;
 
207
}
 
208
 
 
209
void
 
210
im_widget_send_message (gchar *id, const gchar *message)
 
211
{
 
212
 
 
213
    callable_obj_t *im_widget_call = calllist_get (current_calls, id);
 
214
    conference_obj_t *im_widget_conf = conferencelist_get (id);
 
215
 
 
216
    /* If the call has been hungup, it is not anymore in the current_calls calltab */
 
217
    if (!im_widget_call) {
 
218
        /* So try the history tab */
 
219
        im_widget_call = calllist_get (history, id);
 
220
    }
 
221
 
 
222
    if (im_widget_conf) {
 
223
        dbus_send_text_message (id, message);
 
224
    }
 
225
    /* First check if the call is in CURRENT state, otherwise it could not be sent */
 
226
    else if (im_widget_call) {
 
227
        if (im_widget_call->_type == CALL && (im_widget_call->_state == CALL_STATE_CURRENT ||
 
228
                                              im_widget_call->_state == CALL_STATE_HOLD ||
 
229
                                              im_widget_call->_state == CALL_STATE_RECORD)) {
 
230
            /* Ship the message through D-Bus */
 
231
            dbus_send_text_message (id, message);
 
232
        } else {
 
233
            /* Display an error message */
 
234
            im_widget_add_message (IM_WIDGET (im_widget_call->_im_widget), "sflphoned", "Oups, something went wrong! Unable to send text messages outside a call.", MESSAGE_LEVEL_ERROR);
 
235
        }
 
236
    }
 
237
}
 
238
 
 
239
 
 
240
static void
 
241
im_widget_class_init (IMWidgetClass *klass UNUSED)
 
242
{
 
243
}
 
244
 
 
245
static void
 
246
im_widget_init (IMWidget *im)
 
247
{
 
248
    /* A text view to enable users to enter text */
 
249
    im->textarea = gtk_text_view_new ();
 
250
 
 
251
    /* The webkit widget to display the message */
 
252
    im->web_view = webkit_web_view_new();
 
253
    GtkWidget *textscrollwin = gtk_scrolled_window_new (NULL, NULL);
 
254
    GtkWidget *webscrollwin = gtk_scrolled_window_new (NULL, NULL);
 
255
    im->info_bar = gtk_info_bar_new ();
 
256
 
 
257
    /* A bar with the entry text and the button to send the message */
 
258
    GtkWidget *hbox = gtk_hbox_new (FALSE, 10);
 
259
    gtk_text_view_set_editable (GTK_TEXT_VIEW (im->textarea), TRUE);
 
260
    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (textscrollwin), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
 
261
    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (webscrollwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
 
262
    gtk_widget_set_size_request (GTK_WIDGET (textscrollwin), -1, 20);
 
263
    gtk_widget_set_size_request (GTK_WIDGET (im->textarea), -1, 20);
 
264
    gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (im->textarea), GTK_WRAP_CHAR);
 
265
    // gtk_container_set_resize_mode(GTK_CONTAINER(im->textarea), GTK_RESIZE_PARENT);
 
266
 
 
267
    gtk_container_add (GTK_CONTAINER (textscrollwin), im->textarea);
 
268
    gtk_container_add (GTK_CONTAINER (webscrollwin), im->web_view);
 
269
    gtk_container_add (GTK_CONTAINER (hbox), textscrollwin);
 
270
    gtk_box_pack_start (GTK_BOX (im), im->info_bar, FALSE, FALSE, 2);
 
271
    gtk_box_pack_start (GTK_BOX (im), webscrollwin, TRUE, TRUE, 5);
 
272
    gtk_box_pack_end (GTK_BOX (im), hbox, FALSE, FALSE, 2);
 
273
    g_signal_connect (im->web_view, "navigation-policy-decision-requested", G_CALLBACK (web_view_nav_requested_cb), NULL);
 
274
    g_signal_connect (im->textarea, "key-press-event", G_CALLBACK (on_Textview_changed), im);
 
275
 
 
276
    im->web_frame = webkit_web_view_get_main_frame (WEBKIT_WEB_VIEW (im->web_view));
 
277
    im->js_context = webkit_web_frame_get_global_context (im->web_frame);
 
278
    im->js_global = JSContextGetGlobalObject (im->js_context);
 
279
    webkit_web_view_load_uri (WEBKIT_WEB_VIEW (im->web_view), "file://" DATA_DIR "/webkit/im/im.html");
 
280
 
 
281
    im->containText = FALSE;
 
282
 
 
283
    g_signal_connect (G_OBJECT (im->web_frame), "notify", G_CALLBACK (on_frame_loading_done), im);
 
284
}
 
285
 
 
286
GtkWidget *
 
287
im_widget_new()
 
288
{
 
289
    return GTK_WIDGET (g_object_new (IM_WIDGET_TYPE, NULL));
 
290
}
 
291
 
 
292
GtkWidget *
 
293
im_widget_new_with_first_message (const gchar *message UNUSED)
 
294
{
 
295
    return GTK_WIDGET (g_object_new (IM_WIDGET_TYPE, NULL));
 
296
    // return GTK_WIDGET (g_object_new (IM_WIDGET_TYPE, "first_message", message, NULL));
 
297
}
 
298
 
 
299
 
 
300
GType
 
301
im_widget_get_type (void)
 
302
{
 
303
    static GType im_widget_type = 0;
 
304
 
 
305
    if (!im_widget_type) {
 
306
        static const GTypeInfo im_widget_info = {
 
307
            sizeof (IMWidgetClass),
 
308
            NULL, /* base_init */
 
309
            NULL, /* base_finalize */
 
310
            (GClassInitFunc) im_widget_class_init,
 
311
            NULL, /* class_finalize */
 
312
            NULL, /* class_data */
 
313
            sizeof (IMWidget),
 
314
            0,
 
315
            (GInstanceInitFunc) im_widget_init,
 
316
            NULL  /* value_table */
 
317
        };
 
318
 
 
319
        im_widget_type = g_type_register_static (
 
320
                             GTK_TYPE_VBOX,
 
321
                             "IMWidget",
 
322
                             &im_widget_info,
 
323
                             0);
 
324
    }
 
325
 
 
326
    return im_widget_type;
 
327
}
 
328
 
 
329
gboolean
 
330
im_widget_display (IMWidget **im, const gchar *message, const gchar *id, const gchar *from)
 
331
{
 
332
 
 
333
    /* Work with a copy of the object */
 
334
    // callable_obj_t *tmp = *call;
 
335
 
 
336
    /* Use the widget for this specific call, if exists */
 
337
    // if (tmp) {
 
338
    IMWidget *imwidget = *im;// = IM_WIDGET (tmp->_im_widget);
 
339
 
 
340
    if (!imwidget) {
 
341
        DEBUG ("creating the im widget for this call\n");
 
342
 
 
343
        /* Create the im object, first message must be created asynchronously */
 
344
        if (message)
 
345
            imwidget = IM_WIDGET (im_widget_new ());
 
346
        else
 
347
            imwidget = IM_WIDGET (im_widget_new_with_first_message (message));
 
348
 
 
349
        /* Keep a reference on this object in the call struct */
 
350
        // tmp->_im_widget = im;
 
351
        // *call = tmp;
 
352
 
 
353
        /* Update the widget with some useful call information: ie the call ID */
 
354
        imwidget->call_id = id;
 
355
 
 
356
        /* Create the GtkInfoBar, used to display call information, and status of the IM widget */
 
357
        im_widget_infobar (imwidget);
 
358
 
 
359
        /* Add it to the main instant messaging window */
 
360
        im_window_add (GTK_WIDGET (imwidget));
 
361
 
 
362
        /* Update the first message to appears at widget creation*/
 
363
        if (message)
 
364
            imwidget->first_message = g_strdup (message);
 
365
 
 
366
        if (from)
 
367
            imwidget->first_message_from = g_strdup (from);
 
368
 
 
369
        *im = imwidget;
 
370
 
 
371
        return FALSE;
 
372
    } else {
 
373
        DEBUG ("im widget exists for this call\n");
 
374
        im_window_show ();
 
375
 
 
376
        return TRUE;
 
377
    }
 
378
 
 
379
    // }
 
380
 
 
381
    // return FALSE;
 
382
}
 
383
 
 
384
void
 
385
im_widget_infobar (IMWidget *im)
 
386
{
 
387
 
 
388
    /* Fetch the GTKInfoBar of this very IM Widget */
 
389
    GtkWidget *infobar = im->info_bar;
 
390
    GtkWidget *content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (infobar));
 
391
 
 
392
    /* Fetch call/conference information */
 
393
    callable_obj_t *im_widget_call = calllist_get (current_calls, im->call_id);
 
394
    conference_obj_t *im_widget_conf = conferencelist_get (im->call_id);
 
395
 
 
396
    /* Create the label widgets with the call information saved in the IM Widget struct */
 
397
    gchar *msg1;
 
398
 
 
399
    if (im_widget_call)
 
400
        msg1 = g_strdup_printf ("Calling %s  %s", im_widget_call->_peer_number, im_widget_call->_peer_name);
 
401
    else if (im_widget_conf)
 
402
        msg1 = g_strdup_printf ("Conferencing"); // im_widget_conf->_confID);
 
403
    else
 
404
        msg1 = g_strdup ("");
 
405
 
 
406
    GtkWidget *call_label = gtk_label_new (msg1);
 
407
 
 
408
    if (im_widget_call)
 
409
        im->info_state = call_state_image_widget (im_widget_call->_state);
 
410
 
 
411
    if (im_widget_conf)
 
412
        im->info_state = conf_state_image_widget (im_widget_conf->_state);
 
413
 
 
414
    /* Add a nice icon from our own icon factory */
 
415
    GtkWidget *logoUser = gtk_image_new_from_stock (GTK_STOCK_USER, GTK_ICON_SIZE_LARGE_TOOLBAR);
 
416
 
 
417
    /* Pack it all */
 
418
    gtk_container_add (GTK_CONTAINER (content_area), logoUser);
 
419
    gtk_container_add (GTK_CONTAINER (content_area), call_label);
 
420
    gtk_container_add (GTK_CONTAINER (content_area), im->info_state);
 
421
 
 
422
    /* Message level by default: INFO */
 
423
    gtk_info_bar_set_message_type (GTK_INFO_BAR (infobar), GTK_MESSAGE_INFO);
 
424
 
 
425
    /* Show the info bar */
 
426
    gtk_widget_show (infobar);
 
427
 
 
428
    /* Clean up */
 
429
    free (msg1);
 
430
}
 
431
 
 
432
GtkWidget*
 
433
call_state_image_widget (call_state_t state)
 
434
{
 
435
 
 
436
    GtkWidget *image;
 
437
 
 
438
    switch (state) {
 
439
        case CALL_STATE_CURRENT:
 
440
        case CALL_STATE_HOLD:
 
441
        case CALL_STATE_RECORD:
 
442
            image = gtk_image_new_from_stock (GTK_STOCK_IM, GTK_ICON_SIZE_LARGE_TOOLBAR);
 
443
            break;
 
444
        default:
 
445
            image = gtk_image_new_from_stock (GTK_STOCK_IM, GTK_ICON_SIZE_LARGE_TOOLBAR);
 
446
            break;
 
447
 
 
448
    }
 
449
 
 
450
    return image;
 
451
}
 
452
 
 
453
GtkWidget*
 
454
conf_state_image_widget (conference_state_t state)
 
455
{
 
456
 
 
457
    GtkWidget *image;
 
458
 
 
459
    switch (state) {
 
460
        case CONFERENCE_STATE_ACTIVE_ATACHED:
 
461
        case CONFERENCE_STATE_ACTIVE_DETACHED:
 
462
        case CONFERENCE_STATE_RECORD:
 
463
        case CONFERENCE_STATE_HOLD:
 
464
            image = gtk_image_new_from_stock (GTK_STOCK_IM, GTK_ICON_SIZE_LARGE_TOOLBAR);
 
465
            break;
 
466
        default:
 
467
            image = gtk_image_new_from_stock (GTK_STOCK_FAIL, GTK_ICON_SIZE_LARGE_TOOLBAR);
 
468
            break;
 
469
    }
 
470
 
 
471
    return image;
 
472
}
 
473
 
 
474
void
 
475
im_widget_update_state (IMWidget *im, gboolean active)
 
476
{
 
477
    /* if active = true, it means that we are the call is in current state, so sflphone can send text messages */
 
478
    if (active) {
 
479
        gtk_widget_set_sensitive (im->info_state, TRUE);
 
480
        gtk_info_bar_set_message_type (GTK_INFO_BAR (im->info_bar),
 
481
                                       GTK_MESSAGE_INFO);
 
482
    }
 
483
    /* if active = false, the call is over, we can't send text messages anymore */
 
484
    else {
 
485
        if (im) {
 
486
            gtk_widget_set_sensitive (im->info_state, FALSE);
 
487
            gtk_info_bar_set_message_type (GTK_INFO_BAR (im->info_bar),
 
488
                                           GTK_MESSAGE_WARNING);
 
489
            gtk_widget_set_tooltip_text (im->info_state, "Call has terminated");
 
490
        }
 
491
    }
 
492
}
 
493
 
 
494
 
 
495