Merge branch 'gnome-3-8'
[empathy.git] / src / empathy-debug-window.c
1 /*
2 *  Copyright (C) 2009 Collabora Ltd.
3 *
4 *  This library is free software; you can redistribute it and/or
5 *  modify it under the terms of the GNU Lesser General Public
6 *  License as published by the Free Software Foundation; either
7 *  version 2.1 of the License, or (at your option) any later version.
8 *
9 *  This library 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 GNU
12 *  Lesser General Public License for more details.
13 *
14 *  You should have received a copy of the GNU Lesser General Public
15 *  License along with this library; if not, write to the Free Software
16 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 *
18 *  Authors: Jonny Lamb <jonny.lamb@collabora.co.uk>
19 *           Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
20 */
21
22 #include "config.h"
23 #include "empathy-debug-window.h"
24
25 #include <glib/gi18n.h>
26 #include <libsoup/soup.h>
27 #include <tp-account-widgets/tpaw-utils.h>
28
29 #include "empathy-geometry.h"
30 #include "empathy-ui-utils.h"
31 #include "empathy-utils.h"
32
33 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
34 #include "empathy-debug.h"
35
36 G_DEFINE_TYPE (EmpathyDebugWindow, empathy_debug_window,
37     GTK_TYPE_WINDOW)
38
39 typedef enum
40 {
41   SERVICE_TYPE_CM = 0,
42   SERVICE_TYPE_CLIENT,
43 } ServiceType;
44
45 enum
46 {
47   COL_DEBUG_MESSAGE = 0,
48   NUM_DEBUG_COLS
49 };
50
51 enum
52 {
53   COL_NAME = 0,
54   COL_UNIQUE_NAME,
55   COL_GONE,
56   COL_ACTIVE_BUFFER,
57   COL_PAUSE_BUFFER,
58   COL_PROXY,
59   NUM_COLS
60 };
61
62 enum
63 {
64   COL_LEVEL_NAME,
65   COL_LEVEL_VALUE,
66   NUM_COLS_LEVEL
67 };
68
69 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyDebugWindow)
70 struct _EmpathyDebugWindowPriv
71 {
72   /* Toolbar items */
73   GtkWidget *chooser;
74   GtkToolItem *save_button;
75   GtkToolItem *send_to_pastebin;
76   GtkToolItem *copy_button;
77   GtkToolItem *clear_button;
78   GtkToolItem *pause_button;
79   GtkToolItem *level_label;
80   GtkWidget *level_filter;
81
82   /* TreeView */
83   GtkTreeModel *store_filter;
84   GtkWidget *view;
85   GtkWidget *scrolled_win;
86   GtkWidget *not_supported_label;
87   gboolean view_visible;
88
89   /* Connection */
90   TpDBusDaemon *dbus;
91   TpProxySignalConnection *name_owner_changed_signal;
92
93   /* Whether NewDebugMessage will be fired */
94   gboolean paused;
95
96   /* Service (CM, Client) chooser store */
97   GtkListStore *service_store;
98
99   /* Counters on services detected and added */
100   guint services_detected;
101   guint name_owner_cb_count;
102
103   /* Debug to show upon creation */
104   gchar *select_name;
105
106   /* Misc. */
107   gboolean dispose_run;
108   TpAccountManager *am;
109   GtkListStore *all_active_buffer;
110 };
111
112 static const gchar *
113 log_level_to_string (GLogLevelFlags level)
114 {
115   switch (level)
116     {
117     case G_LOG_LEVEL_ERROR:
118       return "Error";
119       break;
120     case G_LOG_LEVEL_CRITICAL:
121       return "Critical";
122       break;
123     case G_LOG_LEVEL_WARNING:
124       return "Warning";
125       break;
126     case G_LOG_LEVEL_MESSAGE:
127       return "Message";
128       break;
129     case G_LOG_LEVEL_INFO:
130       return "Info";
131       break;
132     case G_LOG_LEVEL_DEBUG:
133       return "Debug";
134       break;
135     default:
136       g_assert_not_reached ();
137       break;
138     }
139 }
140
141 static gchar *
142 get_active_service_name (EmpathyDebugWindow *self)
143 {
144   GtkTreeIter iter;
145   gchar *name;
146
147   if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self->priv->chooser),
148         &iter))
149     return NULL;
150
151   gtk_tree_model_get (GTK_TREE_MODEL (self->priv->service_store), &iter,
152       COL_NAME, &name, -1);
153
154   return name;
155 }
156
157 static gboolean
158 copy_buffered_messages (GtkTreeModel *buffer,
159     GtkTreePath *path,
160     GtkTreeIter *iter,
161     gpointer data)
162 {
163   GtkListStore *active_buffer = data;
164   GtkTreeIter active_buffer_iter;
165   TpDebugMessage *msg;
166
167   gtk_tree_model_get (buffer, iter,
168       COL_DEBUG_MESSAGE, &msg,
169       -1);
170   gtk_list_store_insert_with_values (active_buffer, &active_buffer_iter, -1,
171       COL_DEBUG_MESSAGE, msg,
172       -1);
173
174   g_object_unref (msg);
175
176   return FALSE;
177 }
178
179 static void
180 insert_values_in_buffer (GtkListStore *store,
181     TpDebugMessage *msg)
182 {
183   GtkTreeIter iter;
184
185   gtk_list_store_insert_with_values (store, &iter, -1,
186       COL_DEBUG_MESSAGE, msg,
187       -1);
188 }
189
190 static void
191 debug_window_add_message (EmpathyDebugWindow *self,
192     TpDebugClient *debug,
193     TpDebugMessage *msg)
194 {
195   GtkListStore *active_buffer, *pause_buffer;
196
197   pause_buffer = g_object_get_data (G_OBJECT (debug), "pause-buffer");
198   active_buffer = g_object_get_data (G_OBJECT (debug), "active-buffer");
199
200   if (self->priv->paused)
201     {
202       insert_values_in_buffer (pause_buffer, msg);
203     }
204   else
205     {
206       /* Append 'this' message to this service's and All's active-buffers */
207       insert_values_in_buffer (active_buffer, msg);
208
209       insert_values_in_buffer (self->priv->all_active_buffer, msg);
210     }
211 }
212
213 static void
214 debug_window_new_debug_message_cb (TpDebugClient *debug,
215     TpDebugMessage *msg,
216     gpointer user_data)
217 {
218   EmpathyDebugWindow *self = user_data;
219
220   debug_window_add_message (self, debug, msg);
221 }
222
223 static void
224 set_enabled_cb (GObject *source,
225     GAsyncResult *result,
226     gpointer user_data)
227 {
228   TpDebugClient *debug = TP_DEBUG_CLIENT (source);
229   gboolean enabled = GPOINTER_TO_UINT (user_data);
230   GError *error = NULL;
231
232   if (!tp_debug_client_set_enabled_finish (debug, result, &error))
233     {
234       DEBUG ("Failed to %s debugging on %s", enabled ? "enable" : "disable",
235           tp_proxy_get_bus_name (debug));
236       g_error_free (error);
237     }
238 }
239
240 static void
241 debug_window_set_enabled (TpDebugClient *debug,
242     gboolean enabled)
243 {
244   g_return_if_fail (debug != NULL);
245
246   tp_debug_client_set_enabled_async (debug, enabled,
247       set_enabled_cb, GUINT_TO_POINTER (enabled));
248 }
249
250 static void
251 debug_window_set_toolbar_sensitivity (EmpathyDebugWindow *self,
252     gboolean sensitive)
253 {
254   GtkWidget *vbox = gtk_bin_get_child (GTK_BIN (self));
255
256   gtk_widget_set_sensitive (GTK_WIDGET (self->priv->save_button), sensitive);
257   gtk_widget_set_sensitive (GTK_WIDGET (self->priv->send_to_pastebin),
258       sensitive);
259   gtk_widget_set_sensitive (GTK_WIDGET (self->priv->copy_button), sensitive);
260   gtk_widget_set_sensitive (GTK_WIDGET (self->priv->clear_button), sensitive);
261   gtk_widget_set_sensitive (GTK_WIDGET (self->priv->pause_button), sensitive);
262   gtk_widget_set_sensitive (GTK_WIDGET (self->priv->level_label), sensitive);
263   gtk_widget_set_sensitive (GTK_WIDGET (self->priv->level_filter), sensitive);
264   gtk_widget_set_sensitive (GTK_WIDGET (self->priv->view), sensitive);
265
266   if (sensitive && !self->priv->view_visible)
267     {
268       /* Add view and remove label */
269       gtk_container_remove (GTK_CONTAINER (vbox),
270           self->priv->not_supported_label);
271       gtk_box_pack_start (GTK_BOX (vbox),
272           self->priv->scrolled_win, TRUE, TRUE, 0);
273       self->priv->view_visible = TRUE;
274     }
275   else if (!sensitive && self->priv->view_visible)
276     {
277       /* Add label and remove view */
278       gtk_container_remove (GTK_CONTAINER (vbox), self->priv->scrolled_win);
279       gtk_box_pack_start (GTK_BOX (vbox), self->priv->not_supported_label,
280           TRUE, TRUE, 0);
281       self->priv->view_visible = FALSE;
282     }
283 }
284
285 static gboolean
286 debug_window_get_iter_for_active_buffer (GtkListStore *active_buffer,
287     GtkTreeIter *iter,
288     EmpathyDebugWindow *self)
289 {
290   gboolean valid_iter;
291   GtkTreeModel *model = GTK_TREE_MODEL (self->priv->service_store);
292
293   gtk_tree_model_get_iter_first (model, iter);
294   for (valid_iter = gtk_tree_model_iter_next (model, iter);
295        valid_iter;
296        valid_iter = gtk_tree_model_iter_next (model, iter))
297     {
298       GtkListStore *stored_active_buffer;
299
300       gtk_tree_model_get (model, iter,
301           COL_ACTIVE_BUFFER, &stored_active_buffer,
302           -1);
303       if (active_buffer == stored_active_buffer)
304         {
305           g_object_unref (stored_active_buffer);
306           return valid_iter;
307         }
308       g_object_unref (stored_active_buffer);
309     }
310
311   return valid_iter;
312 }
313
314 static void refresh_all_buffer (EmpathyDebugWindow *self);
315
316 static void
317 proxy_invalidated_cb (TpProxy *proxy,
318     guint domain,
319     gint code,
320     gchar *msg,
321     gpointer user_data)
322 {
323   EmpathyDebugWindow *self = (EmpathyDebugWindow *) user_data;
324   GtkTreeModel *service_store = GTK_TREE_MODEL (self->priv->service_store);
325   TpProxy *stored_proxy;
326   GtkTreeIter iter;
327   gboolean valid_iter;
328
329   /* Proxy has been invalidated so we find and set it to NULL
330    * in service store */
331   gtk_tree_model_get_iter_first (service_store, &iter);
332   for (valid_iter = gtk_tree_model_iter_next (service_store, &iter);
333        valid_iter;
334        valid_iter = gtk_tree_model_iter_next (service_store, &iter))
335     {
336       gtk_tree_model_get (service_store, &iter,
337           COL_PROXY, &stored_proxy,
338           -1);
339
340       if (proxy == stored_proxy)
341         gtk_list_store_set (self->priv->service_store, &iter,
342             COL_PROXY, NULL,
343             -1);
344     }
345
346   /* Also, we refresh "All" selection's active buffer since it should not
347    * show messages obtained from the proxy getting destroyed above */
348   refresh_all_buffer (self);
349 }
350
351 static void
352 debug_window_get_messages_cb (GObject *object,
353     GAsyncResult *result,
354     gpointer user_data)
355 {
356   TpDebugClient *debug = TP_DEBUG_CLIENT (object);
357   EmpathyDebugWindow *self = user_data;
358   gchar *active_service_name;
359   guint i;
360   GtkListStore *active_buffer;
361   gboolean valid_iter;
362   GtkTreeIter iter;
363   gchar *proxy_service_name;
364   GPtrArray *messages;
365   GError *error = NULL;
366
367   active_buffer = g_object_get_data (object, "active-buffer");
368   valid_iter = debug_window_get_iter_for_active_buffer (active_buffer, &iter,
369       self);
370   gtk_tree_model_get (GTK_TREE_MODEL (self->priv->service_store), &iter,
371       COL_NAME, &proxy_service_name,
372       -1);
373
374   active_service_name = get_active_service_name (self);
375
376   messages = tp_debug_client_get_messages_finish (debug, result, &error);
377   if (messages == NULL)
378     {
379       DEBUG ("Failed to get debug messages: %s", error->message);
380       g_error_free (error);
381
382       /* We want to set the window sensitivity to false only when proxy for the
383        * selected service is unable to fetch debug messages */
384       if (!tp_strdiff (active_service_name, proxy_service_name))
385         debug_window_set_toolbar_sensitivity (self, FALSE);
386
387       /* We created the proxy for GetMessages call. Now destroy it. */
388       tp_clear_object (&debug);
389       return;
390     }
391
392   DEBUG ("Retrieved debug messages for %s", active_service_name);
393   g_free (active_service_name);
394   debug_window_set_toolbar_sensitivity (self, TRUE);
395
396   for (i = 0; i < messages->len; i++)
397     {
398       TpDebugMessage *msg = g_ptr_array_index (messages, i);
399
400       debug_window_add_message (self, debug, msg);
401     }
402
403   /* Now we save this precious proxy in the service_store along its service */
404   if (valid_iter)
405     {
406       DEBUG ("Proxy for service: %s was successful in fetching debug"
407           " messages. Saving it.", proxy_service_name);
408
409       gtk_list_store_set (self->priv->service_store, &iter,
410           COL_PROXY, debug,
411           -1);
412     }
413   g_ptr_array_unref (messages);
414
415   g_free (proxy_service_name);
416
417   /* Connect to "invalidated" signal */
418   g_signal_connect (debug, "invalidated",
419       G_CALLBACK (proxy_invalidated_cb), self);
420
421  /* Connect to NewDebugMessage */
422   tp_g_signal_connect_object (debug, "new-debug-message",
423       G_CALLBACK (debug_window_new_debug_message_cb), self, 0);
424
425   /* Now that active-buffer is up to date, we can see which messages are
426    * to be visible */
427   gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (
428         self->priv->store_filter));
429
430   /* Set the proxy to signal for new debug messages */
431   debug_window_set_enabled (debug, TRUE);
432 }
433
434 static void
435 create_proxy_to_get_messages (EmpathyDebugWindow *self,
436     GtkTreeIter *iter,
437     TpDBusDaemon *dbus)
438 {
439   gchar *bus_name, *name = NULL;
440   TpDebugClient *new_proxy, *stored_proxy = NULL;
441   GtkTreeModel *pause_buffer, *active_buffer;
442   gboolean gone;
443   GError *error = NULL;
444
445   gtk_tree_model_get (GTK_TREE_MODEL (self->priv->service_store), iter,
446       COL_NAME, &name,
447       COL_GONE, &gone,
448       COL_ACTIVE_BUFFER, &active_buffer,
449       COL_PAUSE_BUFFER, &pause_buffer,
450       COL_PROXY, &stored_proxy,
451       -1);
452
453   /* If the stored_proxy is not NULL then messages have been obtained and
454    * new-debug-message-signal has been set on it. Also, the proxy is valid.
455    * If the service is gone, we still display the messages-cached till now. */
456   if (gone ||
457       (!gone && stored_proxy != NULL))
458     {
459       /* Nothing needs to be done. The associated active-buffer has already
460        * been set as view's model */
461       goto finally;
462     }
463
464   DEBUG ("Preparing proxy to obtain messages for service %s", name);
465
466   gtk_tree_model_get (GTK_TREE_MODEL (self->priv->service_store), iter,
467       COL_UNIQUE_NAME, &bus_name, -1);
468
469   new_proxy = tp_debug_client_new (dbus, bus_name, &error);
470
471   if (new_proxy == NULL)
472     {
473       DEBUG ("Failed to create TpDebugClient on bus %s: %s", bus_name,
474           error->message);
475       g_free (bus_name);
476       goto finally;
477     }
478
479   g_free (bus_name);
480
481   g_object_set_data (G_OBJECT (new_proxy), "active-buffer", active_buffer);
482   g_object_set_data (G_OBJECT (new_proxy), "pause-buffer", pause_buffer);
483
484   /* Now we call GetMessages with fresh proxy.
485    * The old proxy is NULL due to one of the following -
486    * * Wasn't saved as last GetMessages call failed
487    * * The service has newly arrived and no proxy has been prepared yet for it
488    * * A service with the same name has reappeared but the owner maybe new */
489
490   tp_debug_client_get_messages_async (TP_DEBUG_CLIENT (new_proxy),
491       debug_window_get_messages_cb, self);
492
493 finally:
494   g_free (name);
495   tp_clear_object (&stored_proxy);
496   g_object_unref (active_buffer);
497   g_object_unref (pause_buffer);
498 }
499
500 static GtkListStore *
501 new_list_store_for_service (void)
502 {
503   return gtk_list_store_new (NUM_DEBUG_COLS,
504              TP_TYPE_DEBUG_MESSAGE); /* COL_DEBUG_MESSAGE */
505 }
506
507 static gboolean
508 debug_window_visible_func (GtkTreeModel *model,
509     GtkTreeIter *iter,
510     gpointer user_data)
511 {
512   EmpathyDebugWindow *self = user_data;
513   GLogLevelFlags filter_value;
514   GtkTreeModel *filter_model;
515   GtkTreeIter filter_iter;
516   TpDebugMessage *msg;
517   gboolean result;
518
519   filter_model = gtk_combo_box_get_model (
520       GTK_COMBO_BOX (self->priv->level_filter));
521   gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self->priv->level_filter),
522       &filter_iter);
523
524   gtk_tree_model_get (model, iter, COL_DEBUG_MESSAGE, &msg, -1);
525   gtk_tree_model_get (filter_model, &filter_iter,
526       COL_LEVEL_VALUE, &filter_value, -1);
527
528   result = (tp_debug_message_get_level (msg) <= filter_value);
529   g_object_unref (msg);
530
531   return result;
532 }
533
534 static gboolean
535 tree_view_search_equal_func_cb (GtkTreeModel *model,
536     gint column,
537     const gchar *key,
538     GtkTreeIter *iter,
539     gpointer search_data)
540 {
541   gchar *str;
542   gint key_len;
543   gint len;
544   gint i;
545   gboolean ret = TRUE; /* The return value is counter-intuitive */
546
547   gtk_tree_model_get (model, iter, column, &str, -1);
548
549   key_len = strlen (key);
550   len = strlen (str) - key_len;
551
552   for (i = 0; i <= len; ++i)
553     {
554       if (!g_ascii_strncasecmp (key, str + i, key_len))
555         {
556           ret = FALSE;
557           break;
558         }
559     }
560
561   g_free (str);
562   return ret;
563 }
564
565 static void
566 update_store_filter (EmpathyDebugWindow *self,
567     GtkListStore *active_buffer)
568 {
569   debug_window_set_toolbar_sensitivity (self, FALSE);
570
571   tp_clear_object (&self->priv->store_filter);
572   self->priv->store_filter = gtk_tree_model_filter_new (
573       GTK_TREE_MODEL (active_buffer), NULL);
574
575   gtk_tree_model_filter_set_visible_func (
576       GTK_TREE_MODEL_FILTER (self->priv->store_filter),
577       debug_window_visible_func, self, NULL);
578   gtk_tree_view_set_model (GTK_TREE_VIEW (self->priv->view),
579       self->priv->store_filter);
580
581   /* Since view's model has changed, reset the search column and
582    * search_equal_func */
583   gtk_tree_view_set_search_column (GTK_TREE_VIEW (self->priv->view),
584       COL_DEBUG_MESSAGE);
585   gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (self->priv->view),
586       tree_view_search_equal_func_cb, NULL, NULL);
587
588   debug_window_set_toolbar_sensitivity (self, TRUE);
589 }
590
591 static void
592 refresh_all_buffer (EmpathyDebugWindow *self)
593 {
594   gboolean valid_iter;
595   GtkTreeIter iter;
596   GtkTreeModel *service_store = GTK_TREE_MODEL (self->priv->service_store);
597
598   /* Clear All's active-buffer */
599   gtk_list_store_clear (self->priv->all_active_buffer);
600
601   /* Skipping the first service store iter which is reserved for "All" */
602   gtk_tree_model_get_iter_first (service_store, &iter);
603   for (valid_iter = gtk_tree_model_iter_next (service_store, &iter);
604        valid_iter;
605        valid_iter = gtk_tree_model_iter_next (service_store, &iter))
606     {
607       TpProxy *proxy = NULL;
608       GtkListStore *service_active_buffer;
609       gboolean gone;
610
611       gtk_tree_model_get (service_store, &iter,
612           COL_GONE, &gone,
613           COL_PROXY, &proxy,
614           COL_ACTIVE_BUFFER, &service_active_buffer,
615           -1);
616
617       if (gone)
618         {
619           gtk_tree_model_foreach (GTK_TREE_MODEL (service_active_buffer),
620               copy_buffered_messages, self->priv->all_active_buffer);
621         }
622       else
623         {
624           if (proxy != NULL)
625             {
626               if (service_active_buffer == NULL)
627                 break;
628
629               /* Copy the debug messages to all_active_buffer */
630               gtk_tree_model_foreach (GTK_TREE_MODEL (service_active_buffer),
631                   copy_buffered_messages, self->priv->all_active_buffer);
632             }
633           else
634             {
635               GError *error = NULL;
636               TpDBusDaemon *dbus = tp_dbus_daemon_dup (&error);
637
638               if (error != NULL)
639                 {
640                   DEBUG ("Failed at duping the dbus daemon: %s", error->message);
641                   g_error_free (error);
642                 }
643
644               create_proxy_to_get_messages (self, &iter, dbus);
645
646               g_object_unref (dbus);
647             }
648         }
649
650       g_object_unref (service_active_buffer);
651       tp_clear_object (&proxy);
652     }
653 }
654
655 static void
656 debug_window_service_chooser_changed_cb (GtkComboBox *chooser,
657     EmpathyDebugWindow *self)
658 {
659   TpDBusDaemon *dbus;
660   GError *error = NULL;
661   GtkListStore *stored_active_buffer = NULL;
662   gchar *name = NULL;
663   GtkTreeIter iter;
664   gboolean gone;
665
666   if (!gtk_combo_box_get_active_iter (chooser, &iter))
667     {
668       DEBUG ("No CM is selected");
669       if (gtk_tree_model_iter_n_children (
670           GTK_TREE_MODEL (self->priv->service_store), NULL) > 0)
671         {
672           gtk_combo_box_set_active (chooser, 0);
673         }
674       return;
675     }
676
677   debug_window_set_toolbar_sensitivity (self, TRUE);
678
679   gtk_tree_model_get (GTK_TREE_MODEL (self->priv->service_store), &iter,
680       COL_NAME, &name,
681       COL_GONE, &gone,
682       COL_ACTIVE_BUFFER, &stored_active_buffer,
683       -1);
684
685   DEBUG ("Service chosen: %s", name);
686
687   if (tp_strdiff (name, "All") && stored_active_buffer == NULL)
688     {
689       DEBUG ("No list store assigned to service %s", name);
690       goto finally;
691     }
692
693   if (!tp_strdiff (name, "All"))
694     {
695       update_store_filter (self, self->priv->all_active_buffer);
696       goto finally;
697     }
698
699   update_store_filter (self, stored_active_buffer);
700
701   dbus = tp_dbus_daemon_dup (&error);
702
703   if (error != NULL)
704     {
705       DEBUG ("Failed at duping the dbus daemon: %s", error->message);
706     }
707
708   create_proxy_to_get_messages (self, &iter, dbus);
709
710   g_object_unref (dbus);
711
712 finally:
713   g_free (name);
714   tp_clear_object (&stored_active_buffer);
715 }
716
717 typedef struct
718 {
719   const gchar *name;
720   gboolean found;
721   gboolean use_name;
722   GtkTreeIter **found_iter;
723 } CmInModelForeachData;
724
725 static gboolean
726 debug_window_service_foreach (GtkTreeModel *model,
727     GtkTreePath *path,
728     GtkTreeIter *iter,
729     gpointer user_data)
730 {
731   CmInModelForeachData *data = (CmInModelForeachData *) user_data;
732   gchar *store_name;
733
734   gtk_tree_model_get (model, iter,
735       (data->use_name ? COL_NAME : COL_UNIQUE_NAME),
736       &store_name,
737       -1);
738
739   if (!tp_strdiff (store_name, data->name))
740     {
741       data->found = TRUE;
742
743       if (data->found_iter != NULL)
744         *(data->found_iter) = gtk_tree_iter_copy (iter);
745     }
746
747   g_free (store_name);
748
749   return data->found;
750 }
751
752 static gboolean
753 debug_window_service_is_in_model (EmpathyDebugWindow *self,
754     const gchar *name,
755     GtkTreeIter **iter,
756     gboolean use_name)
757 {
758   CmInModelForeachData *data;
759   gboolean found;
760
761   data = g_slice_new0 (CmInModelForeachData);
762   data->name = name;
763   data->found = FALSE;
764   data->found_iter = iter;
765   data->use_name = use_name;
766
767   gtk_tree_model_foreach (GTK_TREE_MODEL (self->priv->service_store),
768       debug_window_service_foreach, data);
769
770   found = data->found;
771
772   g_slice_free (CmInModelForeachData, data);
773
774   return found;
775 }
776
777 static gchar *
778 get_cm_display_name (EmpathyDebugWindow *self,
779     const char *cm_name)
780 {
781   GHashTable *protocols = g_hash_table_new (g_str_hash, g_str_equal);
782   GList *accounts, *ptr;
783   char *retval;
784
785   accounts = tp_account_manager_dup_valid_accounts (self->priv->am);
786
787   for (ptr = accounts; ptr != NULL; ptr = ptr->next)
788     {
789       TpAccount *account = TP_ACCOUNT (ptr->data);
790
791       if (!tp_strdiff (tp_account_get_cm_name (account), cm_name))
792         {
793           g_hash_table_insert (protocols,
794               (char *) tp_account_get_protocol_name (account),
795               GUINT_TO_POINTER (TRUE));
796         }
797     }
798
799   g_list_free_full (accounts, g_object_unref);
800
801   if (g_hash_table_size (protocols) > 0)
802     {
803       GHashTableIter iter;
804       char **protocolsv;
805       char *key, *str;
806       guint i;
807
808       protocolsv = g_new0 (char *, g_hash_table_size (protocols) + 1);
809
810       g_hash_table_iter_init (&iter, protocols);
811       for (i = 0; g_hash_table_iter_next (&iter, (gpointer) &key, NULL); i++)
812         {
813           protocolsv[i] = key;
814         }
815
816       str = g_strjoinv (", ", protocolsv);
817       retval = g_strdup_printf ("%s (%s)", cm_name, str);
818
819       g_free (protocolsv);
820       g_free (str);
821     }
822   else
823     {
824       retval = g_strdup (cm_name);
825     }
826
827   g_hash_table_unref (protocols);
828
829   return retval;
830 }
831
832 typedef struct
833 {
834   EmpathyDebugWindow *self;
835   gchar *name;
836   ServiceType type;
837 } FillServiceChooserData;
838
839 static FillServiceChooserData *
840 fill_service_chooser_data_new (EmpathyDebugWindow *window,
841     const gchar *name,
842     ServiceType type)
843 {
844   FillServiceChooserData * data = g_slice_new (FillServiceChooserData);
845
846   data->self = window;
847   data->name = g_strdup (name);
848   data->type = SERVICE_TYPE_CM;
849   return data;
850 }
851
852 static void
853 fill_service_chooser_data_free (FillServiceChooserData *data)
854 {
855   g_free (data->name);
856   g_slice_free (FillServiceChooserData, data);
857 }
858
859 static void
860 debug_window_get_name_owner_cb (TpDBusDaemon *proxy,
861     const gchar *out,
862     const GError *error,
863     gpointer user_data,
864     GObject *weak_object)
865 {
866   FillServiceChooserData *data = (FillServiceChooserData *) user_data;
867   EmpathyDebugWindow *self = EMPATHY_DEBUG_WINDOW (data->self);
868   GtkTreeIter iter;
869
870   self->priv->name_owner_cb_count++;
871
872   if (error != NULL)
873     {
874       DEBUG ("GetNameOwner failed: %s", error->message);
875       goto OUT;
876     }
877
878   if (!debug_window_service_is_in_model (data->self, out, NULL, FALSE))
879     {
880       char *name;
881       GtkListStore *active_buffer, *pause_buffer;
882
883       DEBUG ("Adding %s to list: %s at unique name: %s",
884           data->type == SERVICE_TYPE_CM? "CM": "Client",
885           data->name, out);
886
887       if (data->type == SERVICE_TYPE_CM)
888         name = get_cm_display_name (self, data->name);
889       else
890         name = g_strdup (data->name);
891
892       active_buffer = new_list_store_for_service ();
893       pause_buffer = new_list_store_for_service ();
894
895       gtk_list_store_insert_with_values (self->priv->service_store, &iter, -1,
896           COL_NAME, name,
897           COL_UNIQUE_NAME, out,
898           COL_GONE, FALSE,
899           COL_ACTIVE_BUFFER, active_buffer,
900           COL_PAUSE_BUFFER, pause_buffer,
901           COL_PROXY, NULL,
902           -1);
903
904       g_object_unref (active_buffer);
905       g_object_unref (pause_buffer);
906
907       if (self->priv->select_name != NULL &&
908           !tp_strdiff (name, self->priv->select_name))
909         {
910           gtk_combo_box_set_active_iter (GTK_COMBO_BOX (self->priv->chooser),
911               &iter);
912           tp_clear_pointer (&self->priv->select_name, g_free);
913         }
914
915       g_free (name);
916     }
917
918     if (self->priv->services_detected == self->priv->name_owner_cb_count)
919       {
920         /* Time to add "All" selection to service_store */
921         gtk_list_store_insert_with_values (self->priv->service_store, &iter, 0,
922             COL_NAME, "All",
923             COL_ACTIVE_BUFFER, NULL,
924             -1);
925
926         self->priv->all_active_buffer = new_list_store_for_service ();
927
928         /* Populate active buffers for all services */
929         refresh_all_buffer (self);
930
931         gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->chooser), 0);
932       }
933
934 OUT:
935   fill_service_chooser_data_free (data);
936 }
937
938 static void
939 debug_window_list_connection_names_cb (const gchar * const *names,
940     gsize n,
941     const gchar * const *cms,
942     const gchar * const *protocols,
943     const GError *error,
944     gpointer user_data,
945     GObject *weak_object)
946 {
947   EmpathyDebugWindow *self = user_data;
948   guint i;
949   TpDBusDaemon *dbus;
950   GError *error2 = NULL;
951
952   if (error != NULL)
953     {
954       DEBUG ("list_connection_names failed: %s", error->message);
955       return;
956     }
957
958   dbus = tp_dbus_daemon_dup (&error2);
959
960   if (error2 != NULL)
961     {
962       DEBUG ("Failed to dup TpDBusDaemon.");
963       g_error_free (error2);
964       return;
965     }
966
967   for (i = 0; cms[i] != NULL; i++)
968     {
969       FillServiceChooserData *data = fill_service_chooser_data_new (
970           self, cms[i], SERVICE_TYPE_CM);
971
972       tp_cli_dbus_daemon_call_get_name_owner (dbus, -1,
973           names[i], debug_window_get_name_owner_cb,
974           data, NULL, NULL);
975
976       self->priv->services_detected ++;
977     }
978
979   g_object_unref (dbus);
980 }
981
982 static void
983 debug_window_name_owner_changed_cb (TpDBusDaemon *proxy,
984     const gchar *arg0,
985     const gchar *arg1,
986     const gchar *arg2,
987     gpointer user_data,
988     GObject *weak_object)
989 {
990   EmpathyDebugWindow *self = EMPATHY_DEBUG_WINDOW (user_data);
991   ServiceType type;
992   const gchar *name;
993
994   if (g_str_has_prefix (arg0, TP_CM_BUS_NAME_BASE))
995     {
996       type = SERVICE_TYPE_CM;
997       name = arg0 + strlen (TP_CM_BUS_NAME_BASE);
998     }
999   else if (g_str_has_prefix (arg0, TP_CLIENT_BUS_NAME_BASE))
1000     {
1001       type = SERVICE_TYPE_CLIENT;
1002       name = arg0 + strlen (TP_CLIENT_BUS_NAME_BASE);
1003     }
1004   else
1005     {
1006       return;
1007     }
1008
1009   if (TPAW_STR_EMPTY (arg1) && !TPAW_STR_EMPTY (arg2))
1010     {
1011       GtkTreeIter *found_at_iter = NULL;
1012       gchar *display_name;
1013
1014       if (type == SERVICE_TYPE_CM)
1015         display_name = get_cm_display_name (self, name);
1016       else
1017         display_name = g_strdup (name);
1018
1019       /* A service joined */
1020       if (!debug_window_service_is_in_model (user_data, display_name,
1021            &found_at_iter, TRUE))
1022         {
1023           GtkTreeIter iter;
1024           GtkListStore *active_buffer, *pause_buffer;
1025
1026           DEBUG ("Adding new service '%s' at %s.", name, arg2);
1027
1028           active_buffer = new_list_store_for_service ();
1029           pause_buffer = new_list_store_for_service ();
1030
1031           gtk_list_store_insert_with_values (self->priv->service_store,
1032               &iter, -1,
1033               COL_NAME, display_name,
1034               COL_UNIQUE_NAME, arg2,
1035               COL_GONE, FALSE,
1036               COL_ACTIVE_BUFFER, active_buffer,
1037               COL_PAUSE_BUFFER, pause_buffer,
1038               COL_PROXY, NULL,
1039               -1);
1040
1041           g_object_unref (active_buffer);
1042           g_object_unref (pause_buffer);
1043         }
1044       else
1045         {
1046           /* a service with the same name is already in the service_store,
1047            * update it and set it as re-enabled.
1048            */
1049           GtkListStore *active_buffer, *pause_buffer;
1050           TpProxy *stored_proxy;
1051
1052           DEBUG ("Refreshing CM '%s' at '%s'.", name, arg2);
1053
1054           active_buffer= new_list_store_for_service ();
1055           pause_buffer = new_list_store_for_service ();
1056
1057           gtk_tree_model_get (GTK_TREE_MODEL (self->priv->service_store),
1058               found_at_iter, COL_PROXY, &stored_proxy, -1);
1059
1060           tp_clear_object (&stored_proxy);
1061
1062           gtk_list_store_set (self->priv->service_store, found_at_iter,
1063               COL_NAME, display_name,
1064               COL_UNIQUE_NAME, arg2,
1065               COL_GONE, FALSE,
1066               COL_ACTIVE_BUFFER, active_buffer,
1067               COL_PAUSE_BUFFER, pause_buffer,
1068               COL_PROXY, NULL,
1069               -1);
1070
1071           g_object_unref (active_buffer);
1072           g_object_unref (pause_buffer);
1073
1074           gtk_tree_iter_free (found_at_iter);
1075
1076           debug_window_service_chooser_changed_cb
1077             (GTK_COMBO_BOX (self->priv->chooser), user_data);
1078         }
1079
1080       /* If a new service arrives when "All" is selected, the view will
1081        * not show its messages which we do not want. So we refresh All's
1082        * active buffer.
1083        * Similarly for when a service with an already seen service name
1084        * appears. */
1085       refresh_all_buffer (self);
1086
1087       g_free (display_name);
1088     }
1089   else if (!TPAW_STR_EMPTY (arg1) && TPAW_STR_EMPTY (arg2))
1090     {
1091       /* A service died */
1092       GtkTreeIter *iter = NULL;
1093
1094       DEBUG ("Setting service disabled from %s.", arg1);
1095
1096       /* set the service as disabled in the model */
1097       if (debug_window_service_is_in_model (user_data, arg1, &iter, FALSE))
1098         {
1099           gtk_list_store_set (self->priv->service_store,
1100               iter, COL_GONE, TRUE, -1);
1101           gtk_tree_iter_free (iter);
1102         }
1103
1104       /* Refresh all's active buffer */
1105       refresh_all_buffer (self);
1106     }
1107 }
1108
1109 static void
1110 add_client (EmpathyDebugWindow *self,
1111     const gchar *name)
1112 {
1113   const gchar *suffix;
1114   FillServiceChooserData *data;
1115
1116   suffix = name + strlen (TP_CLIENT_BUS_NAME_BASE);
1117
1118   data = fill_service_chooser_data_new (self, suffix, SERVICE_TYPE_CLIENT);
1119
1120   tp_cli_dbus_daemon_call_get_name_owner (self->priv->dbus, -1,
1121       name, debug_window_get_name_owner_cb, data, NULL, NULL);
1122
1123   self->priv->services_detected ++;
1124 }
1125
1126 static void
1127 list_names_cb (TpDBusDaemon *bus_daemon,
1128     const gchar * const *names,
1129     const GError *error,
1130     gpointer user_data,
1131     GObject *weak_object)
1132 {
1133   EmpathyDebugWindow *self = EMPATHY_DEBUG_WINDOW (weak_object);
1134   guint i;
1135
1136   if (error != NULL)
1137     {
1138       DEBUG ("Failed to list names: %s", error->message);
1139       return;
1140     }
1141
1142   for (i = 0; names[i] != NULL; i++)
1143     {
1144       if (g_str_has_prefix (names[i], TP_CLIENT_BUS_NAME_BASE))
1145         {
1146           add_client (self, names[i]);
1147         }
1148     }
1149 }
1150
1151 static void
1152 debug_window_fill_service_chooser (EmpathyDebugWindow *self)
1153 {
1154   GError *error = NULL;
1155   GtkTreeIter iter;
1156   GtkListStore *active_buffer, *pause_buffer;
1157
1158   self->priv->dbus = tp_dbus_daemon_dup (&error);
1159
1160   if (error != NULL)
1161     {
1162       DEBUG ("Failed to dup dbus daemon: %s", error->message);
1163       g_error_free (error);
1164       return;
1165     }
1166
1167   /* Keep a count of the services detected and added */
1168   self->priv->services_detected = 0;
1169   self->priv->name_owner_cb_count = 0;
1170
1171   /* Add CMs to list */
1172   tp_list_connection_names (self->priv->dbus,
1173       debug_window_list_connection_names_cb, self, NULL, NULL);
1174
1175   /* add Mission Control */
1176   active_buffer= new_list_store_for_service ();
1177   pause_buffer = new_list_store_for_service ();
1178
1179   gtk_list_store_insert_with_values (self->priv->service_store, &iter, -1,
1180       COL_NAME, "mission-control",
1181       COL_UNIQUE_NAME, "org.freedesktop.Telepathy.MissionControl5",
1182       COL_GONE, FALSE,
1183       COL_ACTIVE_BUFFER, active_buffer,
1184       COL_PAUSE_BUFFER, pause_buffer,
1185       COL_PROXY, NULL,
1186       -1);
1187   g_object_unref (active_buffer);
1188   g_object_unref (pause_buffer);
1189
1190   /* add clients */
1191   tp_dbus_daemon_list_names (self->priv->dbus, 2000,
1192       list_names_cb, NULL, NULL, G_OBJECT (self));
1193
1194   self->priv->name_owner_changed_signal =
1195       tp_cli_dbus_daemon_connect_to_name_owner_changed (self->priv->dbus,
1196       debug_window_name_owner_changed_cb, self, NULL, NULL, NULL);
1197 }
1198
1199 static void
1200 debug_window_pause_toggled_cb (GtkToggleToolButton *pause_,
1201     EmpathyDebugWindow *self)
1202 {
1203   GtkTreeIter iter;
1204   gboolean valid_iter;
1205   GtkTreeModel *model = GTK_TREE_MODEL (self->priv->service_store);
1206
1207   self->priv->paused = gtk_toggle_tool_button_get_active (pause_);
1208
1209   if (!self->priv->paused)
1210     {
1211       /* Pause has been released - flush all pause buffers */
1212       GtkTreeModel *service_store = GTK_TREE_MODEL (self->priv->service_store);
1213
1214       /* Skipping the first iter which is reserved for "All" */
1215       gtk_tree_model_get_iter_first (model, &iter);
1216       for (valid_iter = gtk_tree_model_iter_next (model, &iter);
1217            valid_iter;
1218            valid_iter = gtk_tree_model_iter_next (model, &iter))
1219         {
1220           GtkListStore *pause_buffer, *active_buffer;
1221
1222           gtk_tree_model_get (service_store, &iter,
1223               COL_PAUSE_BUFFER, &pause_buffer,
1224               COL_ACTIVE_BUFFER, &active_buffer,
1225               -1);
1226
1227           gtk_tree_model_foreach (GTK_TREE_MODEL (pause_buffer),
1228               copy_buffered_messages, active_buffer);
1229           gtk_tree_model_foreach (GTK_TREE_MODEL (pause_buffer),
1230               copy_buffered_messages, self->priv->all_active_buffer);
1231
1232           gtk_list_store_clear (pause_buffer);
1233
1234           g_object_unref (active_buffer);
1235           g_object_unref (pause_buffer);
1236         }
1237     }
1238 }
1239
1240 static void
1241 debug_window_filter_changed_cb (GtkComboBox *filter,
1242     EmpathyDebugWindow *self)
1243 {
1244   gtk_tree_model_filter_refilter (
1245       GTK_TREE_MODEL_FILTER (self->priv->store_filter));
1246 }
1247
1248 static void
1249 debug_window_clear_clicked_cb (GtkToolButton *clear_button,
1250     EmpathyDebugWindow *self)
1251 {
1252   GtkTreeIter iter;
1253   GtkListStore *active_buffer;
1254
1255   /* "All" is the first choice in the service chooser and it's buffer is
1256    * not saved in the service-store but is accessed using a self->private
1257    * reference */
1258   if (gtk_combo_box_get_active (GTK_COMBO_BOX (self->priv->chooser)) == 0)
1259     {
1260       gtk_list_store_clear (self->priv->all_active_buffer);
1261       return;
1262     }
1263
1264   gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self->priv->chooser), &iter);
1265   gtk_tree_model_get (GTK_TREE_MODEL (self->priv->service_store), &iter,
1266       COL_ACTIVE_BUFFER, &active_buffer, -1);
1267
1268   gtk_list_store_clear (active_buffer);
1269
1270   g_object_unref (active_buffer);
1271 }
1272
1273 static void
1274 debug_window_menu_copy_activate_cb (GtkMenuItem *menu_item,
1275     EmpathyDebugWindow *self)
1276 {
1277   GtkTreePath *path;
1278   GtkTreeViewColumn *focus_column;
1279   GtkTreeIter iter;
1280   TpDebugMessage *msg;
1281   const gchar *message;
1282   GtkClipboard *clipboard;
1283
1284   gtk_tree_view_get_cursor (GTK_TREE_VIEW (self->priv->view),
1285       &path, &focus_column);
1286
1287   if (path == NULL)
1288     {
1289       DEBUG ("No row is in focus");
1290       return;
1291     }
1292
1293   gtk_tree_model_get_iter (self->priv->store_filter, &iter, path);
1294
1295   gtk_tree_model_get (self->priv->store_filter, &iter,
1296       COL_DEBUG_MESSAGE, &msg,
1297       -1);
1298
1299   message = tp_debug_message_get_message (msg);
1300
1301   if (TPAW_STR_EMPTY (message))
1302     {
1303       DEBUG ("Log message is empty");
1304       return;
1305     }
1306
1307   clipboard = gtk_clipboard_get_for_display (
1308       gtk_widget_get_display (GTK_WIDGET (menu_item)),
1309       GDK_SELECTION_CLIPBOARD);
1310
1311   gtk_clipboard_set_text (clipboard, message, -1);
1312
1313   g_object_unref (msg);
1314 }
1315
1316 typedef struct
1317 {
1318   EmpathyDebugWindow *self;
1319   guint button;
1320   guint32 time;
1321 } MenuPopupData;
1322
1323 static gboolean
1324 debug_window_show_menu (gpointer user_data)
1325 {
1326   MenuPopupData *data = (MenuPopupData *) user_data;
1327   GtkWidget *menu, *item;
1328   GtkMenuShell *shell;
1329
1330   menu = empathy_context_menu_new (GTK_WIDGET (data->self));
1331   shell = GTK_MENU_SHELL (menu);
1332
1333   item = gtk_image_menu_item_new_from_stock (GTK_STOCK_COPY, NULL);
1334
1335   g_signal_connect (item, "activate",
1336       G_CALLBACK (debug_window_menu_copy_activate_cb), data->self);
1337
1338   gtk_menu_shell_append (shell, item);
1339   gtk_widget_show (item);
1340
1341   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1342      data->button, data->time);
1343
1344   g_slice_free (MenuPopupData, user_data);
1345
1346   return FALSE;
1347 }
1348
1349 static gboolean
1350 debug_window_button_press_event_cb (GtkTreeView *view,
1351     GdkEventButton *event,
1352     gpointer user_data)
1353 {
1354   /* A mouse button was pressed on the tree view. */
1355
1356   if (event->button == 3)
1357     {
1358       /* The tree view was right-clicked. (3 == third mouse button) */
1359       MenuPopupData *data;
1360       data = g_slice_new0 (MenuPopupData);
1361       data->self = user_data;
1362       data->button = event->button;
1363       data->time = event->time;
1364       g_idle_add (debug_window_show_menu, data);
1365     }
1366
1367   return FALSE;
1368 }
1369
1370 static gchar *
1371 debug_window_format_timestamp (TpDebugMessage *msg)
1372 {
1373   GDateTime *t;
1374   gchar *time_str, *text;
1375   gint ms;
1376
1377   t = tp_debug_message_get_time (msg);
1378
1379   time_str = g_date_time_format (t, "%x %T");
1380
1381   ms = g_date_time_get_microsecond (t);
1382   text = g_strdup_printf ("%s.%d", time_str, ms);
1383
1384   g_free (time_str);
1385   return text;
1386 }
1387
1388 static void
1389 debug_window_time_formatter (GtkTreeViewColumn *tree_column,
1390     GtkCellRenderer *cell,
1391     GtkTreeModel *tree_model,
1392     GtkTreeIter *iter,
1393     gpointer data)
1394 {
1395   TpDebugMessage *msg;
1396   gchar *time_str;
1397
1398   gtk_tree_model_get (tree_model, iter, COL_DEBUG_MESSAGE, &msg, -1);
1399
1400   time_str = debug_window_format_timestamp (msg);
1401
1402   g_object_set (G_OBJECT (cell), "text", time_str, NULL);
1403
1404   g_object_unref (msg);
1405 }
1406
1407 static void
1408 debug_window_domain_formatter (GtkTreeViewColumn *tree_column,
1409     GtkCellRenderer *cell,
1410     GtkTreeModel *tree_model,
1411     GtkTreeIter *iter,
1412     gpointer data)
1413 {
1414   TpDebugMessage *msg;
1415
1416   gtk_tree_model_get (tree_model, iter, COL_DEBUG_MESSAGE, &msg, -1);
1417
1418   g_object_set (G_OBJECT (cell), "text", tp_debug_message_get_domain (msg),
1419       NULL);
1420
1421   g_object_unref (msg);
1422 }
1423
1424 static void
1425 debug_window_category_formatter (GtkTreeViewColumn *tree_column,
1426     GtkCellRenderer *cell,
1427     GtkTreeModel *tree_model,
1428     GtkTreeIter *iter,
1429     gpointer data)
1430 {
1431   TpDebugMessage *msg;
1432   const gchar *category;
1433
1434   gtk_tree_model_get (tree_model, iter, COL_DEBUG_MESSAGE, &msg, -1);
1435
1436   category = tp_debug_message_get_category (msg);
1437
1438   g_object_set (G_OBJECT (cell), "text", category ? category : "", NULL);
1439
1440   g_object_unref (msg);
1441 }
1442
1443 static void
1444 debug_window_message_formatter (GtkTreeViewColumn *tree_column,
1445     GtkCellRenderer *cell,
1446     GtkTreeModel *tree_model,
1447     GtkTreeIter *iter,
1448     gpointer data)
1449 {
1450   TpDebugMessage *msg;
1451
1452   gtk_tree_model_get (tree_model, iter, COL_DEBUG_MESSAGE, &msg, -1);
1453
1454   g_object_set (G_OBJECT (cell), "text",
1455       tp_debug_message_get_message (msg), NULL);
1456
1457   g_object_unref (msg);
1458 }
1459
1460 static void
1461 debug_window_level_formatter (GtkTreeViewColumn *tree_column,
1462     GtkCellRenderer *cell,
1463     GtkTreeModel *tree_model,
1464     GtkTreeIter *iter,
1465     gpointer data)
1466 {
1467   TpDebugMessage *msg;
1468   const gchar *level;
1469
1470   gtk_tree_model_get (tree_model, iter, COL_DEBUG_MESSAGE, &msg, -1);
1471
1472   level = log_level_to_string (tp_debug_message_get_level (msg));
1473
1474   g_object_set (G_OBJECT (cell), "text", level, NULL);
1475
1476   g_object_unref (msg);
1477 }
1478
1479 static gboolean
1480 debug_window_copy_model_foreach (GtkTreeModel *model,
1481     GtkTreePath *path,
1482     GtkTreeIter *iter,
1483     gpointer user_data)
1484 {
1485   gchar **text = (gchar **) user_data;
1486   gchar *tmp;
1487   gchar *level_upper;
1488   const gchar *level_str, *category;
1489   gchar *line, *time_str;
1490   TpDebugMessage *msg;
1491
1492   if (*text == NULL)
1493     *text = g_strdup ("");
1494
1495   gtk_tree_model_get (model, iter,
1496       COL_DEBUG_MESSAGE, &msg,
1497       -1);
1498
1499   level_str = log_level_to_string (tp_debug_message_get_level (msg));
1500   level_upper = g_ascii_strup (level_str, -1);
1501
1502   time_str = debug_window_format_timestamp (msg);
1503   category = tp_debug_message_get_category (msg);
1504
1505   line = g_strdup_printf ("%s%s%s-%s: %s: %s\n",
1506       tp_debug_message_get_domain (msg),
1507       category ? "" : "/", category ? category : "",
1508       level_upper, time_str, tp_debug_message_get_message (msg));
1509
1510   g_free (time_str);
1511
1512   tmp = g_strconcat (*text, line, NULL);
1513
1514   g_free (*text);
1515   g_free (line);
1516   g_free (level_upper);
1517   g_object_unref (msg);
1518
1519   *text = tmp;
1520
1521   return FALSE;
1522 }
1523
1524 static void
1525 debug_window_save_file_chooser_response_cb (GtkDialog *dialog,
1526     gint response_id,
1527     EmpathyDebugWindow *self)
1528 {
1529   gchar *filename = NULL;
1530   GFile *gfile = NULL;
1531   gchar *debug_data = NULL;
1532   GFileOutputStream *output_stream = NULL;
1533   GError *file_open_error = NULL;
1534   GError *file_write_error = NULL;
1535
1536   if (response_id != GTK_RESPONSE_ACCEPT)
1537     goto OUT;
1538
1539   filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
1540
1541   DEBUG ("Saving log as %s", filename);
1542
1543   gfile = g_file_new_for_path (filename);
1544   output_stream = g_file_replace (gfile, NULL, FALSE,
1545       G_FILE_CREATE_NONE, NULL, &file_open_error);
1546
1547   if (file_open_error != NULL)
1548     {
1549       DEBUG ("Failed to open file for writing: %s", file_open_error->message);
1550       g_error_free (file_open_error);
1551       goto OUT;
1552     }
1553
1554   gtk_tree_model_foreach (self->priv->store_filter,
1555       debug_window_copy_model_foreach, &debug_data);
1556
1557   g_output_stream_write (G_OUTPUT_STREAM (output_stream), debug_data,
1558       strlen (debug_data), NULL, &file_write_error);
1559   g_free (debug_data);
1560
1561   if (file_write_error != NULL)
1562     {
1563       DEBUG ("Failed to write to file: %s", file_write_error->message);
1564       g_error_free (file_write_error);
1565     }
1566
1567 OUT:
1568   if (gfile != NULL)
1569     g_object_unref (gfile);
1570
1571   if (output_stream != NULL)
1572     g_object_unref (output_stream);
1573
1574   if (filename != NULL)
1575     g_free (filename);
1576
1577   gtk_widget_destroy (GTK_WIDGET (dialog));
1578 }
1579
1580 static void
1581 debug_window_save_clicked_cb (GtkToolButton *tool_button,
1582     EmpathyDebugWindow *self)
1583 {
1584   GtkWidget *file_chooser;
1585   gchar *name, *tmp = NULL;
1586   char time_str[32];
1587   time_t t;
1588   struct tm *tm_s;
1589
1590   file_chooser = gtk_file_chooser_dialog_new (_("Save"),
1591       GTK_WINDOW (self), GTK_FILE_CHOOSER_ACTION_SAVE,
1592       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1593       GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1594       NULL);
1595
1596   gtk_window_set_modal (GTK_WINDOW (file_chooser), TRUE);
1597   gtk_file_chooser_set_do_overwrite_confirmation (
1598       GTK_FILE_CHOOSER (file_chooser), TRUE);
1599
1600   gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (file_chooser),
1601       g_get_home_dir ());
1602
1603   name = get_active_service_name (self);
1604
1605   t = time (NULL);
1606   tm_s = localtime (&t);
1607   if (tm_s != NULL)
1608     {
1609       if (strftime (time_str, sizeof (time_str), "%d-%m-%y_%H-%M-%S", tm_s))
1610         tmp = g_strdup_printf ("%s-%s.log", name, time_str);
1611     }
1612
1613   if (tmp == NULL)
1614     tmp = g_strdup_printf ("%s.log", name);
1615   g_free (name);
1616
1617   gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (file_chooser), tmp);
1618   g_free (tmp);
1619
1620   g_signal_connect (file_chooser, "response",
1621       G_CALLBACK (debug_window_save_file_chooser_response_cb),
1622       self);
1623
1624   gtk_widget_show (file_chooser);
1625 }
1626
1627 static void
1628 debug_window_pastebin_response_dialog_closed_cb (GtkDialog *dialog,
1629     gint response_id,
1630     SoupBuffer *buffer)
1631 {
1632   soup_buffer_free (buffer);
1633
1634   gtk_widget_destroy (GTK_WIDGET (dialog));
1635 }
1636
1637 static void
1638 debug_window_pastebin_callback (SoupSession *session,
1639     SoupMessage *msg,
1640     gpointer self)
1641 {
1642   GtkWidget *dialog;
1643   SoupBuffer *buffer;
1644
1645   buffer = soup_message_body_flatten (msg->response_body);
1646   if (g_str_has_prefix (buffer->data, "http://pastebin.com/"))
1647     {
1648       dialog = gtk_message_dialog_new (GTK_WINDOW (self),
1649           GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
1650           _("Pastebin link"));
1651
1652       gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
1653           "<a href=\"%s\">%s</a>", buffer->data, buffer->data);
1654     }
1655   else
1656     {
1657       dialog = gtk_message_dialog_new (GTK_WINDOW (self),
1658           GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE,
1659           _("Pastebin response"));
1660
1661       if (!tp_str_empty (buffer->data))
1662         gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
1663             "%s", buffer->data);
1664       else
1665         gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1666             _("Data too large for a single paste. Please save logs to file."));
1667     }
1668
1669   g_object_unref (session);
1670
1671   gtk_window_set_transient_for (GTK_WINDOW (dialog), self);
1672
1673   gtk_widget_show_all (GTK_WIDGET (dialog));
1674
1675   g_signal_connect_after (dialog, "response", G_CALLBACK (
1676       debug_window_pastebin_response_dialog_closed_cb), buffer);
1677 }
1678
1679 static void
1680 debug_window_message_dialog (EmpathyDebugWindow *self,
1681     const gchar *primary_text,
1682     const gchar *secondary_text)
1683 {
1684   GtkWidget *dialog;
1685
1686   dialog = gtk_message_dialog_new (GTK_WINDOW (self),
1687       GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_OK,
1688       "%s", _(primary_text));
1689   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1690       "%s", _(secondary_text));
1691   gtk_window_set_transient_for (GTK_WINDOW (dialog),
1692       GTK_WINDOW (self));
1693
1694   gtk_dialog_run (GTK_DIALOG (dialog));
1695   gtk_widget_destroy (dialog);
1696 }
1697
1698 static void
1699 debug_window_send_to_pastebin (EmpathyDebugWindow *self,
1700     gchar *debug_data)
1701 {
1702   SoupSession *session;
1703   SoupMessage *msg;
1704   gchar       *api_dev_key, *api_paste_code, *api_paste_name, *formdata;
1705
1706   if (tp_str_empty (debug_data))
1707     {
1708       debug_window_message_dialog (self, "Error", "No data to send");
1709       return;
1710     }
1711
1712   /* Constructing a valid URL for http post. See http://pastebin.com/api#2 */
1713
1714   /* The api_dev_key is the author's developer key to access the Pastebin API
1715    * This developer key is published here with the autorization of pastebin;
1716    * see PASTEBIN-API-KEY.txt */
1717   api_dev_key = soup_uri_encode ("f6ccfabfdcd4b77b825ee38a30d11d52", NULL);
1718   api_paste_code = soup_uri_encode (debug_data, NULL);
1719   api_paste_name = soup_uri_encode ("Empathy debug data", NULL);
1720   formdata = g_strdup_printf ("api_dev_key=%s&api_paste_code=%s"
1721       "&api_paste_name=%s&api_paste_format=text&api_option=paste",
1722       api_dev_key, api_paste_code, api_paste_name);
1723
1724   session = soup_session_async_new ();
1725
1726   msg = soup_message_new ("POST", "http://pastebin.com/api/api_post.php");
1727   soup_message_set_request (msg,
1728       "application/x-www-form-urlencoded;charset=UTF-8", SOUP_MEMORY_COPY,
1729       formdata, strlen (formdata));
1730
1731   g_free (api_dev_key);
1732   g_free (api_paste_code);
1733   g_free (api_paste_name);
1734   g_free (formdata);
1735
1736   soup_session_queue_message (session, msg, debug_window_pastebin_callback,
1737       self);
1738 }
1739
1740 static void
1741 debug_window_send_to_pastebin_cb (GtkToolButton *tool_button,
1742     EmpathyDebugWindow *self)
1743 {
1744   gchar *debug_data = NULL;
1745
1746   DEBUG ("Preparing debug data for sending to pastebin.");
1747
1748   gtk_tree_model_foreach (self->priv->store_filter,
1749       debug_window_copy_model_foreach, &debug_data);
1750
1751   debug_window_send_to_pastebin (self, debug_data);
1752   g_free (debug_data);
1753 }
1754
1755 static void
1756 debug_window_copy_clicked_cb (GtkToolButton *tool_button,
1757     EmpathyDebugWindow *self)
1758 {
1759   GtkClipboard *clipboard;
1760   gchar *text = NULL;
1761
1762   gtk_tree_model_foreach (self->priv->store_filter,
1763       debug_window_copy_model_foreach, &text);
1764
1765   clipboard = gtk_clipboard_get_for_display (
1766       gtk_widget_get_display (GTK_WIDGET (tool_button)),
1767       GDK_SELECTION_CLIPBOARD);
1768
1769   DEBUG ("Copying text to clipboard (length: %" G_GSIZE_FORMAT ")",
1770       strlen (text));
1771
1772   gtk_clipboard_set_text (clipboard, text, -1);
1773
1774   g_free (text);
1775 }
1776
1777 static gboolean
1778 debug_window_key_press_event_cb (GtkWidget *widget,
1779     GdkEventKey *event,
1780     gpointer user_data)
1781 {
1782   if ((event->state & GDK_CONTROL_MASK && event->keyval == GDK_KEY_w)
1783       || event->keyval == GDK_KEY_Escape)
1784     {
1785       gtk_widget_destroy (widget);
1786       return TRUE;
1787     }
1788
1789   return FALSE;
1790 }
1791
1792 static void
1793 empathy_debug_window_select_name (EmpathyDebugWindow *self,
1794     const gchar *name)
1795 {
1796   GtkTreeModel *model = GTK_TREE_MODEL (self->priv->service_store);
1797   GtkTreeIter iter;
1798   gchar *iter_name;
1799   gboolean valid, found = FALSE;
1800
1801   for (valid = gtk_tree_model_get_iter_first (model, &iter);
1802        valid;
1803        valid = gtk_tree_model_iter_next (model, &iter))
1804     {
1805       gtk_tree_model_get (model, &iter,
1806           COL_NAME, &iter_name,
1807           -1);
1808
1809       if (!tp_strdiff (name, iter_name))
1810         found = TRUE;
1811
1812       g_free (iter_name);
1813
1814       if (found)
1815         break;
1816     }
1817
1818   if (found)
1819     gtk_combo_box_set_active_iter (GTK_COMBO_BOX (self->priv->chooser), &iter);
1820 }
1821
1822 static void
1823 am_prepared_cb (GObject *am,
1824     GAsyncResult *res,
1825     gpointer user_data)
1826 {
1827   EmpathyDebugWindow *self = user_data;
1828   GObject *object = user_data;
1829   GtkWidget *vbox;
1830   GtkWidget *toolbar;
1831   GtkWidget *image;
1832   GtkWidget *label;
1833   GtkToolItem *item;
1834   GtkCellRenderer *renderer;
1835   GtkListStore *level_store;
1836   GtkTreeIter iter;
1837   GError *error = NULL;
1838   GtkWidget *infobar, *content;
1839
1840   if (!tp_proxy_prepare_finish (am, res, &error))
1841     {
1842       g_warning ("Failed to prepare AM: %s", error->message);
1843       g_clear_error (&error);
1844     }
1845
1846   empathy_set_css_provider (GTK_WIDGET (object));
1847
1848   gtk_window_set_title (GTK_WINDOW (object), _("Debug Window"));
1849   gtk_window_set_default_size (GTK_WINDOW (object), 800, 400);
1850   empathy_geometry_bind (GTK_WINDOW (object), "debug-window");
1851
1852   g_signal_connect (object, "key-press-event",
1853       G_CALLBACK (debug_window_key_press_event_cb), NULL);
1854
1855   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
1856   gtk_container_add (GTK_CONTAINER (object), vbox);
1857   gtk_widget_show (vbox);
1858
1859   toolbar = gtk_toolbar_new ();
1860   gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH_HORIZ);
1861   gtk_toolbar_set_show_arrow (GTK_TOOLBAR (toolbar), TRUE);
1862   gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar),
1863       GTK_ICON_SIZE_SMALL_TOOLBAR);
1864   gtk_style_context_add_class (gtk_widget_get_style_context (toolbar),
1865                                GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
1866   gtk_widget_show (toolbar);
1867
1868   gtk_box_pack_start (GTK_BOX (vbox), toolbar, FALSE, FALSE, 0);
1869
1870   /* CM */
1871   self->priv->chooser = gtk_combo_box_text_new ();
1872   self->priv->service_store = gtk_list_store_new (NUM_COLS,
1873       G_TYPE_STRING,  /* COL_NAME */
1874       G_TYPE_STRING,  /* COL_UNIQUE_NAME */
1875       G_TYPE_BOOLEAN, /* COL_GONE */
1876       G_TYPE_OBJECT,  /* COL_ACTIVE_BUFFER */
1877       G_TYPE_OBJECT,  /* COL_PAUSE_BUFFER */
1878       TP_TYPE_PROXY); /* COL_PROXY */
1879   gtk_combo_box_set_model (GTK_COMBO_BOX (self->priv->chooser),
1880       GTK_TREE_MODEL (self->priv->service_store));
1881   gtk_widget_show (self->priv->chooser);
1882
1883   item = gtk_tool_item_new ();
1884   gtk_widget_show (GTK_WIDGET (item));
1885   gtk_container_add (GTK_CONTAINER (item), self->priv->chooser);
1886   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
1887   g_signal_connect (self->priv->chooser, "changed",
1888       G_CALLBACK (debug_window_service_chooser_changed_cb), object);
1889   gtk_widget_show (GTK_WIDGET (self->priv->chooser));
1890
1891   item = gtk_separator_tool_item_new ();
1892   gtk_widget_show (GTK_WIDGET (item));
1893   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
1894
1895   /* Save */
1896   self->priv->save_button = gtk_tool_button_new_from_stock (GTK_STOCK_SAVE);
1897   g_signal_connect (self->priv->save_button, "clicked",
1898       G_CALLBACK (debug_window_save_clicked_cb), object);
1899   gtk_widget_show (GTK_WIDGET (self->priv->save_button));
1900   gtk_tool_item_set_is_important (GTK_TOOL_ITEM (self->priv->save_button),
1901       TRUE);
1902   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), self->priv->save_button, -1);
1903
1904   /* Send to pastebin */
1905   self->priv->send_to_pastebin = gtk_tool_button_new_from_stock (
1906       GTK_STOCK_PASTE);
1907   gtk_tool_button_set_label (GTK_TOOL_BUTTON (self->priv->send_to_pastebin),
1908       _("Send to pastebin"));
1909   g_signal_connect (self->priv->send_to_pastebin, "clicked",
1910       G_CALLBACK (debug_window_send_to_pastebin_cb), object);
1911   gtk_widget_show (GTK_WIDGET (self->priv->send_to_pastebin));
1912   gtk_tool_item_set_is_important (GTK_TOOL_ITEM (self->priv->send_to_pastebin),
1913       TRUE);
1914   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), self->priv->send_to_pastebin, -1);
1915
1916   /* Copy */
1917   self->priv->copy_button = gtk_tool_button_new_from_stock (GTK_STOCK_COPY);
1918   g_signal_connect (self->priv->copy_button, "clicked",
1919       G_CALLBACK (debug_window_copy_clicked_cb), object);
1920   gtk_widget_show (GTK_WIDGET (self->priv->copy_button));
1921   gtk_tool_item_set_is_important (GTK_TOOL_ITEM (self->priv->copy_button),
1922       TRUE);
1923   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), self->priv->copy_button, -1);
1924
1925   /* Clear */
1926   self->priv->clear_button = gtk_tool_button_new_from_stock (GTK_STOCK_CLEAR);
1927   g_signal_connect (self->priv->clear_button, "clicked",
1928       G_CALLBACK (debug_window_clear_clicked_cb), object);
1929   gtk_widget_show (GTK_WIDGET (self->priv->clear_button));
1930   gtk_tool_item_set_is_important (GTK_TOOL_ITEM (self->priv->clear_button),
1931       TRUE);
1932   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), self->priv->clear_button, -1);
1933
1934   item = gtk_separator_tool_item_new ();
1935   gtk_widget_show (GTK_WIDGET (item));
1936   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
1937
1938   /* Pause */
1939   self->priv->paused = FALSE;
1940   image = gtk_image_new_from_stock (GTK_STOCK_MEDIA_PAUSE,
1941       GTK_ICON_SIZE_MENU);
1942   gtk_widget_show (image);
1943   self->priv->pause_button = gtk_toggle_tool_button_new ();
1944   gtk_toggle_tool_button_set_active (
1945       GTK_TOGGLE_TOOL_BUTTON (self->priv->pause_button), self->priv->paused);
1946   g_signal_connect (self->priv->pause_button, "toggled",
1947       G_CALLBACK (debug_window_pause_toggled_cb), object);
1948   gtk_widget_show (GTK_WIDGET (self->priv->pause_button));
1949   gtk_tool_item_set_is_important (GTK_TOOL_ITEM (self->priv->pause_button),
1950       TRUE);
1951   gtk_tool_button_set_label (GTK_TOOL_BUTTON (self->priv->pause_button),
1952       _("Pause"));
1953   gtk_tool_button_set_icon_widget (
1954       GTK_TOOL_BUTTON (self->priv->pause_button), image);
1955   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), self->priv->pause_button, -1);
1956
1957   item = gtk_separator_tool_item_new ();
1958   gtk_widget_show (GTK_WIDGET (item));
1959   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
1960
1961   /* Level */
1962   self->priv->level_label = gtk_tool_item_new ();
1963   gtk_widget_show (GTK_WIDGET (self->priv->level_label));
1964   label = gtk_label_new (_("Level "));
1965   gtk_widget_show (label);
1966   gtk_container_add (GTK_CONTAINER (self->priv->level_label), label);
1967   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), self->priv->level_label, -1);
1968
1969   self->priv->level_filter = gtk_combo_box_text_new ();
1970   gtk_widget_show (self->priv->level_filter);
1971
1972   item = gtk_tool_item_new ();
1973   gtk_widget_show (GTK_WIDGET (item));
1974   gtk_container_add (GTK_CONTAINER (item), self->priv->level_filter);
1975   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
1976
1977   level_store = gtk_list_store_new (NUM_COLS_LEVEL,
1978       G_TYPE_STRING, G_TYPE_UINT);
1979   gtk_combo_box_set_model (GTK_COMBO_BOX (self->priv->level_filter),
1980       GTK_TREE_MODEL (level_store));
1981
1982   gtk_list_store_insert_with_values (level_store, &iter, -1,
1983       COL_LEVEL_NAME, _("Debug"),
1984       COL_LEVEL_VALUE, G_LOG_LEVEL_DEBUG,
1985       -1);
1986
1987   gtk_list_store_insert_with_values (level_store, &iter, -1,
1988       COL_LEVEL_NAME, _("Info"),
1989       COL_LEVEL_VALUE, G_LOG_LEVEL_INFO,
1990       -1);
1991
1992   gtk_list_store_insert_with_values (level_store, &iter, -1,
1993       COL_LEVEL_NAME, _("Message"),
1994       COL_LEVEL_VALUE, G_LOG_LEVEL_MESSAGE,
1995       -1);
1996
1997   gtk_list_store_insert_with_values (level_store, &iter, -1,
1998       COL_LEVEL_NAME, _("Warning"),
1999       COL_LEVEL_VALUE, G_LOG_LEVEL_WARNING,
2000       -1);
2001
2002   gtk_list_store_insert_with_values (level_store, &iter, -1,
2003       COL_LEVEL_NAME, _("Critical"),
2004       COL_LEVEL_VALUE, G_LOG_LEVEL_CRITICAL,
2005       -1);
2006
2007   gtk_list_store_insert_with_values (level_store, &iter, -1,
2008       COL_LEVEL_NAME, _("Error"),
2009       COL_LEVEL_VALUE, G_LOG_LEVEL_ERROR,
2010       -1);
2011
2012   gtk_combo_box_set_active (GTK_COMBO_BOX (self->priv->level_filter), 0);
2013   g_signal_connect (self->priv->level_filter, "changed",
2014       G_CALLBACK (debug_window_filter_changed_cb), object);
2015
2016   /* Info bar */
2017   infobar = gtk_info_bar_new ();
2018   gtk_info_bar_set_message_type (GTK_INFO_BAR (infobar), GTK_MESSAGE_INFO);
2019
2020   label = gtk_label_new (
2021         _("Even if they don't display passwords, logs can contain sensitive "
2022           "information such as your list of contacts or the messages you "
2023           "recently sent or received.\nIf you don't want to see such "
2024           "information available in a public bug report, you "
2025           "can choose to limit the visibility of your bug to "
2026           "Empathy developers when reporting it by displaying "
2027           "the advanced fields in the "
2028           "<a href=\"https://bugzilla.gnome.org/enter_bug.cgi?product=empathy\">"
2029           "bug report</a>."));
2030   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
2031   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
2032   gtk_style_context_add_class (gtk_widget_get_style_context (label),
2033       GTK_STYLE_CLASS_DIM_LABEL);
2034
2035   content = gtk_info_bar_get_content_area (GTK_INFO_BAR (infobar));
2036   gtk_box_pack_start (GTK_BOX (content), label, FALSE, FALSE, 0);
2037
2038   gtk_widget_show (infobar);
2039   gtk_widget_show (label);
2040   gtk_box_pack_start (GTK_BOX (vbox), infobar, FALSE, FALSE, 0);
2041
2042   /* Debug treeview */
2043   self->priv->view = gtk_tree_view_new ();
2044   gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (self->priv->view), TRUE);
2045
2046   g_signal_connect (self->priv->view, "button-press-event",
2047       G_CALLBACK (debug_window_button_press_event_cb), object);
2048
2049   renderer = gtk_cell_renderer_text_new ();
2050   g_object_set (renderer, "yalign", (gfloat) 0, NULL);
2051
2052   gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (self->priv->view),
2053       -1, _("Time"), renderer,
2054       (GtkTreeCellDataFunc) debug_window_time_formatter, NULL, NULL);
2055   gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (self->priv->view),
2056       -1, _("Domain"), renderer,
2057       (GtkTreeCellDataFunc) debug_window_domain_formatter, NULL, NULL);
2058   gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (self->priv->view),
2059       -1, _("Category"), renderer,
2060       (GtkTreeCellDataFunc) debug_window_category_formatter, NULL, NULL);
2061   gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (self->priv->view),
2062       -1, _("Level"), renderer,
2063       (GtkTreeCellDataFunc) debug_window_level_formatter, NULL, NULL);
2064
2065   renderer = gtk_cell_renderer_text_new ();
2066
2067   g_object_set (renderer,
2068       "family", "Monospace",
2069       "ellipsize", PANGO_ELLIPSIZE_END,
2070       NULL);
2071
2072   gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (self->priv->view),
2073       -1, _("Message"), renderer,
2074       (GtkTreeCellDataFunc) debug_window_message_formatter, NULL, NULL);
2075
2076   self->priv->store_filter = NULL;
2077
2078   gtk_tree_view_set_model (GTK_TREE_VIEW (self->priv->view),
2079       self->priv->store_filter);
2080
2081   /* Scrolled window */
2082   self->priv->scrolled_win = g_object_ref (gtk_scrolled_window_new (
2083         NULL, NULL));
2084   gtk_scrolled_window_set_policy (
2085       GTK_SCROLLED_WINDOW (self->priv->scrolled_win),
2086       GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2087
2088   gtk_widget_show (self->priv->view);
2089   gtk_container_add (GTK_CONTAINER (self->priv->scrolled_win),
2090       self->priv->view);
2091
2092   gtk_widget_show (self->priv->scrolled_win);
2093
2094   /* Not supported label */
2095   self->priv->not_supported_label = g_object_ref (gtk_label_new (
2096           _("The selected connection manager does not support the remote "
2097               "debugging extension.")));
2098   gtk_widget_show (self->priv->not_supported_label);
2099   gtk_box_pack_start (GTK_BOX (vbox), self->priv->not_supported_label,
2100       TRUE, TRUE, 0);
2101
2102   self->priv->view_visible = FALSE;
2103
2104   self->priv->all_active_buffer = NULL;
2105
2106   debug_window_set_toolbar_sensitivity (EMPATHY_DEBUG_WINDOW (object), FALSE);
2107   debug_window_fill_service_chooser (EMPATHY_DEBUG_WINDOW (object));
2108   gtk_widget_show (GTK_WIDGET (object));
2109 }
2110
2111 static void
2112 debug_window_constructed (GObject *object)
2113 {
2114   EmpathyDebugWindow *self = EMPATHY_DEBUG_WINDOW (object);
2115
2116   self->priv->am = tp_account_manager_dup ();
2117   tp_proxy_prepare_async (self->priv->am, NULL, am_prepared_cb, object);
2118 }
2119
2120 static void
2121 empathy_debug_window_init (EmpathyDebugWindow *self)
2122 {
2123   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2124       EMPATHY_TYPE_DEBUG_WINDOW, EmpathyDebugWindowPriv);
2125 }
2126
2127 static void
2128 debug_window_set_property (GObject *object,
2129     guint prop_id,
2130     const GValue *value,
2131     GParamSpec *pspec)
2132 {
2133   switch (prop_id)
2134     {
2135       default:
2136         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2137         break;
2138     }
2139 }
2140
2141 static void
2142 debug_window_get_property (GObject *object,
2143     guint prop_id,
2144     GValue *value,
2145     GParamSpec *pspec)
2146 {
2147   switch (prop_id)
2148     {
2149       default:
2150         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2151         break;
2152     }
2153 }
2154
2155 static void
2156 debug_window_finalize (GObject *object)
2157 {
2158   EmpathyDebugWindow *self = EMPATHY_DEBUG_WINDOW (object);
2159
2160   g_free (self->priv->select_name);
2161
2162   (G_OBJECT_CLASS (empathy_debug_window_parent_class)->finalize) (object);
2163 }
2164
2165 static void
2166 debug_window_dispose (GObject *object)
2167 {
2168   EmpathyDebugWindow *self = EMPATHY_DEBUG_WINDOW (object);
2169
2170   if (self->priv->name_owner_changed_signal != NULL)
2171     tp_proxy_signal_connection_disconnect (
2172         self->priv->name_owner_changed_signal);
2173
2174   g_clear_object (&self->priv->service_store);
2175   g_clear_object (&self->priv->dbus);
2176   g_clear_object (&self->priv->am);
2177   g_clear_object (&self->priv->all_active_buffer);
2178
2179   (G_OBJECT_CLASS (empathy_debug_window_parent_class)->dispose) (object);
2180 }
2181
2182 static void
2183 empathy_debug_window_class_init (EmpathyDebugWindowClass *klass)
2184 {
2185   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2186   object_class->constructed = debug_window_constructed;
2187   object_class->dispose = debug_window_dispose;
2188   object_class->finalize = debug_window_finalize;
2189   object_class->set_property = debug_window_set_property;
2190   object_class->get_property = debug_window_get_property;
2191
2192   g_type_class_add_private (klass, sizeof (EmpathyDebugWindowPriv));
2193 }
2194
2195 /* public methods */
2196
2197 GtkWidget *
2198 empathy_debug_window_new (GtkWindow *parent)
2199 {
2200   g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), NULL);
2201
2202   return GTK_WIDGET (g_object_new (EMPATHY_TYPE_DEBUG_WINDOW,
2203       "transient-for", parent, NULL));
2204 }
2205
2206 void
2207 empathy_debug_window_show (EmpathyDebugWindow *self,
2208     const gchar *name)
2209 {
2210   if (self->priv->service_store != NULL)
2211     {
2212       empathy_debug_window_select_name (self, name);
2213     }
2214   else
2215     {
2216       g_free (self->priv->select_name);
2217       self->priv->select_name = g_strdup (name);
2218     }
2219 }