aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/libempathy-gtk
diff options
context:
space:
mode:
authorXavier Claessens <xclaesse@src.gnome.org>2007-06-01 23:29:18 +0800
committerXavier Claessens <xclaesse@src.gnome.org>2007-06-01 23:29:18 +0800
commit86381b9188f51cb87cd074835b4eb42cc10e721f (patch)
treedb8a6d3d77ae59ced74268baf6c6ba7b4e88c99c /trunk/libempathy-gtk
parent2b97b3d9e72dcbb6ac6132ae9278baebed923162 (diff)
downloadgsoc2013-empathy-EMPATHY_0_6.tar
gsoc2013-empathy-EMPATHY_0_6.tar.gz
gsoc2013-empathy-EMPATHY_0_6.tar.bz2
gsoc2013-empathy-EMPATHY_0_6.tar.lz
gsoc2013-empathy-EMPATHY_0_6.tar.xz
gsoc2013-empathy-EMPATHY_0_6.tar.zst
gsoc2013-empathy-EMPATHY_0_6.zip
Tagged for release 0.6.EMPATHY_0_6
svn path=/tags/EMPATHY_0_6/; revision=108
Diffstat (limited to 'trunk/libempathy-gtk')
-rw-r--r--trunk/libempathy-gtk/Makefile.am68
-rw-r--r--trunk/libempathy-gtk/empathy-images.h43
-rw-r--r--trunk/libempathy-gtk/empathy-main-window.c919
-rw-r--r--trunk/libempathy-gtk/empathy-main-window.glade427
-rw-r--r--trunk/libempathy-gtk/empathy-main-window.h34
-rw-r--r--trunk/libempathy-gtk/empathy-status-icon.c345
-rw-r--r--trunk/libempathy-gtk/empathy-status-icon.glade78
-rw-r--r--trunk/libempathy-gtk/empathy-status-icon.h54
-rw-r--r--trunk/libempathy-gtk/ephy-spinner.c977
-rw-r--r--trunk/libempathy-gtk/ephy-spinner.h70
-rw-r--r--trunk/libempathy-gtk/gossip-about-dialog.c113
-rw-r--r--trunk/libempathy-gtk/gossip-about-dialog.h34
-rw-r--r--trunk/libempathy-gtk/gossip-account-chooser.c637
-rw-r--r--trunk/libempathy-gtk/gossip-account-chooser.h66
-rw-r--r--trunk/libempathy-gtk/gossip-account-widget-generic.c310
-rw-r--r--trunk/libempathy-gtk/gossip-account-widget-generic.h39
-rw-r--r--trunk/libempathy-gtk/gossip-account-widget-jabber.c281
-rw-r--r--trunk/libempathy-gtk/gossip-account-widget-jabber.glade335
-rw-r--r--trunk/libempathy-gtk/gossip-account-widget-jabber.h34
-rw-r--r--trunk/libempathy-gtk/gossip-accounts-dialog.c1038
-rw-r--r--trunk/libempathy-gtk/gossip-accounts-dialog.glade757
-rw-r--r--trunk/libempathy-gtk/gossip-accounts-dialog.h35
-rw-r--r--trunk/libempathy-gtk/gossip-cell-renderer-expander.c482
-rw-r--r--trunk/libempathy-gtk/gossip-cell-renderer-expander.h59
-rw-r--r--trunk/libempathy-gtk/gossip-cell-renderer-text.c368
-rw-r--r--trunk/libempathy-gtk/gossip-cell-renderer-text.h56
-rw-r--r--trunk/libempathy-gtk/gossip-chat-manager.loT7
-rw-r--r--trunk/libempathy-gtk/gossip-chat-view.c2208
-rw-r--r--trunk/libempathy-gtk/gossip-chat-view.h134
-rw-r--r--trunk/libempathy-gtk/gossip-chat-window.c1910
-rw-r--r--trunk/libempathy-gtk/gossip-chat-window.h80
-rw-r--r--trunk/libempathy-gtk/gossip-chat.c1539
-rw-r--r--trunk/libempathy-gtk/gossip-chat.glade699
-rw-r--r--trunk/libempathy-gtk/gossip-chat.h120
-rw-r--r--trunk/libempathy-gtk/gossip-chatrooms-window.c530
-rw-r--r--trunk/libempathy-gtk/gossip-chatrooms-window.glade477
-rw-r--r--trunk/libempathy-gtk/gossip-chatrooms-window.h35
-rw-r--r--trunk/libempathy-gtk/gossip-contact-groups.c286
-rw-r--r--trunk/libempathy-gtk/gossip-contact-groups.dtd17
-rw-r--r--trunk/libempathy-gtk/gossip-contact-groups.h38
-rw-r--r--trunk/libempathy-gtk/gossip-contact-list-store.c1458
-rw-r--r--trunk/libempathy-gtk/gossip-contact-list-store.h122
-rw-r--r--trunk/libempathy-gtk/gossip-contact-list-view.c1505
-rw-r--r--trunk/libempathy-gtk/gossip-contact-list-view.h78
-rw-r--r--trunk/libempathy-gtk/gossip-geometry.c186
-rw-r--r--trunk/libempathy-gtk/gossip-geometry.h45
-rw-r--r--trunk/libempathy-gtk/gossip-group-chat.c632
-rw-r--r--trunk/libempathy-gtk/gossip-group-chat.glade183
-rw-r--r--trunk/libempathy-gtk/gossip-group-chat.h69
-rw-r--r--trunk/libempathy-gtk/gossip-new-chatroom-dialog.c690
-rw-r--r--trunk/libempathy-gtk/gossip-new-chatroom-dialog.glade519
-rw-r--r--trunk/libempathy-gtk/gossip-new-chatroom-dialog.h34
-rw-r--r--trunk/libempathy-gtk/gossip-preferences.c985
-rw-r--r--trunk/libempathy-gtk/gossip-preferences.glade1092
-rw-r--r--trunk/libempathy-gtk/gossip-preferences.h58
-rw-r--r--trunk/libempathy-gtk/gossip-presence-chooser.c1022
-rw-r--r--trunk/libempathy-gtk/gossip-presence-chooser.glade173
-rw-r--r--trunk/libempathy-gtk/gossip-presence-chooser.h64
-rw-r--r--trunk/libempathy-gtk/gossip-private-chat.c374
-rw-r--r--trunk/libempathy-gtk/gossip-private-chat.h68
-rw-r--r--trunk/libempathy-gtk/gossip-profile-chooser.c106
-rw-r--r--trunk/libempathy-gtk/gossip-profile-chooser.h34
-rw-r--r--trunk/libempathy-gtk/gossip-spell.c457
-rw-r--r--trunk/libempathy-gtk/gossip-spell.h39
-rw-r--r--trunk/libempathy-gtk/gossip-status-presets.c389
-rw-r--r--trunk/libempathy-gtk/gossip-status-presets.dtd14
-rw-r--r--trunk/libempathy-gtk/gossip-status-presets.h44
-rw-r--r--trunk/libempathy-gtk/gossip-theme-manager.c1045
-rw-r--r--trunk/libempathy-gtk/gossip-theme-manager.h64
-rw-r--r--trunk/libempathy-gtk/gossip-ui-utils.c1336
-rw-r--r--trunk/libempathy-gtk/gossip-ui-utils.h115
71 files changed, 28739 insertions, 0 deletions
diff --git a/trunk/libempathy-gtk/Makefile.am b/trunk/libempathy-gtk/Makefile.am
new file mode 100644
index 000000000..183f7d8e2
--- /dev/null
+++ b/trunk/libempathy-gtk/Makefile.am
@@ -0,0 +1,68 @@
+AM_CPPFLAGS = \
+ -I. \
+ -I$(top_srcdir) \
+ $(EMPATHY_CFLAGS) \
+ $(WARN_CFLAGS)
+
+noinst_LTLIBRARIES = libempathy-gtk.la
+
+libempathy_gtk_la_SOURCES = \
+ ephy-spinner.c ephy-spinner.h \
+ empathy-images.h \
+ empathy-main-window.c empathy-main-window.h \
+ empathy-status-icon.c empathy-status-icon.h \
+ gossip-accounts-dialog.c gossip-accounts-dialog.h \
+ gossip-account-widget-generic.c gossip-account-widget-generic.h \
+ gossip-account-widget-jabber.c gossip-account-widget-jabber.h \
+ gossip-profile-chooser.c gossip-profile-chooser.h \
+ gossip-cell-renderer-expander.c gossip-cell-renderer-expander.h \
+ gossip-cell-renderer-text.c gossip-cell-renderer-text.h \
+ gossip-spell.c gossip-spell.h \
+ gossip-contact-groups.c gossip-contact-groups.h \
+ gossip-contact-list-store.c gossip-contact-list-store.h \
+ gossip-contact-list-view.c gossip-contact-list-view.h \
+ gossip-preferences.c gossip-preferences.h \
+ gossip-theme-manager.c gossip-theme-manager.h \
+ gossip-chat-window.c gossip-chat-window.h \
+ gossip-chat.c gossip-chat.h \
+ gossip-chat-view.c gossip-chat-view.h \
+ gossip-private-chat.c gossip-private-chat.h \
+ gossip-group-chat.c gossip-group-chat.h \
+ gossip-geometry.c gossip-geometry.h \
+ gossip-status-presets.c gossip-status-presets.h \
+ gossip-presence-chooser.c gossip-presence-chooser.h \
+ gossip-about-dialog.c gossip-about-dialog.h \
+ gossip-account-chooser.c gossip-account-chooser.h \
+ gossip-new-chatroom-dialog.c gossip-new-chatroom-dialog.h \
+ gossip-chatrooms-window.c gossip-chatrooms-window.h \
+ gossip-ui-utils.c gossip-ui-utils.h
+
+libempathy_gtk_la_LIBADD = \
+ $(EMPATHY_LIBS) \
+ $(top_builddir)/libempathy/libempathy.la
+
+libempathy_gtk_includedir = $(includedir)/empathy/
+
+gladedir = $(datadir)/empathy
+glade_DATA = \
+ empathy-main-window.glade \
+ empathy-status-icon.glade \
+ gossip-preferences.glade \
+ gossip-presence-chooser.glade \
+ gossip-accounts-dialog.glade \
+ gossip-account-widget-jabber.glade \
+ gossip-new-chatroom-dialog.glade \
+ gossip-group-chat.glade \
+ gossip-chatrooms-window.glade \
+ gossip-chat.glade
+
+dtddir = $(datadir)/empathy
+dtd_DATA = \
+ gossip-status-presets.dtd \
+ gossip-contact-groups.dtd
+
+
+EXTRA_DIST = \
+ $(glade_DATA) \
+ $(dtd_DATA)
+
diff --git a/trunk/libempathy-gtk/empathy-images.h b/trunk/libempathy-gtk/empathy-images.h
new file mode 100644
index 000000000..5fb4e7173
--- /dev/null
+++ b/trunk/libempathy-gtk/empathy-images.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __EMPATHY_IMAGES_H__
+#define __EMPATHY_IMAGES_H__
+
+G_BEGIN_DECLS
+
+#define EMPATHY_IMAGE_OFFLINE "empathy-offline"
+#define EMPATHY_IMAGE_AVAILABLE "empathy-available"
+#define EMPATHY_IMAGE_BUSY "empathy-busy"
+#define EMPATHY_IMAGE_AWAY "empathy-away"
+#define EMPATHY_IMAGE_EXT_AWAY "empathy-extended-away"
+#define EMPATHY_IMAGE_PENDING "empathy-pending"
+
+#define EMPATHY_IMAGE_MESSAGE "empathy-message"
+#define EMPATHY_IMAGE_NEW_MESSAGE "empathy-new-message"
+#define EMPATHY_IMAGE_TYPING "empathy-typing"
+#define EMPATHY_IMAGE_CONTACT_INFORMATION "empathy-contact-information"
+#define EMPATHY_IMAGE_GROUP_MESSAGE "empathy-group-message"
+
+G_END_DECLS
+
+#endif /* __EMPATHY_IMAGES_ICONS_H__ */
diff --git a/trunk/libempathy-gtk/empathy-main-window.c b/trunk/libempathy-gtk/empathy-main-window.c
new file mode 100644
index 000000000..1ae227410
--- /dev/null
+++ b/trunk/libempathy-gtk/empathy-main-window.c
@@ -0,0 +1,919 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include <config.h>
+
+#include <sys/stat.h>
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+
+#include <libempathy/gossip-conf.h>
+#include <libempathy/gossip-contact.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-utils.h>
+#include <libempathy/gossip-chatroom-manager.h>
+#include <libempathy/gossip-chatroom.h>
+#include <libempathy/empathy-contact-list.h>
+#include <libempathy/empathy-contact-manager.h>
+
+#include "empathy-main-window.h"
+#include "ephy-spinner.h"
+#include "gossip-contact-list-store.h"
+#include "gossip-contact-list-view.h"
+#include "gossip-presence-chooser.h"
+#include "gossip-ui-utils.h"
+#include "gossip-status-presets.h"
+#include "gossip-geometry.h"
+#include "gossip-preferences.h"
+#include "gossip-accounts-dialog.h"
+#include "gossip-about-dialog.h"
+#include "gossip-new-chatroom-dialog.h"
+#include "gossip-chatrooms-window.h"
+
+#define DEBUG_DOMAIN "MainWindow"
+
+/* Minimum width of roster window if something goes wrong. */
+#define MIN_WIDTH 50
+
+/* Accels (menu shortcuts) can be configured and saved */
+#define ACCELS_FILENAME "accels.txt"
+
+/* Name in the geometry file */
+#define GEOMETRY_NAME "main-window"
+
+typedef struct {
+ GossipContactListView *list_view;
+ GossipContactListStore *list_store;
+ MissionControl *mc;
+ GossipChatroomManager *chatroom_manager;
+
+ /* Main widgets */
+ GtkWidget *window;
+ GtkWidget *main_vbox;
+
+ /* Tooltips for all widgets */
+ GtkTooltips *tooltips;
+
+ /* Menu widgets */
+ GtkWidget *room;
+ GtkWidget *room_menu;
+ GtkWidget *room_sep;
+ GtkWidget *room_join_favorites;
+ GtkWidget *edit_context;
+ GtkWidget *edit_context_separator;
+
+ /* Throbber */
+ GtkWidget *throbber;
+
+ /* Widgets that are enabled when there is... */
+ GList *widgets_connected; /* ... connected accounts */
+ GList *widgets_disconnected; /* ... disconnected accounts */
+
+ /* Status popup */
+ GtkWidget *presence_toolbar;
+ GtkWidget *presence_chooser;
+
+ /* Misc */
+ guint size_timeout_id;
+} EmpathyMainWindow;
+
+static void main_window_destroy_cb (GtkWidget *widget,
+ EmpathyMainWindow *window);
+static void main_window_favorite_chatroom_menu_setup (EmpathyMainWindow *window);
+static void main_window_favorite_chatroom_menu_added_cb (GossipChatroomManager *manager,
+ GossipChatroom *chatroom,
+ EmpathyMainWindow *window);
+static void main_window_favorite_chatroom_menu_removed_cb (GossipChatroomManager *manager,
+ GossipChatroom *chatroom,
+ EmpathyMainWindow *window);
+static void main_window_favorite_chatroom_menu_activate_cb (GtkMenuItem *menu_item,
+ GossipChatroom *chatroom);
+static void main_window_favorite_chatroom_menu_update (EmpathyMainWindow *window);
+static void main_window_favorite_chatroom_menu_add (EmpathyMainWindow *window,
+ GossipChatroom *chatroom);
+static void main_window_favorite_chatroom_join (GossipChatroom *chatroom);
+static void main_window_chat_quit_cb (GtkWidget *widget,
+ EmpathyMainWindow *window);
+static void main_window_chat_new_message_cb (GtkWidget *widget,
+ EmpathyMainWindow *window);
+static void main_window_chat_history_cb (GtkWidget *widget,
+ EmpathyMainWindow *window);
+static void main_window_room_join_new_cb (GtkWidget *widget,
+ EmpathyMainWindow *window);
+static void main_window_room_join_favorites_cb (GtkWidget *widget,
+ EmpathyMainWindow *window);
+static void main_window_room_manage_favorites_cb (GtkWidget *widget,
+ EmpathyMainWindow *window);
+static void main_window_chat_add_contact_cb (GtkWidget *widget,
+ EmpathyMainWindow *window);
+static void main_window_chat_show_offline_cb (GtkCheckMenuItem *item,
+ EmpathyMainWindow *window);
+static gboolean main_window_edit_button_press_event_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ EmpathyMainWindow *window);
+static void main_window_edit_accounts_cb (GtkWidget *widget,
+ EmpathyMainWindow *window);
+static void main_window_edit_personal_information_cb (GtkWidget *widget,
+ EmpathyMainWindow *window);
+static void main_window_edit_preferences_cb (GtkWidget *widget,
+ EmpathyMainWindow *window);
+static void main_window_help_about_cb (GtkWidget *widget,
+ EmpathyMainWindow *window);
+static void main_window_help_contents_cb (GtkWidget *widget,
+ EmpathyMainWindow *window);
+static gboolean main_window_throbber_button_press_event_cb (GtkWidget *throbber_ebox,
+ GdkEventButton *event,
+ EmpathyMainWindow *window);
+static void main_window_status_changed_cb (MissionControl *mc,
+ TelepathyConnectionStatus status,
+ McPresence presence,
+ TelepathyConnectionStatusReason reason,
+ const gchar *unique_name,
+ EmpathyMainWindow *window);
+static void main_window_update_status (EmpathyMainWindow *window);
+static void main_window_accels_load (void);
+static void main_window_accels_save (void);
+static void main_window_connection_items_setup (EmpathyMainWindow *window,
+ GladeXML *glade);
+static gboolean main_window_configure_event_timeout_cb (EmpathyMainWindow *window);
+static gboolean main_window_configure_event_cb (GtkWidget *widget,
+ GdkEventConfigure *event,
+ EmpathyMainWindow *window);
+static void main_window_notify_show_offline_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer check_menu_item);
+static void main_window_notify_show_avatars_cb (GossipConf *conf,
+ const gchar *key,
+ EmpathyMainWindow *window);
+static void main_window_notify_compact_contact_list_cb (GossipConf *conf,
+ const gchar *key,
+ EmpathyMainWindow *window);
+static void main_window_notify_sort_criterium_cb (GossipConf *conf,
+ const gchar *key,
+ EmpathyMainWindow *window);
+
+GtkWidget *
+empathy_main_window_show (void)
+{
+ static EmpathyMainWindow *window = NULL;
+ EmpathyContactList *list_iface;
+ GladeXML *glade;
+ GossipConf *conf;
+ GtkWidget *sw;
+ GtkWidget *show_offline_widget;
+ GtkWidget *ebox;
+ GtkToolItem *item;
+ gchar *str;
+ gboolean show_offline;
+ gboolean show_avatars;
+ gboolean compact_contact_list;
+ gint x, y, w, h;
+
+ if (window) {
+ gtk_window_present (GTK_WINDOW (window->window));
+ return window->window;
+ }
+
+ window = g_new0 (EmpathyMainWindow, 1);
+
+ /* Set up interface */
+ glade = gossip_glade_get_file ("empathy-main-window.glade",
+ "main_window",
+ NULL,
+ "main_window", &window->window,
+ "main_vbox", &window->main_vbox,
+ "chat_show_offline", &show_offline_widget,
+ "room", &window->room,
+ "room_sep", &window->room_sep,
+ "room_join_favorites", &window->room_join_favorites,
+ "edit_context", &window->edit_context,
+ "edit_context_separator", &window->edit_context_separator,
+ "presence_toolbar", &window->presence_toolbar,
+ "roster_scrolledwindow", &sw,
+ NULL);
+
+ gossip_glade_connect (glade,
+ window,
+ "main_window", "destroy", main_window_destroy_cb,
+ "main_window", "configure_event", main_window_configure_event_cb,
+ "chat_quit", "activate", main_window_chat_quit_cb,
+ "chat_new_message", "activate", main_window_chat_new_message_cb,
+ "chat_history", "activate", main_window_chat_history_cb,
+ "room_join_new", "activate", main_window_room_join_new_cb,
+ "room_join_favorites", "activate", main_window_room_join_favorites_cb,
+ "room_manage_favorites", "activate", main_window_room_manage_favorites_cb,
+ "chat_add_contact", "activate", main_window_chat_add_contact_cb,
+ "chat_show_offline", "toggled", main_window_chat_show_offline_cb,
+ "edit", "button-press-event", main_window_edit_button_press_event_cb,
+ "edit_accounts", "activate", main_window_edit_accounts_cb,
+ "edit_personal_information", "activate", main_window_edit_personal_information_cb,
+ "edit_preferences", "activate", main_window_edit_preferences_cb,
+ "help_about", "activate", main_window_help_about_cb,
+ "help_contents", "activate", main_window_help_contents_cb,
+ NULL);
+
+ /* Set up connection related widgets. */
+ main_window_connection_items_setup (window, glade);
+ g_object_unref (glade);
+
+ window->tooltips = g_object_ref_sink (gtk_tooltips_new ());
+ window->mc = gossip_mission_control_new ();
+ dbus_g_proxy_connect_signal (DBUS_G_PROXY (window->mc), "AccountStatusChanged",
+ G_CALLBACK (main_window_status_changed_cb),
+ window, NULL);
+
+ /* Set up menu */
+ main_window_favorite_chatroom_menu_setup (window);
+
+ gtk_widget_hide (window->edit_context);
+ gtk_widget_hide (window->edit_context_separator);
+
+ /* Set up presence chooser */
+ window->presence_chooser = gossip_presence_chooser_new ();
+ gtk_widget_show (window->presence_chooser);
+ item = gtk_tool_item_new ();
+ gtk_widget_show (GTK_WIDGET (item));
+ gtk_container_add (GTK_CONTAINER (item), window->presence_chooser);
+ gtk_tool_item_set_is_important (item, TRUE);
+ gtk_tool_item_set_expand (item, TRUE);
+ gtk_toolbar_insert (GTK_TOOLBAR (window->presence_toolbar), item, -1);
+
+ /* Set up the throbber */
+ ebox = gtk_event_box_new ();
+ gtk_event_box_set_visible_window (GTK_EVENT_BOX (ebox), FALSE);
+
+ window->throbber = ephy_spinner_new ();
+ ephy_spinner_set_size (EPHY_SPINNER (window->throbber), GTK_ICON_SIZE_LARGE_TOOLBAR);
+ gtk_container_add (GTK_CONTAINER (ebox), window->throbber);
+
+ item = gtk_tool_item_new ();
+ gtk_container_add (GTK_CONTAINER (item), ebox);
+ gtk_widget_show_all (GTK_WIDGET (item));
+
+ gtk_toolbar_insert (GTK_TOOLBAR (window->presence_toolbar), item, -1);
+
+ str = _("Show and edit accounts");
+ gtk_tooltips_set_tip (GTK_TOOLTIPS (window->tooltips),
+ ebox, str, str);
+
+ g_signal_connect (ebox,
+ "button-press-event",
+ G_CALLBACK (main_window_throbber_button_press_event_cb),
+ window);
+
+ /* Set up contact list. */
+ gossip_status_presets_get_all ();
+
+ list_iface = EMPATHY_CONTACT_LIST (empathy_contact_manager_new ());
+ empathy_contact_list_setup (list_iface);
+ window->list_store = gossip_contact_list_store_new (list_iface);
+ window->list_view = gossip_contact_list_view_new (window->list_store);
+ g_object_unref (list_iface);
+
+ gtk_widget_show (GTK_WIDGET (window->list_view));
+ gtk_container_add (GTK_CONTAINER (sw),
+ GTK_WIDGET (window->list_view));
+
+ /* Load user-defined accelerators. */
+ main_window_accels_load ();
+
+ /* Set window size. */
+ gossip_geometry_load (GEOMETRY_NAME, &x, &y, &w, &h);
+
+ if (w >= 1 && h >= 1) {
+ /* Use the defaults from the glade file if we
+ * don't have good w, h geometry.
+ */
+ gossip_debug (DEBUG_DOMAIN, "Configuring window default size w:%d, h:%d", w, h);
+ gtk_window_set_default_size (GTK_WINDOW (window->window), w, h);
+ }
+
+ if (x >= 0 && y >= 0) {
+ /* Let the window manager position it if we
+ * don't have good x, y coordinates.
+ */
+ gossip_debug (DEBUG_DOMAIN, "Configuring window default position x:%d, y:%d", x, y);
+ gtk_window_move (GTK_WINDOW (window->window), x, y);
+ }
+
+ conf = gossip_conf_get ();
+
+ /* Show offline ? */
+ gossip_conf_get_bool (conf,
+ GOSSIP_PREFS_CONTACTS_SHOW_OFFLINE,
+ &show_offline);
+ gossip_conf_notify_add (conf,
+ GOSSIP_PREFS_CONTACTS_SHOW_OFFLINE,
+ main_window_notify_show_offline_cb,
+ show_offline_widget);
+
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (show_offline_widget),
+ show_offline);
+
+ /* Show avatars ? */
+ gossip_conf_get_bool (conf,
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ &show_avatars);
+ gossip_conf_notify_add (conf,
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ (GossipConfNotifyFunc) main_window_notify_show_avatars_cb,
+ window);
+ gossip_contact_list_store_set_show_avatars (window->list_store, show_avatars);
+
+ /* Is compact ? */
+ gossip_conf_get_bool (conf,
+ GOSSIP_PREFS_UI_COMPACT_CONTACT_LIST,
+ &compact_contact_list);
+ gossip_conf_notify_add (conf,
+ GOSSIP_PREFS_UI_COMPACT_CONTACT_LIST,
+ (GossipConfNotifyFunc) main_window_notify_compact_contact_list_cb,
+ window);
+ gossip_contact_list_store_set_is_compact (window->list_store, compact_contact_list);
+
+ /* Sort criterium */
+ gossip_conf_notify_add (conf,
+ GOSSIP_PREFS_CONTACTS_SORT_CRITERIUM,
+ (GossipConfNotifyFunc) main_window_notify_sort_criterium_cb,
+ window);
+ main_window_notify_sort_criterium_cb (conf,
+ GOSSIP_PREFS_CONTACTS_SORT_CRITERIUM,
+ window);
+
+ main_window_update_status (window);
+
+ return window->window;
+}
+
+static void
+main_window_destroy_cb (GtkWidget *widget,
+ EmpathyMainWindow *window)
+{
+ /* Save user-defined accelerators. */
+ main_window_accels_save ();
+
+ dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (window->mc), "AccountStatusChanged",
+ G_CALLBACK (main_window_status_changed_cb),
+ window);
+
+ if (window->size_timeout_id) {
+ g_source_remove (window->size_timeout_id);
+ }
+
+ g_list_free (window->widgets_connected);
+ g_list_free (window->widgets_disconnected);
+
+ g_object_unref (window->tooltips);
+ g_object_unref (window->mc);
+ g_object_unref (window->list_store);
+
+ g_free (window);
+}
+
+static void
+main_window_favorite_chatroom_menu_setup (EmpathyMainWindow *window)
+{
+ GList *chatrooms, *l;
+
+ window->chatroom_manager = gossip_chatroom_manager_new ();
+ chatrooms = gossip_chatroom_manager_get_chatrooms (window->chatroom_manager, NULL);
+ window->room_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (window->room));
+
+ for (l = chatrooms; l; l = l->next) {
+ main_window_favorite_chatroom_menu_add (window, l->data);
+ }
+
+ if (!chatrooms) {
+ gtk_widget_hide (window->room_sep);
+ }
+
+ gtk_widget_set_sensitive (window->room_join_favorites, chatrooms != NULL);
+
+ g_signal_connect (window->chatroom_manager, "chatroom-added",
+ G_CALLBACK (main_window_favorite_chatroom_menu_added_cb),
+ window);
+ g_signal_connect (window->chatroom_manager, "chatroom-removed",
+ G_CALLBACK (main_window_favorite_chatroom_menu_removed_cb),
+ window);
+
+ g_list_free (chatrooms);
+}
+
+static void
+main_window_favorite_chatroom_menu_added_cb (GossipChatroomManager *manager,
+ GossipChatroom *chatroom,
+ EmpathyMainWindow *window)
+{
+ main_window_favorite_chatroom_menu_add (window, chatroom);
+ gtk_widget_show (window->room_sep);
+ gtk_widget_set_sensitive (window->room_join_favorites, TRUE);
+}
+
+static void
+main_window_favorite_chatroom_menu_removed_cb (GossipChatroomManager *manager,
+ GossipChatroom *chatroom,
+ EmpathyMainWindow *window)
+{
+ GtkWidget *menu_item;
+
+ menu_item = g_object_get_data (G_OBJECT (chatroom), "menu_item");
+
+ g_object_set_data (G_OBJECT (chatroom), "menu_item", NULL);
+ gtk_widget_destroy (menu_item);
+
+ main_window_favorite_chatroom_menu_update (window);
+}
+
+static void
+main_window_favorite_chatroom_menu_activate_cb (GtkMenuItem *menu_item,
+ GossipChatroom *chatroom)
+{
+ main_window_favorite_chatroom_join (chatroom);
+}
+
+static void
+main_window_favorite_chatroom_menu_update (EmpathyMainWindow *window)
+{
+ GList *chatrooms;
+
+ chatrooms = gossip_chatroom_manager_get_chatrooms (window->chatroom_manager, NULL);
+
+ if (chatrooms) {
+ gtk_widget_show (window->room_sep);
+ } else {
+ gtk_widget_hide (window->room_sep);
+ }
+
+ gtk_widget_set_sensitive (window->room_join_favorites, chatrooms != NULL);
+ g_list_free (chatrooms);
+}
+
+static void
+main_window_favorite_chatroom_menu_add (EmpathyMainWindow *window,
+ GossipChatroom *chatroom)
+{
+ GtkWidget *menu_item;
+ const gchar *name;
+
+ if (g_object_get_data (G_OBJECT (chatroom), "menu_item")) {
+ return;
+ }
+
+ name = gossip_chatroom_get_name (chatroom);
+ menu_item = gtk_menu_item_new_with_label (name);
+
+ g_object_set_data (G_OBJECT (chatroom), "menu_item", menu_item);
+ g_signal_connect (menu_item, "activate",
+ G_CALLBACK (main_window_favorite_chatroom_menu_activate_cb),
+ chatroom);
+
+ gtk_menu_shell_insert (GTK_MENU_SHELL (window->room_menu),
+ menu_item, 3);
+
+ gtk_widget_show (menu_item);
+}
+
+static void
+main_window_favorite_chatroom_join (GossipChatroom *chatroom)
+{
+ MissionControl *mc;
+ McAccount *account;
+ const gchar *room;
+
+ mc = gossip_mission_control_new ();
+ account = gossip_chatroom_get_account (chatroom);
+ room = gossip_chatroom_get_room (chatroom);
+
+ gossip_debug (DEBUG_DOMAIN, "Requesting channel for '%s'", room);
+
+ mission_control_request_channel_with_string_handle (mc,
+ account,
+ TP_IFACE_CHANNEL_TYPE_TEXT,
+ room,
+ TP_HANDLE_TYPE_ROOM,
+ NULL, NULL);
+ g_object_unref (mc);
+}
+
+static void
+main_window_chat_quit_cb (GtkWidget *widget,
+ EmpathyMainWindow *window)
+{
+ gtk_main_quit ();
+}
+
+static void
+main_window_chat_new_message_cb (GtkWidget *widget,
+ EmpathyMainWindow *window)
+{
+ //gossip_new_message_dialog_show (GTK_WINDOW (window->window));
+}
+
+static void
+main_window_chat_history_cb (GtkWidget *widget,
+ EmpathyMainWindow *window)
+{
+ //gossip_log_window_show (NULL, NULL);
+}
+
+static void
+main_window_room_join_new_cb (GtkWidget *widget,
+ EmpathyMainWindow *window)
+{
+ gossip_new_chatroom_dialog_show (GTK_WINDOW (window->window));
+}
+
+static void
+main_window_room_join_favorites_cb (GtkWidget *widget,
+ EmpathyMainWindow *window)
+{
+ GList *chatrooms, *l;
+
+ chatrooms = gossip_chatroom_manager_get_chatrooms (window->chatroom_manager, NULL);
+ for (l = chatrooms; l; l = l->next) {
+ main_window_favorite_chatroom_join (l->data);
+ }
+ g_list_free (chatrooms);
+}
+
+static void
+main_window_room_manage_favorites_cb (GtkWidget *widget,
+ EmpathyMainWindow *window)
+{
+ gossip_chatrooms_window_show (GTK_WINDOW (window->window));
+}
+
+static void
+main_window_chat_add_contact_cb (GtkWidget *widget,
+ EmpathyMainWindow *window)
+{
+ //gossip_add_contact_dialog_show (GTK_WINDOW (window->window), NULL);
+}
+
+static void
+main_window_chat_show_offline_cb (GtkCheckMenuItem *item,
+ EmpathyMainWindow *window)
+{
+ gboolean current;
+
+ current = gtk_check_menu_item_get_active (item);
+
+ gossip_conf_set_bool (gossip_conf_get (),
+ GOSSIP_PREFS_CONTACTS_SHOW_OFFLINE,
+ current);
+
+ /* Turn off sound just while we alter the contact list. */
+ // FIXME: gossip_sound_set_enabled (FALSE);
+ gossip_contact_list_store_set_show_offline (window->list_store, current);
+ //gossip_sound_set_enabled (TRUE);
+}
+
+static gboolean
+main_window_edit_button_press_event_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ EmpathyMainWindow *window)
+{
+ GossipContact *contact;
+ gchar *group;
+
+ if (!event->button == 1) {
+ return FALSE;
+ }
+
+ group = gossip_contact_list_view_get_selected_group (window->list_view);
+ if (group) {
+ GtkMenuItem *item;
+ GtkWidget *label;
+ GtkWidget *submenu;
+
+ item = GTK_MENU_ITEM (window->edit_context);
+ label = gtk_bin_get_child (GTK_BIN (item));
+ gtk_label_set_text (GTK_LABEL (label), _("Group"));
+
+ gtk_widget_show (window->edit_context);
+ gtk_widget_show (window->edit_context_separator);
+
+ submenu = gossip_contact_list_view_get_group_menu (window->list_view);
+ gtk_menu_item_set_submenu (item, submenu);
+
+ g_free (group);
+
+ return FALSE;
+ }
+
+ contact = gossip_contact_list_view_get_selected (window->list_view);
+ if (contact) {
+ GtkMenuItem *item;
+ GtkWidget *label;
+ GtkWidget *submenu;
+
+ item = GTK_MENU_ITEM (window->edit_context);
+ label = gtk_bin_get_child (GTK_BIN (item));
+ gtk_label_set_text (GTK_LABEL (label), _("Contact"));
+
+ gtk_widget_show (window->edit_context);
+ gtk_widget_show (window->edit_context_separator);
+
+ submenu = gossip_contact_list_view_get_contact_menu (window->list_view,
+ contact);
+ gtk_menu_item_set_submenu (item, submenu);
+
+ g_object_unref (contact);
+
+ return FALSE;
+ }
+
+ gtk_widget_hide (window->edit_context);
+ gtk_widget_hide (window->edit_context_separator);
+
+ return FALSE;
+}
+
+static void
+main_window_edit_accounts_cb (GtkWidget *widget,
+ EmpathyMainWindow *window)
+{
+ gossip_accounts_dialog_show (GTK_WINDOW (window->window));
+}
+
+static void
+main_window_edit_personal_information_cb (GtkWidget *widget,
+ EmpathyMainWindow *window)
+{
+ //gossip_vcard_dialog_show (GTK_WINDOW (window->window));
+}
+
+static void
+main_window_edit_preferences_cb (GtkWidget *widget,
+ EmpathyMainWindow *window)
+{
+ gossip_preferences_show (GTK_WINDOW (window->window));
+}
+
+static void
+main_window_help_about_cb (GtkWidget *widget,
+ EmpathyMainWindow *window)
+{
+ gossip_about_dialog_new (GTK_WINDOW (window->window));
+}
+
+static void
+main_window_help_contents_cb (GtkWidget *widget,
+ EmpathyMainWindow *window)
+{
+ //gossip_help_show ();
+}
+
+static gboolean
+main_window_throbber_button_press_event_cb (GtkWidget *throbber_ebox,
+ GdkEventButton *event,
+ EmpathyMainWindow *window)
+{
+ if (event->type != GDK_BUTTON_PRESS ||
+ event->button != 1) {
+ return FALSE;
+ }
+
+ gossip_accounts_dialog_show (GTK_WINDOW (window->window));
+
+ return FALSE;
+}
+
+static void
+main_window_status_changed_cb (MissionControl *mc,
+ TelepathyConnectionStatus status,
+ McPresence presence,
+ TelepathyConnectionStatusReason reason,
+ const gchar *unique_name,
+ EmpathyMainWindow *window)
+{
+ main_window_update_status (window);
+}
+
+static void
+main_window_update_status (EmpathyMainWindow *window)
+{
+ GList *accounts, *l;
+ guint connected = 0;
+ guint connecting = 0;
+ guint disconnected = 0;
+
+ /* Count number of connected/connecting/disconnected accounts */
+ accounts = mc_accounts_list ();
+ for (l = accounts; l; l = l->next) {
+ McAccount *account;
+ guint status;
+
+ account = l->data;
+
+ status = mission_control_get_connection_status (window->mc,
+ account,
+ NULL);
+
+ if (status == 0) {
+ connected++;
+ } else if (status == 1) {
+ connecting++;
+ } else if (status == 2) {
+ disconnected++;
+ }
+
+ g_object_unref (account);
+ }
+ g_list_free (accounts);
+
+ /* Update the spinner state */
+ if (connecting > 0) {
+ ephy_spinner_start (EPHY_SPINNER (window->throbber));
+ } else {
+ ephy_spinner_stop (EPHY_SPINNER (window->throbber));
+ }
+
+ /* Update widgets sensibility */
+ for (l = window->widgets_connected; l; l = l->next) {
+ gtk_widget_set_sensitive (l->data, (connected > 0));
+ }
+
+ for (l = window->widgets_disconnected; l; l = l->next) {
+ gtk_widget_set_sensitive (l->data, (disconnected > 0));
+ }
+}
+
+/*
+ * Accels
+ */
+static void
+main_window_accels_load (void)
+{
+ gchar *filename;
+
+ filename = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, ACCELS_FILENAME, NULL);
+ if (g_file_test (filename, G_FILE_TEST_EXISTS)) {
+ gossip_debug (DEBUG_DOMAIN, "Loading from:'%s'", filename);
+ gtk_accel_map_load (filename);
+ }
+
+ g_free (filename);
+}
+
+static void
+main_window_accels_save (void)
+{
+ gchar *dir;
+ gchar *file_with_path;
+
+ dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
+ g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
+ file_with_path = g_build_filename (dir, ACCELS_FILENAME, NULL);
+ g_free (dir);
+
+ gossip_debug (DEBUG_DOMAIN, "Saving to:'%s'", file_with_path);
+ gtk_accel_map_save (file_with_path);
+
+ g_free (file_with_path);
+}
+
+static void
+main_window_connection_items_setup (EmpathyMainWindow *window,
+ GladeXML *glade)
+{
+ GList *list;
+ GtkWidget *w;
+ gint i;
+ const gchar *widgets_connected[] = {
+ "room",
+ "chat_new_message",
+ "chat_add_contact",
+ "edit_personal_information"
+ };
+ const gchar *widgets_disconnected[] = {
+ };
+
+ for (i = 0, list = NULL; i < G_N_ELEMENTS (widgets_connected); i++) {
+ w = glade_xml_get_widget (glade, widgets_connected[i]);
+ list = g_list_prepend (list, w);
+ }
+
+ window->widgets_connected = list;
+
+ for (i = 0, list = NULL; i < G_N_ELEMENTS (widgets_disconnected); i++) {
+ w = glade_xml_get_widget (glade, widgets_disconnected[i]);
+ list = g_list_prepend (list, w);
+ }
+
+ window->widgets_disconnected = list;
+}
+
+static gboolean
+main_window_configure_event_timeout_cb (EmpathyMainWindow *window)
+{
+ gint x, y, w, h;
+
+ gtk_window_get_size (GTK_WINDOW (window->window), &w, &h);
+ gtk_window_get_position (GTK_WINDOW (window->window), &x, &y);
+
+ gossip_geometry_save (GEOMETRY_NAME, x, y, w, h);
+
+ window->size_timeout_id = 0;
+
+ return FALSE;
+}
+
+static gboolean
+main_window_configure_event_cb (GtkWidget *widget,
+ GdkEventConfigure *event,
+ EmpathyMainWindow *window)
+{
+ if (window->size_timeout_id) {
+ g_source_remove (window->size_timeout_id);
+ }
+
+ window->size_timeout_id = g_timeout_add (500,
+ (GSourceFunc) main_window_configure_event_timeout_cb,
+ window);
+
+ return FALSE;
+}
+
+static void
+main_window_notify_show_offline_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer check_menu_item)
+{
+ gboolean show_offline;
+
+ if (gossip_conf_get_bool (conf, key, &show_offline)) {
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (check_menu_item),
+ show_offline);
+ }
+}
+
+static void
+main_window_notify_show_avatars_cb (GossipConf *conf,
+ const gchar *key,
+ EmpathyMainWindow *window)
+{
+ gboolean show_avatars;
+
+ if (gossip_conf_get_bool (conf, key, &show_avatars)) {
+ gossip_contact_list_store_set_show_avatars (window->list_store,
+ show_avatars);
+ }
+}
+
+static void
+main_window_notify_compact_contact_list_cb (GossipConf *conf,
+ const gchar *key,
+ EmpathyMainWindow *window)
+{
+ gboolean compact_contact_list;
+
+ if (gossip_conf_get_bool (conf, key, &compact_contact_list)) {
+ gossip_contact_list_store_set_is_compact (window->list_store,
+ compact_contact_list);
+ }
+}
+
+static void
+main_window_notify_sort_criterium_cb (GossipConf *conf,
+ const gchar *key,
+ EmpathyMainWindow *window)
+{
+ gchar *str = NULL;
+
+ if (gossip_conf_get_string (conf, key, &str)) {
+ GType type;
+ GEnumClass *enum_class;
+ GEnumValue *enum_value;
+
+ type = gossip_contact_list_store_sort_get_type ();
+ enum_class = G_ENUM_CLASS (g_type_class_peek (type));
+ enum_value = g_enum_get_value_by_nick (enum_class, str);
+ g_free (str);
+
+ if (enum_value) {
+ gossip_contact_list_store_set_sort_criterium (window->list_store,
+ enum_value->value);
+ }
+ }
+}
+
diff --git a/trunk/libempathy-gtk/empathy-main-window.glade b/trunk/libempathy-gtk/empathy-main-window.glade
new file mode 100644
index 000000000..41e5d61ae
--- /dev/null
+++ b/trunk/libempathy-gtk/empathy-main-window.glade
@@ -0,0 +1,427 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="main_window">
+ <property name="title" translatable="yes">Contact List - Empathy</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">225</property>
+ <property name="default_height">325</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+
+ <child>
+ <widget class="GtkVBox" id="main_vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkMenuBar" id="menubar2">
+ <property name="visible">True</property>
+ <property name="pack_direction">GTK_PACK_DIRECTION_LTR</property>
+ <property name="child_pack_direction">GTK_PACK_DIRECTION_LTR</property>
+
+ <child>
+ <widget class="GtkMenuItem" id="chat">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Chat</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="chat_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="chat_new_message">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_New Message...</property>
+ <property name="use_underline">True</property>
+ <accelerator key="N" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image885">
+ <property name="visible">True</property>
+ <property name="icon_size">1</property>
+ <property name="icon_name">empathy-new-message</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="chat_history">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_View Previous Conversations</property>
+ <property name="use_underline">True</property>
+ <accelerator key="F3" modifiers="0" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image886">
+ <property name="visible">True</property>
+ <property name="stock">gtk-justify-left</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator5">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="chat_add_contact">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Add Contact...</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image887">
+ <property name="visible">True</property>
+ <property name="stock">gtk-add</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator3">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkCheckMenuItem" id="chat_show_offline">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Show _Offline Contacts</property>
+ <property name="use_underline">True</property>
+ <property name="active">False</property>
+ <accelerator key="H" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator6">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="chat_quit">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Quit</property>
+ <property name="use_underline">True</property>
+ <accelerator key="Q" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image888">
+ <property name="visible">True</property>
+ <property name="stock">gtk-quit</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="room">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Room</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="room_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="room_join_new">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Join _New...</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image889">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="room_join_favorites">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Join _Favorites</property>
+ <property name="use_underline">True</property>
+ <accelerator key="F5" modifiers="0" signal="activate"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="room_sep">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="room_sep2">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="room_manage_favorites">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Manage Favorites...</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image890">
+ <property name="visible">True</property>
+ <property name="icon_size">1</property>
+ <property name="icon_name">empathy-group-message</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="edit">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Edit</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="edit_menu">
+
+ <child>
+ <widget class="GtkMenuItem" id="edit_context">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Context</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="edit_context_separator">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="edit_accounts">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Accounts</property>
+ <property name="use_underline">True</property>
+ <accelerator key="F4" modifiers="0" signal="activate"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="edit_personal_information">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Personal Information</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator2">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="edit_preferences">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Preferences</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image891">
+ <property name="visible">True</property>
+ <property name="stock">gtk-preferences</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="help">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Help</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="help_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="help_contents">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Contents</property>
+ <property name="use_underline">True</property>
+ <accelerator key="F1" modifiers="0" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image892">
+ <property name="visible">True</property>
+ <property name="stock">gtk-help</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="help_about">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_About</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image893">
+ <property name="visible">True</property>
+ <property name="stock">gtk-about</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkToolbar" id="presence_toolbar">
+ <property name="visible">True</property>
+ <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
+ <property name="toolbar_style">GTK_TOOLBAR_BOTH</property>
+ <property name="tooltips">True</property>
+ <property name="show_arrow">True</property>
+
+ <child>
+ <placeholder/>
+ </child>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="errors_vbox">
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="roster_scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/trunk/libempathy-gtk/empathy-main-window.h b/trunk/libempathy-gtk/empathy-main-window.h
new file mode 100644
index 000000000..eca78da1e
--- /dev/null
+++ b/trunk/libempathy-gtk/empathy-main-window.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __EMPATHY_MAIN_WINDOW_H__
+#define __EMPATHY_MAIN_WINDOW_H__
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+GtkWidget *empathy_main_window_show (void);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_MAIN_WINDOW_H__ */
diff --git a/trunk/libempathy-gtk/empathy-status-icon.c b/trunk/libempathy-gtk/empathy-status-icon.c
new file mode 100644
index 000000000..01a542493
--- /dev/null
+++ b/trunk/libempathy-gtk/empathy-status-icon.c
@@ -0,0 +1,345 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <libmissioncontrol/mission-control.h>
+
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-utils.h>
+#include <libempathy/gossip-conf.h>
+#include <libempathy/empathy-idle.h>
+
+#include "empathy-status-icon.h"
+#include "gossip-presence-chooser.h"
+#include "gossip-preferences.h"
+#include "gossip-ui-utils.h"
+#include "gossip-accounts-dialog.h"
+
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
+ EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIconPriv))
+
+#define DEBUG_DOMAIN "StatusIcon"
+
+struct _EmpathyStatusIconPriv {
+ MissionControl *mc;
+ GtkStatusIcon *icon;
+ EmpathyIdle *idle;
+
+ GtkWindow *window;
+
+ GtkWidget *popup_menu;
+ GtkWidget *show_window_item;
+ GtkWidget *message_item;
+ GtkWidget *status_item;
+};
+
+static void empathy_status_icon_class_init (EmpathyStatusIconClass *klass);
+static void empathy_status_icon_init (EmpathyStatusIcon *icon);
+static void status_icon_finalize (GObject *object);
+static void status_icon_presence_changed_cb (MissionControl *mc,
+ McPresence state,
+ EmpathyStatusIcon *icon);
+static void status_icon_toggle_visibility (EmpathyStatusIcon *icon);
+static void status_icon_activate_cb (GtkStatusIcon *status_icon,
+ EmpathyStatusIcon *icon);
+static gboolean status_icon_delete_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ EmpathyStatusIcon *icon);
+static void status_icon_popup_menu_cb (GtkStatusIcon *status_icon,
+ guint button,
+ guint activate_time,
+ EmpathyStatusIcon *icon);
+static void status_icon_create_menu (EmpathyStatusIcon *icon);
+static void status_icon_new_message_cb (GtkWidget *widget,
+ EmpathyStatusIcon *icon);
+static void status_icon_quit_cb (GtkWidget *window,
+ EmpathyStatusIcon *icon);
+static void status_icon_show_hide_window_cb (GtkWidget *widget,
+ EmpathyStatusIcon *icon);
+
+G_DEFINE_TYPE (EmpathyStatusIcon, empathy_status_icon, G_TYPE_OBJECT);
+
+static void
+empathy_status_icon_class_init (EmpathyStatusIconClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = status_icon_finalize;
+
+ g_type_class_add_private (object_class, sizeof (EmpathyStatusIconPriv));
+}
+
+static void
+empathy_status_icon_init (EmpathyStatusIcon *icon)
+{
+ EmpathyStatusIconPriv *priv;
+ McPresence state;
+
+ priv = GET_PRIV (icon);
+
+ priv->icon = gtk_status_icon_new ();
+ priv->mc = gossip_mission_control_new ();
+ priv->idle = empathy_idle_new ();
+
+ status_icon_create_menu (icon);
+
+ state = mission_control_get_presence_actual (priv->mc, NULL);
+ status_icon_presence_changed_cb (priv->mc, state, icon);
+
+ dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->mc),
+ "PresenceStatusActual",
+ G_CALLBACK (status_icon_presence_changed_cb),
+ icon, NULL);
+ g_signal_connect (priv->icon, "activate",
+ G_CALLBACK (status_icon_activate_cb),
+ icon);
+ g_signal_connect (priv->icon, "popup-menu",
+ G_CALLBACK (status_icon_popup_menu_cb),
+ icon);
+}
+
+static void
+status_icon_finalize (GObject *object)
+{
+ EmpathyStatusIconPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->mc),
+ "PresenceStatusActual",
+ G_CALLBACK (status_icon_presence_changed_cb),
+ object);
+
+ g_object_unref (priv->mc);
+ g_object_unref (priv->icon);
+ g_object_unref (priv->window);
+ g_object_unref (priv->idle);
+}
+
+EmpathyStatusIcon *
+empathy_status_icon_new (GtkWindow *window)
+{
+ EmpathyStatusIconPriv *priv;
+ EmpathyStatusIcon *icon;
+ gboolean should_hide;
+ gboolean visible;
+
+ g_return_val_if_fail (GTK_IS_WINDOW (window), NULL);
+
+ icon = g_object_new (EMPATHY_TYPE_STATUS_ICON, NULL);
+ priv = GET_PRIV (icon);
+
+ priv->window = g_object_ref (window);
+
+ g_signal_connect (priv->window, "delete-event",
+ G_CALLBACK (status_icon_delete_event_cb),
+ icon);
+
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_UI_MAIN_WINDOW_HIDDEN,
+ &should_hide);
+ visible = gossip_window_get_is_visible (window);
+
+ if ((!should_hide && !visible) || (should_hide && visible)) {
+ status_icon_toggle_visibility (icon);
+ }
+
+ return icon;
+}
+
+static void
+status_icon_presence_changed_cb (MissionControl *mc,
+ McPresence state,
+ EmpathyStatusIcon *icon)
+{
+ EmpathyStatusIconPriv *priv;
+ const gchar *icon_name;
+ gchar *status;
+
+ priv = GET_PRIV (icon);
+
+ icon_name = gossip_icon_name_for_presence_state (state);
+ status = mission_control_get_presence_message_actual (priv->mc, NULL);
+ if (G_STR_EMPTY (status)) {
+ g_free (status);
+ status = g_strdup (gossip_presence_state_get_default_status (state));
+ }
+
+ gtk_status_icon_set_from_icon_name (priv->icon, icon_name);
+ gtk_status_icon_set_tooltip (priv->icon, status);
+
+ g_free (status);
+
+ if (state < MC_PRESENCE_AVAILABLE) {
+ gtk_widget_set_sensitive (priv->message_item, FALSE);
+ } else {
+ gtk_widget_set_sensitive (priv->message_item, TRUE);
+ }
+}
+
+static void
+status_icon_toggle_visibility (EmpathyStatusIcon *icon)
+{
+ EmpathyStatusIconPriv *priv;
+ gboolean visible;
+
+ priv = GET_PRIV (icon);
+
+ visible = gossip_window_get_is_visible (GTK_WINDOW (priv->window));
+
+ if (visible) {
+ gtk_widget_hide (GTK_WIDGET (priv->window));
+ gossip_conf_set_bool (gossip_conf_get (),
+ GOSSIP_PREFS_UI_MAIN_WINDOW_HIDDEN, TRUE);
+ } else {
+ GList *accounts;
+
+ gossip_window_present (GTK_WINDOW (priv->window), TRUE);
+ gossip_conf_set_bool (gossip_conf_get (),
+ GOSSIP_PREFS_UI_MAIN_WINDOW_HIDDEN, FALSE);
+
+ /* Show the accounts dialog if there is no enabled accounts */
+ accounts = mc_accounts_list_by_enabled (TRUE);
+ if (accounts) {
+ mc_accounts_list_free (accounts);
+ } else {
+ gossip_debug (DEBUG_DOMAIN,
+ "No enabled account, Showing account dialog");
+ gossip_accounts_dialog_show (GTK_WINDOW (priv->window));
+ }
+ }
+}
+
+static void
+status_icon_activate_cb (GtkStatusIcon *status_icon,
+ EmpathyStatusIcon *icon)
+{
+ status_icon_toggle_visibility (icon);
+}
+
+static gboolean
+status_icon_delete_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ EmpathyStatusIcon *icon)
+{
+ status_icon_toggle_visibility (icon);
+
+ return TRUE;
+}
+
+static void
+status_icon_popup_menu_cb (GtkStatusIcon *status_icon,
+ guint button,
+ guint activate_time,
+ EmpathyStatusIcon *icon)
+{
+ EmpathyStatusIconPriv *priv;
+ GtkWidget *submenu;
+ gboolean show;
+
+ priv = GET_PRIV (icon);
+
+ show = gossip_window_get_is_visible (GTK_WINDOW (priv->window));
+
+ g_signal_handlers_block_by_func (priv->show_window_item,
+ status_icon_show_hide_window_cb,
+ icon);
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->show_window_item),
+ show);
+ g_signal_handlers_unblock_by_func (priv->show_window_item,
+ status_icon_show_hide_window_cb,
+ icon);
+
+ submenu = gossip_presence_chooser_create_menu ();
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (priv->status_item),
+ submenu);
+
+ gtk_menu_popup (GTK_MENU (priv->popup_menu),
+ NULL, NULL,
+ gtk_status_icon_position_menu,
+ priv->icon,
+ button,
+ activate_time);
+}
+
+static void
+status_icon_create_menu (EmpathyStatusIcon *icon)
+{
+ EmpathyStatusIconPriv *priv;
+ GladeXML *glade;
+
+ priv = GET_PRIV (icon);
+
+ glade = gossip_glade_get_file ("empathy-status-icon.glade",
+ "tray_menu",
+ NULL,
+ "tray_menu", &priv->popup_menu,
+ "tray_show_list", &priv->show_window_item,
+ "tray_new_message", &priv->message_item,
+ "tray_status", &priv->status_item,
+ NULL);
+
+ gossip_glade_connect (glade,
+ icon,
+ "tray_new_message", "activate", status_icon_new_message_cb,
+ "tray_quit", "activate", status_icon_quit_cb,
+ NULL);
+
+ g_signal_connect (priv->show_window_item, "toggled",
+ G_CALLBACK (status_icon_show_hide_window_cb),
+ icon);
+
+ g_object_unref (glade);
+}
+
+static void
+status_icon_new_message_cb (GtkWidget *widget,
+ EmpathyStatusIcon *icon)
+{
+ EmpathyStatusIconPriv *priv;
+
+ priv = GET_PRIV (icon);
+
+ //gossip_new_message_dialog_show (GTK_WINDOW (priv->window));
+}
+
+static void
+status_icon_quit_cb (GtkWidget *window,
+ EmpathyStatusIcon *icon)
+{
+ gtk_main_quit ();
+}
+
+static void
+status_icon_show_hide_window_cb (GtkWidget *widget,
+ EmpathyStatusIcon *icon)
+{
+ status_icon_toggle_visibility (icon);
+}
+
diff --git a/trunk/libempathy-gtk/empathy-status-icon.glade b/trunk/libempathy-gtk/empathy-status-icon.glade
new file mode 100644
index 000000000..d76a5dfda
--- /dev/null
+++ b/trunk/libempathy-gtk/empathy-status-icon.glade
@@ -0,0 +1,78 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkMenu" id="tray_menu">
+
+ <child>
+ <widget class="GtkCheckMenuItem" id="tray_show_list">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Show Contact List</property>
+ <property name="use_underline">True</property>
+ <property name="active">False</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="avskiljare5">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="tray_new_message">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_New Message...</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image599">
+ <property name="visible">True</property>
+ <property name="icon_size">1</property>
+ <property name="icon_name">empathy-new-message</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="tray_status">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Status</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="avskiljare6">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="tray_quit">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Quit</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image600">
+ <property name="visible">True</property>
+ <property name="stock">gtk-quit</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/trunk/libempathy-gtk/empathy-status-icon.h b/trunk/libempathy-gtk/empathy-status-icon.h
new file mode 100644
index 000000000..6cfa8899f
--- /dev/null
+++ b/trunk/libempathy-gtk/empathy-status-icon.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __EMPATHY_STATUS_ICON_H__
+#define __EMPATHY_STATUS_ICON_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_STATUS_ICON (empathy_status_icon_get_type ())
+#define EMPATHY_STATUS_ICON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIcon))
+#define EMPATHY_STATUS_ICON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIconClass))
+#define EMPATHY_IS_STATUS_ICON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_STATUS_ICON))
+#define EMPATHY_IS_STATUS_ICON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_STATUS_ICON))
+#define EMPATHY_STATUS_ICON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIconClass))
+
+typedef struct _EmpathyStatusIcon EmpathyStatusIcon;
+typedef struct _EmpathyStatusIconClass EmpathyStatusIconClass;
+typedef struct _EmpathyStatusIconPriv EmpathyStatusIconPriv;
+
+struct _EmpathyStatusIcon {
+ GObject parent;
+};
+
+struct _EmpathyStatusIconClass {
+ GObjectClass parent_class;
+};
+
+GType empathy_status_icon_get_type (void) G_GNUC_CONST;
+EmpathyStatusIcon *empathy_status_icon_new (GtkWindow *window);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_STATUS_ICON_H__ */
diff --git a/trunk/libempathy-gtk/ephy-spinner.c b/trunk/libempathy-gtk/ephy-spinner.c
new file mode 100644
index 000000000..a8f371df3
--- /dev/null
+++ b/trunk/libempathy-gtk/ephy-spinner.c
@@ -0,0 +1,977 @@
+/*
+ * Copyright © 2000 Eazel, Inc.
+ * Copyright © 2002-2004 Marco Pesenti Gritti
+ * Copyright © 2004, 2006 Christian Persch
+ *
+ * Nautilus is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Andy Hertzfeld <andy@eazel.com>
+ *
+ * Ephy port by Marco Pesenti Gritti <marco@it.gnome.org>
+ *
+ * $Id: ephy-spinner.c 2114 2006-12-25 12:15:00Z mr $
+ */
+
+#include "config.h"
+
+#include "ephy-spinner.h"
+
+/* #include "ephy-debug.h" */
+#define LOG(msg, args...)
+#define START_PROFILER(name)
+#define STOP_PROFILER(name)
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtkicontheme.h>
+#include <gtk/gtkiconfactory.h>
+#include <gtk/gtksettings.h>
+
+/* Spinner cache implementation */
+
+#define EPHY_TYPE_SPINNER_CACHE (ephy_spinner_cache_get_type())
+#define EPHY_SPINNER_CACHE(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_SPINNER_CACHE, EphySpinnerCache))
+#define EPHY_SPINNER_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_SPINNER_CACHE, EphySpinnerCacheClass))
+#define EPHY_IS_SPINNER_CACHE(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_SPINNER_CACHE))
+#define EPHY_IS_SPINNER_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_SPINNER_CACHE))
+#define EPHY_SPINNER_CACHE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_SPINNER_CACHE, EphySpinnerCacheClass))
+
+typedef struct _EphySpinnerCache EphySpinnerCache;
+typedef struct _EphySpinnerCacheClass EphySpinnerCacheClass;
+typedef struct _EphySpinnerCachePrivate EphySpinnerCachePrivate;
+
+struct _EphySpinnerCacheClass
+{
+ GObjectClass parent_class;
+};
+
+struct _EphySpinnerCache
+{
+ GObject parent_object;
+
+ /*< private >*/
+ EphySpinnerCachePrivate *priv;
+};
+
+#define EPHY_SPINNER_CACHE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_SPINNER_CACHE, EphySpinnerCachePrivate))
+
+struct _EphySpinnerCachePrivate
+{
+ /* Hash table of GdkScreen -> EphySpinnerCacheData */
+ GHashTable *hash;
+};
+
+typedef struct
+{
+ guint ref_count;
+ GtkIconSize size;
+ int width;
+ int height;
+ GdkPixbuf **animation_pixbufs;
+ guint n_animation_pixbufs;
+} EphySpinnerImages;
+
+#define LAST_ICON_SIZE GTK_ICON_SIZE_DIALOG + 1
+#define SPINNER_ICON_NAME "process-working"
+#define SPINNER_FALLBACK_ICON_NAME "gnome-spinner"
+#define EPHY_SPINNER_IMAGES_INVALID ((EphySpinnerImages *) 0x1)
+
+typedef struct
+{
+ GdkScreen *screen;
+ GtkIconTheme *icon_theme;
+ EphySpinnerImages *images[LAST_ICON_SIZE];
+} EphySpinnerCacheData;
+
+static void ephy_spinner_cache_class_init (EphySpinnerCacheClass *klass);
+static void ephy_spinner_cache_init (EphySpinnerCache *cache);
+
+static GObjectClass *ephy_spinner_cache_parent_class;
+
+static GType
+ephy_spinner_cache_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0))
+ {
+ const GTypeInfo our_info =
+ {
+ sizeof (EphySpinnerCacheClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) ephy_spinner_cache_class_init,
+ NULL,
+ NULL,
+ sizeof (EphySpinnerCache),
+ 0,
+ (GInstanceInitFunc) ephy_spinner_cache_init
+ };
+
+ type = g_type_register_static (G_TYPE_OBJECT,
+ "EphySpinnerCache",
+ &our_info, 0);
+ }
+
+ return type;
+}
+
+static EphySpinnerImages *
+ephy_spinner_images_ref (EphySpinnerImages *images)
+{
+ g_return_val_if_fail (images != NULL, NULL);
+
+ images->ref_count++;
+
+ return images;
+}
+
+static void
+ephy_spinner_images_unref (EphySpinnerImages *images)
+{
+ g_return_if_fail (images != NULL);
+
+ images->ref_count--;
+ if (images->ref_count == 0)
+ {
+ guint i;
+
+ LOG ("Freeing spinner images %p for size %d", images, images->size);
+
+ for (i = 0; i < images->n_animation_pixbufs; ++i)
+ {
+ g_object_unref (images->animation_pixbufs[i]);
+ }
+ g_free (images->animation_pixbufs);
+
+ g_free (images);
+ }
+}
+
+static void
+ephy_spinner_cache_data_unload (EphySpinnerCacheData *data)
+{
+ GtkIconSize size;
+ EphySpinnerImages *images;
+
+ g_return_if_fail (data != NULL);
+
+ LOG ("EphySpinnerDataCache unload for screen %p", data->screen);
+
+ for (size = GTK_ICON_SIZE_INVALID; size < LAST_ICON_SIZE; ++size)
+ {
+ images = data->images[size];
+ data->images[size] = NULL;
+
+ if (images != NULL && images != EPHY_SPINNER_IMAGES_INVALID)
+ {
+ ephy_spinner_images_unref (images);
+ }
+ }
+}
+
+static GdkPixbuf *
+extract_frame (GdkPixbuf *grid_pixbuf,
+ int x,
+ int y,
+ int size)
+{
+ GdkPixbuf *pixbuf;
+
+ if (x + size > gdk_pixbuf_get_width (grid_pixbuf) ||
+ y + size > gdk_pixbuf_get_height (grid_pixbuf))
+ {
+ return NULL;
+ }
+
+ pixbuf = gdk_pixbuf_new_subpixbuf (grid_pixbuf,
+ x, y,
+ size, size);
+ g_return_val_if_fail (pixbuf != NULL, NULL);
+
+ return pixbuf;
+}
+
+static GdkPixbuf *
+scale_to_size (GdkPixbuf *pixbuf,
+ int dw,
+ int dh)
+{
+ GdkPixbuf *result;
+ int pw, ph;
+
+ g_return_val_if_fail (pixbuf != NULL, NULL);
+
+ pw = gdk_pixbuf_get_width (pixbuf);
+ ph = gdk_pixbuf_get_height (pixbuf);
+
+ if (pw != dw || ph != dh)
+ {
+ result = gdk_pixbuf_scale_simple (pixbuf, dw, dh,
+ GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ return result;
+ }
+
+ return pixbuf;
+}
+
+static EphySpinnerImages *
+ephy_spinner_images_load (GdkScreen *screen,
+ GtkIconTheme *icon_theme,
+ GtkIconSize icon_size)
+{
+ EphySpinnerImages *images;
+ GdkPixbuf *icon_pixbuf, *pixbuf;
+ GtkIconInfo *icon_info = NULL;
+ int grid_width, grid_height, x, y, requested_size, size, isw, ish, n;
+ const char *icon;
+ GSList *list = NULL, *l;
+
+ LOG ("EphySpinnerCacheData loading for screen %p at size %d", screen, icon_size);
+
+ START_PROFILER ("loading spinner animation")
+
+ if (!gtk_icon_size_lookup_for_settings (gtk_settings_get_for_screen (screen),
+ icon_size, &isw, &ish)) goto loser;
+
+ requested_size = MAX (ish, isw);
+
+ /* Load the animation. The 'rest icon' is the 0th frame */
+ icon_info = gtk_icon_theme_lookup_icon (icon_theme,
+ SPINNER_ICON_NAME,
+ requested_size, 0);
+ if (icon_info == NULL)
+ {
+ g_warning ("Throbber animation not found");
+
+ /* If the icon naming spec compliant name wasn't found, try the old name */
+ icon_info = gtk_icon_theme_lookup_icon (icon_theme,
+ SPINNER_FALLBACK_ICON_NAME,
+ requested_size, 0);
+ if (icon_info == NULL)
+ {
+ g_warning ("Throbber fallback animation not found either");
+ goto loser;
+ }
+ }
+ g_assert (icon_info != NULL);
+
+ size = gtk_icon_info_get_base_size (icon_info);
+ icon = gtk_icon_info_get_filename (icon_info);
+ if (icon == NULL) goto loser;
+
+ icon_pixbuf = gdk_pixbuf_new_from_file (icon, NULL);
+ gtk_icon_info_free (icon_info);
+ icon_info = NULL;
+
+ if (icon_pixbuf == NULL)
+ {
+ g_warning ("Could not load the spinner file");
+ goto loser;
+ }
+
+ grid_width = gdk_pixbuf_get_width (icon_pixbuf);
+ grid_height = gdk_pixbuf_get_height (icon_pixbuf);
+
+ n = 0;
+ for (y = 0; y < grid_height; y += size)
+ {
+ for (x = 0; x < grid_width ; x += size)
+ {
+ pixbuf = extract_frame (icon_pixbuf, x, y, size);
+
+ if (pixbuf)
+ {
+ list = g_slist_prepend (list, pixbuf);
+ ++n;
+ }
+ else
+ {
+ g_warning ("Cannot extract frame (%d, %d) from the grid\n", x, y);
+ }
+ }
+ }
+
+ g_object_unref (icon_pixbuf);
+
+ if (list == NULL) goto loser;
+ g_assert (n > 0);
+
+ if (size > requested_size)
+ {
+ for (l = list; l != NULL; l = l->next)
+ {
+ l->data = scale_to_size (l->data, isw, ish);
+ }
+ }
+
+ /* Now we've successfully got all the data */
+ images = g_new (EphySpinnerImages, 1);
+ images->ref_count = 1;
+
+ images->size = icon_size;
+ images->width = images->height = requested_size;
+
+ images->n_animation_pixbufs = n;
+ images->animation_pixbufs = g_new (GdkPixbuf *, n);
+
+ for (l = list; l != NULL; l = l->next)
+ {
+ g_assert (l->data != NULL);
+ images->animation_pixbufs[--n] = l->data;
+ }
+ g_assert (n == 0);
+
+ g_slist_free (list);
+
+ STOP_PROFILER ("loading spinner animation")
+
+ return images;
+
+loser:
+ if (icon_info)
+ {
+ gtk_icon_info_free (icon_info);
+ }
+ g_slist_foreach (list, (GFunc) g_object_unref, NULL);
+
+ STOP_PROFILER ("loading spinner animation")
+
+ return NULL;
+}
+
+static EphySpinnerCacheData *
+ephy_spinner_cache_data_new (GdkScreen *screen)
+{
+ EphySpinnerCacheData *data;
+
+ data = g_new0 (EphySpinnerCacheData, 1);
+
+ data->screen = screen;
+ data->icon_theme = gtk_icon_theme_get_for_screen (screen);
+ g_signal_connect_swapped (data->icon_theme, "changed",
+ G_CALLBACK (ephy_spinner_cache_data_unload),
+ data);
+
+ return data;
+}
+
+static void
+ephy_spinner_cache_data_free (EphySpinnerCacheData *data)
+{
+ g_return_if_fail (data != NULL);
+ g_return_if_fail (data->icon_theme != NULL);
+
+ g_signal_handlers_disconnect_by_func
+ (data->icon_theme,
+ G_CALLBACK (ephy_spinner_cache_data_unload), data);
+
+ ephy_spinner_cache_data_unload (data);
+
+ g_free (data);
+}
+
+static EphySpinnerImages *
+ephy_spinner_cache_get_images (EphySpinnerCache *cache,
+ GdkScreen *screen,
+ GtkIconSize icon_size)
+{
+ EphySpinnerCachePrivate *priv = cache->priv;
+ EphySpinnerCacheData *data;
+ EphySpinnerImages *images;
+
+ LOG ("Getting animation images for screen %p at size %d", screen, icon_size);
+
+ g_return_val_if_fail (icon_size >= 0 && icon_size < LAST_ICON_SIZE, NULL);
+
+ /* Backward compat: "invalid" meant "native" size which doesn't exist anymore */
+ if (icon_size == GTK_ICON_SIZE_INVALID)
+ {
+ icon_size = GTK_ICON_SIZE_DIALOG;
+ }
+
+ data = g_hash_table_lookup (priv->hash, screen);
+ if (data == NULL)
+ {
+ data = ephy_spinner_cache_data_new (screen);
+ /* FIXME: think about what happens when the screen's display is closed later on */
+ g_hash_table_insert (priv->hash, screen, data);
+ }
+
+ images = data->images[icon_size];
+ if (images == EPHY_SPINNER_IMAGES_INVALID)
+ {
+ /* Load failed, but don't try endlessly again! */
+ return NULL;
+ }
+
+ if (images != NULL)
+ {
+ /* Return cached data */
+ return ephy_spinner_images_ref (images);
+ }
+
+ images = ephy_spinner_images_load (screen, data->icon_theme, icon_size);
+
+ if (images == NULL)
+ {
+ /* Mark as failed-to-load */
+ data->images[icon_size] = EPHY_SPINNER_IMAGES_INVALID;
+
+ return NULL;
+ }
+
+ data->images[icon_size] = images;
+
+ return ephy_spinner_images_ref (images);
+}
+
+static void
+ephy_spinner_cache_init (EphySpinnerCache *cache)
+{
+ EphySpinnerCachePrivate *priv;
+
+ priv = cache->priv = EPHY_SPINNER_CACHE_GET_PRIVATE (cache);
+
+ LOG ("EphySpinnerCache initialising");
+
+ priv->hash = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+ NULL,
+ (GDestroyNotify) ephy_spinner_cache_data_free);
+}
+
+static void
+ephy_spinner_cache_finalize (GObject *object)
+{
+ EphySpinnerCache *cache = EPHY_SPINNER_CACHE (object);
+ EphySpinnerCachePrivate *priv = cache->priv;
+
+ g_hash_table_destroy (priv->hash);
+
+ LOG ("EphySpinnerCache finalised");
+
+ G_OBJECT_CLASS (ephy_spinner_cache_parent_class)->finalize (object);
+}
+
+static void
+ephy_spinner_cache_class_init (EphySpinnerCacheClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ ephy_spinner_cache_parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = ephy_spinner_cache_finalize;
+
+ g_type_class_add_private (object_class, sizeof (EphySpinnerCachePrivate));
+}
+
+static EphySpinnerCache *spinner_cache = NULL;
+
+static EphySpinnerCache *
+ephy_spinner_cache_ref (void)
+{
+ if (spinner_cache == NULL)
+ {
+ EphySpinnerCache **cache_ptr;
+
+ spinner_cache = g_object_new (EPHY_TYPE_SPINNER_CACHE, NULL);
+ cache_ptr = &spinner_cache;
+ g_object_add_weak_pointer (G_OBJECT (spinner_cache),
+ (gpointer *) cache_ptr);
+
+ return spinner_cache;
+ }
+
+ return g_object_ref (spinner_cache);
+}
+
+/* Spinner implementation */
+
+#define SPINNER_TIMEOUT 125 /* ms */
+
+#define EPHY_SPINNER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_SPINNER, EphySpinnerDetails))
+
+struct _EphySpinnerDetails
+{
+ GtkIconTheme *icon_theme;
+ EphySpinnerCache *cache;
+ GtkIconSize size;
+ EphySpinnerImages *images;
+ guint current_image;
+ guint timeout;
+ guint timer_task;
+ guint spinning : 1;
+ guint need_load : 1;
+};
+
+static void ephy_spinner_class_init (EphySpinnerClass *class);
+static void ephy_spinner_init (EphySpinner *spinner);
+
+static GObjectClass *parent_class;
+
+GType
+ephy_spinner_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0))
+ {
+ const GTypeInfo our_info =
+ {
+ sizeof (EphySpinnerClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) ephy_spinner_class_init,
+ NULL,
+ NULL, /* class_data */
+ sizeof (EphySpinner),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) ephy_spinner_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_WIDGET,
+ "EphySpinner",
+ &our_info, 0);
+ }
+
+ return type;
+}
+
+static gboolean
+ephy_spinner_load_images (EphySpinner *spinner)
+{
+ EphySpinnerDetails *details = spinner->details;
+
+ if (details->need_load)
+ {
+ START_PROFILER ("ephy_spinner_load_images")
+
+ details->images =
+ ephy_spinner_cache_get_images
+ (details->cache,
+ gtk_widget_get_screen (GTK_WIDGET (spinner)),
+ details->size);
+
+ STOP_PROFILER ("ephy_spinner_load_images")
+
+ details->current_image = 0; /* 'rest' icon */
+ details->need_load = FALSE;
+ }
+
+ return details->images != NULL;
+}
+
+static void
+ephy_spinner_unload_images (EphySpinner *spinner)
+{
+ EphySpinnerDetails *details = spinner->details;
+
+ if (details->images != NULL)
+ {
+ ephy_spinner_images_unref (details->images);
+ details->images = NULL;
+ }
+
+ details->current_image = 0;
+ details->need_load = TRUE;
+}
+
+static void
+icon_theme_changed_cb (GtkIconTheme *icon_theme,
+ EphySpinner *spinner)
+{
+ ephy_spinner_unload_images (spinner);
+ gtk_widget_queue_resize (GTK_WIDGET (spinner));
+}
+
+static void
+ephy_spinner_init (EphySpinner *spinner)
+{
+ EphySpinnerDetails *details;
+
+ details = spinner->details = EPHY_SPINNER_GET_PRIVATE (spinner);
+
+ GTK_WIDGET_SET_FLAGS (GTK_WIDGET (spinner), GTK_NO_WINDOW);
+
+ details->cache = ephy_spinner_cache_ref ();
+ details->size = GTK_ICON_SIZE_DIALOG;
+ details->spinning = FALSE;
+ details->timeout = SPINNER_TIMEOUT;
+ details->need_load = TRUE;
+}
+
+static int
+ephy_spinner_expose (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ EphySpinner *spinner = EPHY_SPINNER (widget);
+ EphySpinnerDetails *details = spinner->details;
+ EphySpinnerImages *images;
+ GdkPixbuf *pixbuf;
+ GdkGC *gc;
+ int x_offset, y_offset, width, height;
+ GdkRectangle pix_area, dest;
+
+ if (!GTK_WIDGET_DRAWABLE (spinner))
+ {
+ return FALSE;
+ }
+
+ if (details->need_load &&
+ !ephy_spinner_load_images (spinner))
+ {
+ return FALSE;
+ }
+
+ images = details->images;
+ if (images == NULL)
+ {
+ return FALSE;
+ }
+
+ /* Otherwise |images| will be NULL anyway */
+ g_assert (images->n_animation_pixbufs > 0);
+
+ g_assert (details->current_image >= 0 &&
+ details->current_image < images->n_animation_pixbufs);
+
+ pixbuf = images->animation_pixbufs[details->current_image];
+
+ g_assert (pixbuf != NULL);
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+
+ /* Compute the offsets for the image centered on our allocation */
+ x_offset = (widget->allocation.width - width) / 2;
+ y_offset = (widget->allocation.height - height) / 2;
+
+ pix_area.x = x_offset + widget->allocation.x;
+ pix_area.y = y_offset + widget->allocation.y;
+ pix_area.width = width;
+ pix_area.height = height;
+
+ if (!gdk_rectangle_intersect (&event->area, &pix_area, &dest))
+ {
+ return FALSE;
+ }
+
+ gc = gdk_gc_new (widget->window);
+ gdk_draw_pixbuf (widget->window, gc, pixbuf,
+ dest.x - x_offset - widget->allocation.x,
+ dest.y - y_offset - widget->allocation.y,
+ dest.x, dest.y,
+ dest.width, dest.height,
+ GDK_RGB_DITHER_MAX, 0, 0);
+ g_object_unref (gc);
+
+ return FALSE;
+}
+
+static gboolean
+bump_spinner_frame_cb (EphySpinner *spinner)
+{
+ EphySpinnerDetails *details = spinner->details;
+
+ /* This can happen when we've unloaded the images on a theme
+ * change, but haven't been in the queued size request yet.
+ * Just skip this update.
+ */
+ if (details->images == NULL) return TRUE;
+
+ details->current_image++;
+ if (details->current_image >= details->images->n_animation_pixbufs)
+ {
+ /* the 0th frame is the 'rest' icon */
+ details->current_image = MIN (1, details->images->n_animation_pixbufs);
+ }
+
+ gtk_widget_queue_draw (GTK_WIDGET (spinner));
+
+ /* run again */
+ return TRUE;
+}
+
+/**
+ * ephy_spinner_start:
+ * @spinner: a #EphySpinner
+ *
+ * Start the spinner animation.
+ **/
+void
+ephy_spinner_start (EphySpinner *spinner)
+{
+ EphySpinnerDetails *details = spinner->details;
+
+ details->spinning = TRUE;
+
+ if (GTK_WIDGET_MAPPED (GTK_WIDGET (spinner)) &&
+ details->timer_task == 0 &&
+ ephy_spinner_load_images (spinner))
+ {
+ /* the 0th frame is the 'rest' icon */
+ details->current_image = MIN (1, details->images->n_animation_pixbufs);
+
+ details->timer_task =
+ g_timeout_add_full (G_PRIORITY_LOW,
+ details->timeout,
+ (GSourceFunc) bump_spinner_frame_cb,
+ spinner,
+ NULL);
+ }
+}
+
+static void
+ephy_spinner_remove_update_callback (EphySpinner *spinner)
+{
+ EphySpinnerDetails *details = spinner->details;
+
+ if (details->timer_task != 0)
+ {
+ g_source_remove (details->timer_task);
+ details->timer_task = 0;
+ }
+}
+
+/**
+ * ephy_spinner_stop:
+ * @spinner: a #EphySpinner
+ *
+ * Stop the spinner animation.
+ **/
+void
+ephy_spinner_stop (EphySpinner *spinner)
+{
+ EphySpinnerDetails *details = spinner->details;
+
+ details->spinning = FALSE;
+ details->current_image = 0;
+
+ if (details->timer_task != 0)
+ {
+ ephy_spinner_remove_update_callback (spinner);
+
+ if (GTK_WIDGET_MAPPED (GTK_WIDGET (spinner)))
+ {
+ gtk_widget_queue_draw (GTK_WIDGET (spinner));
+ }
+ }
+}
+
+/*
+ * ephy_spinner_set_size:
+ * @spinner: a #EphySpinner
+ * @size: the size of type %GtkIconSize
+ *
+ * Set the size of the spinner.
+ **/
+void
+ephy_spinner_set_size (EphySpinner *spinner,
+ GtkIconSize size)
+{
+ if (size == GTK_ICON_SIZE_INVALID)
+ {
+ size = GTK_ICON_SIZE_DIALOG;
+ }
+
+ if (size != spinner->details->size)
+ {
+ ephy_spinner_unload_images (spinner);
+
+ spinner->details->size = size;
+
+ gtk_widget_queue_resize (GTK_WIDGET (spinner));
+ }
+}
+
+#if 0
+/*
+ * ephy_spinner_set_timeout:
+ * @spinner: a #EphySpinner
+ * @timeout: time delay between updates to the spinner.
+ *
+ * Sets the timeout delay for spinner updates.
+ **/
+void
+ephy_spinner_set_timeout (EphySpinner *spinner,
+ guint timeout)
+{
+ EphySpinnerDetails *details = spinner->details;
+
+ if (timeout != details->timeout)
+ {
+ ephy_spinner_stop (spinner);
+
+ details->timeout = timeout;
+
+ if (details->spinning)
+ {
+ ephy_spinner_start (spinner);
+ }
+ }
+}
+#endif
+
+static void
+ephy_spinner_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ EphySpinner *spinner = EPHY_SPINNER (widget);
+ EphySpinnerDetails *details = spinner->details;
+
+ if ((details->need_load &&
+ !ephy_spinner_load_images (spinner)) ||
+ details->images == NULL)
+ {
+ requisition->width = requisition->height = 0;
+ gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (widget),
+ details->size,
+ &requisition->width,
+ &requisition->height);
+ return;
+ }
+
+ requisition->width = details->images->width;
+ requisition->height = details->images->height;
+
+ /* FIXME fix this hack */
+ /* allocate some extra margin so we don't butt up against toolbar edges */
+ if (details->size != GTK_ICON_SIZE_MENU)
+ {
+ requisition->width += 2;
+ requisition->height += 2;
+ }
+}
+
+static void
+ephy_spinner_map (GtkWidget *widget)
+{
+ EphySpinner *spinner = EPHY_SPINNER (widget);
+ EphySpinnerDetails *details = spinner->details;
+
+ GTK_WIDGET_CLASS (parent_class)->map (widget);
+
+ if (details->spinning)
+ {
+ ephy_spinner_start (spinner);
+ }
+}
+
+static void
+ephy_spinner_unmap (GtkWidget *widget)
+{
+ EphySpinner *spinner = EPHY_SPINNER (widget);
+
+ ephy_spinner_remove_update_callback (spinner);
+
+ GTK_WIDGET_CLASS (parent_class)->unmap (widget);
+}
+
+static void
+ephy_spinner_dispose (GObject *object)
+{
+ EphySpinner *spinner = EPHY_SPINNER (object);
+
+ g_signal_handlers_disconnect_by_func
+ (spinner->details->icon_theme,
+ G_CALLBACK (icon_theme_changed_cb), spinner);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+ephy_spinner_finalize (GObject *object)
+{
+ EphySpinner *spinner = EPHY_SPINNER (object);
+
+ ephy_spinner_remove_update_callback (spinner);
+ ephy_spinner_unload_images (spinner);
+
+ g_object_unref (spinner->details->cache);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+ephy_spinner_screen_changed (GtkWidget *widget,
+ GdkScreen *old_screen)
+{
+ EphySpinner *spinner = EPHY_SPINNER (widget);
+ EphySpinnerDetails *details = spinner->details;
+ GdkScreen *screen;
+
+ if (GTK_WIDGET_CLASS (parent_class)->screen_changed)
+ {
+ GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, old_screen);
+ }
+
+ screen = gtk_widget_get_screen (widget);
+
+ /* FIXME: this seems to be happening when then spinner is destroyed!? */
+ if (old_screen == screen) return;
+
+ /* We'll get mapped again on the new screen, but not unmapped from
+ * the old screen, so remove timeout here.
+ */
+ ephy_spinner_remove_update_callback (spinner);
+
+ ephy_spinner_unload_images (spinner);
+
+ if (old_screen != NULL)
+ {
+ g_signal_handlers_disconnect_by_func
+ (gtk_icon_theme_get_for_screen (old_screen),
+ G_CALLBACK (icon_theme_changed_cb), spinner);
+ }
+
+ details->icon_theme = gtk_icon_theme_get_for_screen (screen);
+ g_signal_connect (details->icon_theme, "changed",
+ G_CALLBACK (icon_theme_changed_cb), spinner);
+}
+
+static void
+ephy_spinner_class_init (EphySpinnerClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ parent_class = g_type_class_peek_parent (class);
+
+ object_class->dispose = ephy_spinner_dispose;
+ object_class->finalize = ephy_spinner_finalize;
+
+ widget_class->expose_event = ephy_spinner_expose;
+ widget_class->size_request = ephy_spinner_size_request;
+ widget_class->map = ephy_spinner_map;
+ widget_class->unmap = ephy_spinner_unmap;
+ widget_class->screen_changed = ephy_spinner_screen_changed;
+
+ g_type_class_add_private (object_class, sizeof (EphySpinnerDetails));
+}
+
+/*
+ * ephy_spinner_new:
+ *
+ * Create a new #EphySpinner. The spinner is a widget
+ * that gives the user feedback about network status with
+ * an animated image.
+ *
+ * Return Value: the spinner #GtkWidget
+ **/
+GtkWidget *
+ephy_spinner_new (void)
+{
+ return GTK_WIDGET (g_object_new (EPHY_TYPE_SPINNER, NULL));
+}
diff --git a/trunk/libempathy-gtk/ephy-spinner.h b/trunk/libempathy-gtk/ephy-spinner.h
new file mode 100644
index 000000000..4435fe371
--- /dev/null
+++ b/trunk/libempathy-gtk/ephy-spinner.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Copyright © 2000 Eazel, Inc.
+ * Copyright © 2004, 2006 Christian Persch
+ *
+ * Nautilus is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Nautilus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: Andy Hertzfeld <andy@eazel.com>
+ *
+ * $Id: ephy-spinner.h 2114 2006-12-25 12:15:00Z mr $
+ */
+
+#ifndef EPHY_SPINNER_H
+#define EPHY_SPINNER_H
+
+#include <gtk/gtkwidget.h>
+#include <gtk/gtkenums.h>
+
+G_BEGIN_DECLS
+
+#define EPHY_TYPE_SPINNER (ephy_spinner_get_type ())
+#define EPHY_SPINNER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EPHY_TYPE_SPINNER, EphySpinner))
+#define EPHY_SPINNER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EPHY_TYPE_SPINNER, EphySpinnerClass))
+#define EPHY_IS_SPINNER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EPHY_TYPE_SPINNER))
+#define EPHY_IS_SPINNER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EPHY_TYPE_SPINNER))
+#define EPHY_SPINNER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EPHY_TYPE_SPINNER, EphySpinnerClass))
+
+typedef struct _EphySpinner EphySpinner;
+typedef struct _EphySpinnerClass EphySpinnerClass;
+typedef struct _EphySpinnerDetails EphySpinnerDetails;
+
+struct _EphySpinner
+{
+ GtkWidget parent;
+
+ /*< private >*/
+ EphySpinnerDetails *details;
+};
+
+struct _EphySpinnerClass
+{
+ GtkWidgetClass parent_class;
+};
+
+GType ephy_spinner_get_type (void);
+
+GtkWidget *ephy_spinner_new (void);
+
+void ephy_spinner_start (EphySpinner *throbber);
+
+void ephy_spinner_stop (EphySpinner *throbber);
+
+void ephy_spinner_set_size (EphySpinner *spinner,
+ GtkIconSize size);
+
+G_END_DECLS
+
+#endif /* EPHY_SPINNER_H */
diff --git a/trunk/libempathy-gtk/gossip-about-dialog.c b/trunk/libempathy-gtk/gossip-about-dialog.c
new file mode 100644
index 000000000..d2c13faa2
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-about-dialog.c
@@ -0,0 +1,113 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtkaboutdialog.h>
+#include <gtk/gtksizegroup.h>
+#include <glade/glade.h>
+
+#include "gossip-about-dialog.h"
+#include "gossip-ui-utils.h"
+
+#define WEB_SITE "http://live.gnome.org/Empathy"
+
+static void about_dialog_activate_link_cb (GtkAboutDialog *about,
+ const gchar *link,
+ gpointer data);
+
+static const char *authors[] = {
+ "Mikael Hallendal",
+ "Richard Hult",
+ "Martyn Russell",
+ "Geert-Jan Van den Bogaerde",
+ "Kevin Dougherty",
+ "Eitan Isaacson",
+ "Xavier Claessens",
+ NULL
+};
+
+static const char *documenters[] = {
+ NULL
+};
+
+static const char *artists[] = {
+ "Andreas Nilsson <nisses.mail@home.se>",
+ "Vinicius Depizzol <vdepizzol@gmail.com>",
+ NULL
+};
+
+static const char *license[] = {
+ N_("Empathy is free software; you can redistribute it and/or modify "
+ "it under the terms of the GNU General Public License as published by "
+ "the Free Software Foundation; either version 2 of the License, or "
+ "(at your option) any later version."),
+ N_("Empathy is distributed in the hope that it will be useful, "
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of "
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "
+ "GNU General Public License for more details."),
+ N_("You should have received a copy of the GNU General Public License "
+ "along with Empathy; if not, write to the Free Software Foundation, Inc., "
+ "51 Franklin Street, Fifth Floor, Boston, MA 02110-130159 USA")
+};
+
+static void
+about_dialog_activate_link_cb (GtkAboutDialog *about,
+ const gchar *link,
+ gpointer data)
+{
+ gossip_url_show (link);
+}
+
+void
+gossip_about_dialog_new (GtkWindow *parent)
+{
+ gchar *license_trans;
+
+ gtk_about_dialog_set_url_hook (about_dialog_activate_link_cb, NULL, NULL);
+
+ license_trans = g_strconcat (_(license[0]), "\n\n",
+ _(license[1]), "\n\n",
+ _(license[2]), "\n\n",
+ NULL);
+
+ gtk_show_about_dialog (parent,
+ "artists", artists,
+ "authors", authors,
+ "comments", _("An Instant Messaging client for GNOME"),
+ "license", license_trans,
+ "wrap-license", TRUE,
+ "copyright", "Imendio AB 2002-2007\nCollabora Ltd 2007",
+ "documenters", documenters,
+ "logo-icon-name", "empathy",
+ "translator-credits", _("translator-credits"),
+ "version", PACKAGE_VERSION,
+ "website", WEB_SITE,
+ NULL);
+
+ g_free (license_trans);
+}
+
+
diff --git a/trunk/libempathy-gtk/gossip-about-dialog.h b/trunk/libempathy-gtk/gossip-about-dialog.h
new file mode 100644
index 000000000..0e5b9d4fb
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-about-dialog.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_ABOUT_DIALOG_H__
+#define __GOSSIP_ABOUT_DIALOG_H__
+
+#include <gtk/gtkwindow.h>
+
+G_BEGIN_DECLS
+
+void gossip_about_dialog_new (GtkWindow *parent);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_ABOUT_DIALOG_H__ */
diff --git a/trunk/libempathy-gtk/gossip-account-chooser.c b/trunk/libempathy-gtk/gossip-account-chooser.c
new file mode 100644
index 000000000..dfed1bb54
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-account-chooser.c
@@ -0,0 +1,637 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <libtelepathy/tp-conn.h>
+#include <libmissioncontrol/mc-account-monitor.h>
+#include <libmissioncontrol/mission-control.h>
+
+#include <libempathy/gossip-utils.h>
+
+#include "gossip-ui-utils.h"
+#include "gossip-account-chooser.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_ACCOUNT_CHOOSER, GossipAccountChooserPriv))
+
+typedef struct {
+ MissionControl *mc;
+ McAccountMonitor *monitor;
+
+ gboolean set_active_item;
+ gboolean can_select_all;
+ gboolean has_all_option;
+} GossipAccountChooserPriv;
+
+typedef struct {
+ GossipAccountChooser *chooser;
+ McAccount *account;
+ gboolean set;
+} SetAccountData;
+
+enum {
+ COL_ACCOUNT_IMAGE,
+ COL_ACCOUNT_TEXT,
+ COL_ACCOUNT_ENABLED, /* Usually tied to connected state */
+ COL_ACCOUNT_POINTER,
+ COL_ACCOUNT_COUNT
+};
+
+static void account_chooser_finalize (GObject *object);
+static void account_chooser_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void account_chooser_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void account_chooser_setup (GossipAccountChooser *chooser);
+static void account_chooser_account_created_cb (McAccountMonitor *monitor,
+ const gchar *unique_name,
+ GossipAccountChooser *chooser);
+static void account_chooser_account_add_foreach (McAccount *account,
+ GossipAccountChooser *chooser);
+static void account_chooser_account_deleted_cb (McAccountMonitor *monitor,
+ const gchar *unique_name,
+ GossipAccountChooser *chooser);
+static void account_chooser_account_remove_foreach (McAccount *account,
+ GossipAccountChooser *chooser);
+static void account_chooser_update_iter (GossipAccountChooser *chooser,
+ GtkTreeIter *iter,
+ McAccount *account);
+static void account_chooser_status_changed_cb (MissionControl *mc,
+ TelepathyConnectionStatus status,
+ McPresence presence,
+ TelepathyConnectionStatusReason reason,
+ const gchar *unique_name,
+ GossipAccountChooser *chooser);
+static gboolean account_chooser_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipAccountChooser *chooser);
+static gboolean account_chooser_set_account_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ SetAccountData *data);
+static gboolean account_chooser_set_enabled_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GossipAccountChooser *chooser);
+
+enum {
+ PROP_0,
+ PROP_CAN_SELECT_ALL,
+ PROP_HAS_ALL_OPTION,
+};
+
+G_DEFINE_TYPE (GossipAccountChooser, gossip_account_chooser, GTK_TYPE_COMBO_BOX);
+
+static void
+gossip_account_chooser_class_init (GossipAccountChooserClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = account_chooser_finalize;
+ object_class->get_property = account_chooser_get_property;
+ object_class->set_property = account_chooser_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_CAN_SELECT_ALL,
+ g_param_spec_boolean ("can-select-all",
+ "Can Select All",
+ "Should the user be able to select offline accounts",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_HAS_ALL_OPTION,
+ g_param_spec_boolean ("has-all-option",
+ "Has All Option",
+ "Have a separate option in the list to mean ALL accounts",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (GossipAccountChooserPriv));
+}
+
+static void
+gossip_account_chooser_init (GossipAccountChooser *chooser)
+{
+}
+
+static void
+account_chooser_finalize (GObject *object)
+{
+ GossipAccountChooser *chooser;
+ GossipAccountChooserPriv *priv;
+
+ chooser = GOSSIP_ACCOUNT_CHOOSER (object);
+ priv = GET_PRIV (object);
+
+ g_signal_handlers_disconnect_by_func (priv->monitor,
+ account_chooser_account_created_cb,
+ chooser);
+ g_signal_handlers_disconnect_by_func (priv->monitor,
+ account_chooser_account_deleted_cb,
+ chooser);
+ dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->mc),
+ "AccountStatusChanged",
+ G_CALLBACK (account_chooser_status_changed_cb),
+ chooser);
+ g_object_unref (priv->mc);
+ g_object_unref (priv->monitor);
+
+ G_OBJECT_CLASS (gossip_account_chooser_parent_class)->finalize (object);
+}
+
+static void
+account_chooser_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GossipAccountChooserPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_CAN_SELECT_ALL:
+ g_value_set_boolean (value, priv->can_select_all);
+ break;
+ case PROP_HAS_ALL_OPTION:
+ g_value_set_boolean (value, priv->has_all_option);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+account_chooser_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GossipAccountChooserPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_CAN_SELECT_ALL:
+ gossip_account_chooser_set_can_select_all (GOSSIP_ACCOUNT_CHOOSER (object),
+ g_value_get_boolean (value));
+ break;
+ case PROP_HAS_ALL_OPTION:
+ gossip_account_chooser_set_has_all_option (GOSSIP_ACCOUNT_CHOOSER (object),
+ g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+GtkWidget *
+gossip_account_chooser_new (void)
+{
+ GossipAccountChooserPriv *priv;
+ McAccountMonitor *monitor;
+ GtkWidget *chooser;
+
+ monitor = mc_account_monitor_new ();
+ chooser = g_object_new (GOSSIP_TYPE_ACCOUNT_CHOOSER, NULL);
+
+ priv = GET_PRIV (chooser);
+
+ priv->mc = gossip_mission_control_new ();
+ priv->monitor = mc_account_monitor_new ();
+
+ g_signal_connect (priv->monitor, "account-created",
+ G_CALLBACK (account_chooser_account_created_cb),
+ chooser);
+ g_signal_connect (priv->monitor, "account-deleted",
+ G_CALLBACK (account_chooser_account_deleted_cb),
+ chooser);
+ dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->mc), "AccountStatusChanged",
+ G_CALLBACK (account_chooser_status_changed_cb),
+ chooser, NULL);
+
+ account_chooser_setup (GOSSIP_ACCOUNT_CHOOSER (chooser));
+
+ return chooser;
+}
+
+McAccount *
+gossip_account_chooser_get_account (GossipAccountChooser *chooser)
+{
+ GossipAccountChooserPriv *priv;
+ McAccount *account;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ g_return_val_if_fail (GOSSIP_IS_ACCOUNT_CHOOSER (chooser), NULL);
+
+ priv = GET_PRIV (chooser);
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser));
+ gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser), &iter);
+
+ gtk_tree_model_get (model, &iter, COL_ACCOUNT_POINTER, &account, -1);
+
+ return account;
+}
+
+gboolean
+gossip_account_chooser_set_account (GossipAccountChooser *chooser,
+ McAccount *account)
+{
+ GtkComboBox *combobox;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ SetAccountData data;
+
+ g_return_val_if_fail (GOSSIP_IS_ACCOUNT_CHOOSER (chooser), FALSE);
+
+ combobox = GTK_COMBO_BOX (chooser);
+ model = gtk_combo_box_get_model (combobox);
+ gtk_combo_box_get_active_iter (combobox, &iter);
+
+ data.chooser = chooser;
+ data.account = account;
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc) account_chooser_set_account_foreach,
+ &data);
+
+ return data.set;
+}
+
+gboolean
+gossip_account_chooser_get_can_select_all (GossipAccountChooser *chooser)
+{
+ GossipAccountChooserPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_ACCOUNT_CHOOSER (chooser), FALSE);
+
+ priv = GET_PRIV (chooser);
+
+ return priv->can_select_all;
+}
+
+void
+gossip_account_chooser_set_can_select_all (GossipAccountChooser *chooser,
+ gboolean can_select_all)
+{
+ GossipAccountChooserPriv *priv;
+ GtkComboBox *combobox;
+ GtkTreeModel *model;
+
+ g_return_if_fail (GOSSIP_IS_ACCOUNT_CHOOSER (chooser));
+
+ priv = GET_PRIV (chooser);
+
+ if (priv->can_select_all == can_select_all) {
+ return;
+ }
+
+ combobox = GTK_COMBO_BOX (chooser);
+ model = gtk_combo_box_get_model (combobox);
+
+ priv->can_select_all = can_select_all;
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc) account_chooser_set_enabled_foreach,
+ chooser);
+
+ g_object_notify (G_OBJECT (chooser), "can-select-all");
+}
+
+gboolean
+gossip_account_chooser_get_has_all_option (GossipAccountChooser *chooser)
+{
+ GossipAccountChooserPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_ACCOUNT_CHOOSER (chooser), FALSE);
+
+ priv = GET_PRIV (chooser);
+
+ return priv->has_all_option;
+}
+
+void
+gossip_account_chooser_set_has_all_option (GossipAccountChooser *chooser,
+ gboolean has_all_option)
+{
+ GossipAccountChooserPriv *priv;
+ GtkComboBox *combobox;
+ GtkListStore *store;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ g_return_if_fail (GOSSIP_IS_ACCOUNT_CHOOSER (chooser));
+
+ priv = GET_PRIV (chooser);
+
+ if (priv->has_all_option == has_all_option) {
+ return;
+ }
+
+ combobox = GTK_COMBO_BOX (chooser);
+ model = gtk_combo_box_get_model (combobox);
+ store = GTK_LIST_STORE (model);
+
+ priv->has_all_option = has_all_option;
+
+ /*
+ * The first 2 options are the ALL and separator
+ */
+
+ if (has_all_option) {
+ gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (chooser),
+ (GtkTreeViewRowSeparatorFunc)
+ account_chooser_separator_func,
+ chooser,
+ NULL);
+
+ gtk_list_store_prepend (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COL_ACCOUNT_TEXT, NULL,
+ COL_ACCOUNT_ENABLED, TRUE,
+ COL_ACCOUNT_POINTER, NULL,
+ -1);
+
+ gtk_list_store_prepend (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COL_ACCOUNT_TEXT, _("All"),
+ COL_ACCOUNT_ENABLED, TRUE,
+ COL_ACCOUNT_POINTER, NULL,
+ -1);
+ } else {
+ if (gtk_tree_model_get_iter_first (model, &iter)) {
+ if (gtk_list_store_remove (GTK_LIST_STORE (model), &iter)) {
+ gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+ }
+ }
+
+ gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (chooser),
+ (GtkTreeViewRowSeparatorFunc)
+ NULL,
+ NULL,
+ NULL);
+ }
+
+ g_object_notify (G_OBJECT (chooser), "has-all-option");
+}
+
+static void
+account_chooser_setup (GossipAccountChooser *chooser)
+{
+ GossipAccountChooserPriv *priv;
+ GList *accounts;
+ GtkListStore *store;
+ GtkCellRenderer *renderer;
+ GtkComboBox *combobox;
+
+ priv = GET_PRIV (chooser);
+
+ /* Set up combo box with new store */
+ combobox = GTK_COMBO_BOX (chooser);
+
+ gtk_cell_layout_clear (GTK_CELL_LAYOUT (combobox));
+
+ store = gtk_list_store_new (COL_ACCOUNT_COUNT,
+ G_TYPE_STRING,
+ G_TYPE_STRING, /* Name */
+ G_TYPE_BOOLEAN, /* Enabled */
+ MC_TYPE_ACCOUNT);
+
+ gtk_combo_box_set_model (combobox, GTK_TREE_MODEL (store));
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combobox), renderer, FALSE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combobox), renderer,
+ "icon-name", COL_ACCOUNT_IMAGE,
+ "sensitive", COL_ACCOUNT_ENABLED,
+ NULL);
+ g_object_set (renderer, "stock-size", GTK_ICON_SIZE_BUTTON, NULL);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combobox), renderer, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combobox), renderer,
+ "text", COL_ACCOUNT_TEXT,
+ "sensitive", COL_ACCOUNT_ENABLED,
+ NULL);
+
+ /* Populate accounts */
+ accounts = mc_accounts_list ();
+ g_list_foreach (accounts,
+ (GFunc) account_chooser_account_add_foreach,
+ chooser);
+
+ mc_accounts_list_free (accounts);
+ g_object_unref (store);
+}
+
+static void
+account_chooser_account_created_cb (McAccountMonitor *monitor,
+ const gchar *unique_name,
+ GossipAccountChooser *chooser)
+{
+ McAccount *account;
+
+ account = mc_account_lookup (unique_name);
+ account_chooser_account_add_foreach (account, chooser);
+ g_object_unref (account);
+}
+
+static void
+account_chooser_account_add_foreach (McAccount *account,
+ GossipAccountChooser *chooser)
+{
+ GossipAccountChooserPriv *priv;
+ GtkListStore *store;
+ GtkComboBox *combobox;
+ GtkTreeIter iter;
+
+ priv = GET_PRIV (chooser);
+
+ combobox = GTK_COMBO_BOX (chooser);
+ store = GTK_LIST_STORE (gtk_combo_box_get_model (combobox));
+
+ gtk_list_store_append (store, &iter);
+ account_chooser_update_iter (chooser, &iter, account);
+}
+
+static void
+account_chooser_account_deleted_cb (McAccountMonitor *monitor,
+ const gchar *unique_name,
+ GossipAccountChooser *chooser)
+{
+ McAccount *account;
+
+ account = mc_account_lookup (unique_name);
+ account_chooser_account_remove_foreach (account, chooser);
+ g_object_unref (account);
+}
+
+static void
+account_chooser_account_remove_foreach (McAccount *account,
+ GossipAccountChooser *chooser)
+{
+ /* Fixme: TODO */
+}
+
+static void
+account_chooser_update_iter (GossipAccountChooser *chooser,
+ GtkTreeIter *iter,
+ McAccount *account)
+{
+ GossipAccountChooserPriv *priv;
+ GtkListStore *store;
+ GtkComboBox *combobox;
+ TpConn *tp_conn;
+ const gchar *icon_name;
+ gboolean is_enabled;
+
+ priv = GET_PRIV (chooser);
+
+ combobox = GTK_COMBO_BOX (chooser);
+ store = GTK_LIST_STORE (gtk_combo_box_get_model (combobox));
+
+ icon_name = gossip_icon_name_from_account (account);
+ tp_conn = mission_control_get_connection (priv->mc, account, NULL);
+ is_enabled = (tp_conn != NULL || priv->can_select_all);
+
+ if (tp_conn) {
+ g_object_unref (tp_conn);
+ }
+
+ gtk_list_store_set (store, iter,
+ COL_ACCOUNT_IMAGE, icon_name,
+ COL_ACCOUNT_TEXT, mc_account_get_display_name (account),
+ COL_ACCOUNT_ENABLED, is_enabled,
+ COL_ACCOUNT_POINTER, account,
+ -1);
+
+ /* set first connected account as active account */
+ if (priv->set_active_item == FALSE && is_enabled) {
+ priv->set_active_item = TRUE;
+ gtk_combo_box_set_active_iter (combobox, iter);
+ }
+}
+
+static void
+account_chooser_status_changed_cb (MissionControl *mc,
+ TelepathyConnectionStatus status,
+ McPresence presence,
+ TelepathyConnectionStatusReason reason,
+ const gchar *unique_name,
+ GossipAccountChooser *chooser)
+{
+ /* FIXME: implement */
+}
+
+static gboolean
+account_chooser_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipAccountChooser *chooser)
+{
+ GossipAccountChooserPriv *priv;
+ gchar *text;
+ gboolean is_separator;
+
+ priv = GET_PRIV (chooser);
+
+ if (!priv->has_all_option) {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (model, iter, COL_ACCOUNT_TEXT, &text, -1);
+ is_separator = text == NULL;
+ g_free (text);
+
+ return is_separator;
+}
+
+static gboolean
+account_chooser_set_account_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ SetAccountData *data)
+{
+ McAccount *account;
+ gboolean equal;
+
+ gtk_tree_model_get (model, iter, COL_ACCOUNT_POINTER, &account, -1);
+
+ /* Special case so we can make it possible to select the All option */
+ if (!data->account && !account) {
+ equal = TRUE;
+ }
+ else if ((data->account && !account) || (!data->account && account)) {
+ equal = FALSE;
+ } else {
+ equal = gossip_account_equal (data->account, account);
+ g_object_unref (account);
+ }
+
+ if (equal) {
+ GtkComboBox *combobox;
+
+ combobox = GTK_COMBO_BOX (data->chooser);
+ gtk_combo_box_set_active_iter (combobox, iter);
+
+ data->set = TRUE;
+ }
+
+ return equal;
+}
+
+static gboolean
+account_chooser_set_enabled_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GossipAccountChooser *chooser)
+{
+ GossipAccountChooserPriv *priv;
+ McAccount *account;
+
+ priv = GET_PRIV (chooser);
+
+ gtk_tree_model_get (model, iter, COL_ACCOUNT_POINTER, &account, -1);
+ if (!account) {
+ return FALSE;
+ }
+
+ account_chooser_update_iter (chooser, iter, account);
+ g_object_unref (account);
+
+ return FALSE;
+}
+
diff --git a/trunk/libempathy-gtk/gossip-account-chooser.h b/trunk/libempathy-gtk/gossip-account-chooser.h
new file mode 100644
index 000000000..b2d7c0cdd
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-account-chooser.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_ACCOUNT_CHOOSER_H__
+#define __GOSSIP_ACCOUNT_CHOOSER_H__
+
+#include <gtk/gtkcombobox.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_ACCOUNT_CHOOSER (gossip_account_chooser_get_type ())
+#define GOSSIP_ACCOUNT_CHOOSER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_ACCOUNT_CHOOSER, GossipAccountChooser))
+#define GOSSIP_ACCOUNT_CHOOSER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_ACCOUNT_CHOOSER, GossipAccountChooserClass))
+#define GOSSIP_IS_ACCOUNT_CHOOSER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_ACCOUNT_CHOOSER))
+#define GOSSIP_IS_ACCOUNT_CHOOSER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_ACCOUNT_CHOOSER))
+#define GOSSIP_ACCOUNT_CHOOSER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_ACCOUNT_CHOOSER, GossipAccountChooserClass))
+
+typedef struct _GossipAccountChooser GossipAccountChooser;
+typedef struct _GossipAccountChooserClass GossipAccountChooserClass;
+
+struct _GossipAccountChooser {
+ GtkComboBox parent;
+};
+
+struct _GossipAccountChooserClass {
+ GtkComboBoxClass parent_class;
+};
+
+GType gossip_account_chooser_get_type (void) G_GNUC_CONST;
+GtkWidget * gossip_account_chooser_new (void);
+McAccount * gossip_account_chooser_get_account (GossipAccountChooser *chooser);
+gboolean gossip_account_chooser_set_account (GossipAccountChooser *chooser,
+ McAccount *account);
+gboolean gossip_account_chooser_get_can_select_all (GossipAccountChooser *chooser);
+
+void gossip_account_chooser_set_can_select_all (GossipAccountChooser *chooser,
+ gboolean can_select_all);
+gboolean gossip_account_chooser_get_has_all_option (GossipAccountChooser *chooser);
+void gossip_account_chooser_set_has_all_option (GossipAccountChooser *chooser,
+ gboolean has_all_option);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_ACCOUNT_CHOOSER_H__ */
+
diff --git a/trunk/libempathy-gtk/gossip-account-widget-generic.c b/trunk/libempathy-gtk/gossip-account-widget-generic.c
new file mode 100644
index 000000000..4ba0f9f2d
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-account-widget-generic.c
@@ -0,0 +1,310 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include <libmissioncontrol/mc-account.h>
+#include <libmissioncontrol/mc-protocol.h>
+
+#include <libempathy-gtk/gossip-ui-utils.h>
+
+#include "gossip-account-widget-generic.h"
+
+typedef struct {
+ McAccount *account;
+
+ GtkWidget *sw;
+ GtkWidget *table_settings;
+ GtkSizeGroup *size_group;
+
+ guint n_rows;
+} GossipAccountWidgetGeneric;
+
+static gboolean account_widget_generic_entry_focus_cb (GtkWidget *widget,
+ GdkEventFocus *event,
+ GossipAccountWidgetGeneric *settings);
+static void account_widget_generic_int_changed_cb (GtkWidget *widget,
+ GossipAccountWidgetGeneric *settings);
+static void account_widget_generic_checkbutton_toggled_cb (GtkWidget *widget,
+ GossipAccountWidgetGeneric *settings);
+static gchar * account_widget_generic_format_param_name (const gchar *param_name);
+static void account_widget_generic_setup_foreach (McProtocolParam *param,
+ GossipAccountWidgetGeneric *settings);
+static void account_widget_generic_destroy_cb (GtkWidget *widget,
+ GossipAccountWidgetGeneric *settings);
+
+static gboolean
+account_widget_generic_entry_focus_cb (GtkWidget *widget,
+ GdkEventFocus *event,
+ GossipAccountWidgetGeneric *settings)
+{
+ const gchar *str;
+ const gchar *param_name;
+
+ str = gtk_entry_get_text (GTK_ENTRY (widget));
+ param_name = g_object_get_data (G_OBJECT (widget), "param_name");
+
+ mc_account_set_param_string (settings->account, param_name, str);
+
+ return FALSE;
+}
+
+static void
+account_widget_generic_int_changed_cb (GtkWidget *widget,
+ GossipAccountWidgetGeneric *settings)
+{
+ const gchar *param_name;
+ gint value;
+
+ value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (widget));
+ param_name = g_object_get_data (G_OBJECT (widget), "param_name");
+
+ mc_account_set_param_int (settings->account, param_name, value);
+}
+
+static void
+account_widget_generic_checkbutton_toggled_cb (GtkWidget *widget,
+ GossipAccountWidgetGeneric *settings)
+{
+ gboolean active;
+ const gchar *param_name;
+
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
+ param_name = g_object_get_data (G_OBJECT (widget), "param_name");
+
+ mc_account_set_param_boolean (settings->account, param_name, active);
+}
+
+static gchar *
+account_widget_generic_format_param_name (const gchar *param_name)
+{
+ gchar *str;
+ gchar *p;
+
+ str = g_strdup (param_name);
+
+ if (str && g_ascii_isalpha (str[0])) {
+ str[0] = g_ascii_toupper (str[0]);
+ }
+
+ while ((p = strchr (str, '-')) != NULL) {
+ if (p[1] != '\0' && g_ascii_isalpha (p[1])) {
+ p[0] = ' ';
+ p[1] = g_ascii_toupper (p[1]);
+ }
+
+ p++;
+ }
+
+ return str;
+}
+
+static void
+account_widget_generic_setup_foreach (McProtocolParam *param,
+ GossipAccountWidgetGeneric *settings)
+{
+ GtkWidget *widget;
+ gchar *param_name_formatted;
+
+ param_name_formatted = account_widget_generic_format_param_name (param->name);
+
+ gtk_table_resize (GTK_TABLE (settings->table_settings),
+ ++settings->n_rows,
+ 2);
+
+ if (param->signature[0] == 's') {
+ gchar *str = NULL;
+
+ str = g_strdup_printf (_("%s:"), param_name_formatted);
+ widget = gtk_label_new (str);
+ g_free (str);
+
+ gtk_size_group_add_widget (settings->size_group, widget);
+ gtk_table_attach (GTK_TABLE (settings->table_settings),
+ widget,
+ 0, 1,
+ settings->n_rows - 1, settings->n_rows,
+ GTK_FILL, 0,
+ 0, 0);
+
+ str = NULL;
+ widget = gtk_entry_new ();
+ mc_account_get_param_string (settings->account,
+ param->name,
+ &str);
+ if (str) {
+ gtk_entry_set_text (GTK_ENTRY (widget), str);
+ g_free (str);
+ }
+
+ if (strstr (param->name, "password")) {
+ gtk_entry_set_visibility (GTK_ENTRY (widget), FALSE);
+ }
+
+ g_signal_connect (widget, "focus-out-event",
+ G_CALLBACK (account_widget_generic_entry_focus_cb),
+ settings);
+
+ gtk_table_attach (GTK_TABLE (settings->table_settings),
+ widget,
+ 1, 2,
+ settings->n_rows - 1, settings->n_rows,
+ GTK_FILL | GTK_EXPAND, 0,
+ 0, 0);
+ }
+ else if (param->signature[0] == 'q' ||
+ param->signature[0] == 'n') {
+ gchar *str = NULL;
+ gint value = 0;
+
+ str = g_strdup_printf (_("%s:"), param_name_formatted);
+ widget = gtk_label_new (str);
+ g_free (str);
+
+ gtk_size_group_add_widget (settings->size_group, widget);
+ gtk_table_attach (GTK_TABLE (settings->table_settings),
+ widget,
+ 0, 1,
+ settings->n_rows - 1, settings->n_rows,
+ GTK_FILL, 0,
+ 0, 0);
+
+ widget = gtk_spin_button_new_with_range (0, G_MAXINT, 1);
+ mc_account_get_param_int (settings->account,
+ param->name,
+ &value);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), value);
+
+ g_signal_connect (widget, "value-changed",
+ G_CALLBACK (account_widget_generic_int_changed_cb),
+ settings);
+
+ gtk_table_attach (GTK_TABLE (settings->table_settings),
+ widget,
+ 1, 2,
+ settings->n_rows - 1, settings->n_rows,
+ GTK_FILL | GTK_EXPAND, 0,
+ 0, 0);
+ }
+ else if (param->signature[0] == 'b') {
+ gboolean value = FALSE;
+
+ mc_account_get_param_boolean (settings->account,
+ param->name,
+ &value);
+
+ widget = gtk_check_button_new_with_label (param_name_formatted);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), value);
+
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (account_widget_generic_checkbutton_toggled_cb),
+ settings);
+
+ gtk_table_attach (GTK_TABLE (settings->table_settings),
+ widget,
+ 0, 2,
+ settings->n_rows - 1, settings->n_rows,
+ GTK_FILL | GTK_EXPAND, 0,
+ 0, 0);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ g_free (param_name_formatted);
+
+ g_object_set_data_full (G_OBJECT (widget), "param_name",
+ g_strdup (param->name), g_free);
+}
+
+static void
+accounts_widget_generic_setup (GossipAccountWidgetGeneric *settings)
+{
+ McProtocol *protocol;
+ McProfile *profile;
+ GSList *params;
+
+ profile = mc_account_get_profile (settings->account);
+ protocol = mc_profile_get_protocol (profile);
+ params = mc_protocol_get_params (protocol);
+
+ g_slist_foreach (params,
+ (GFunc) account_widget_generic_setup_foreach,
+ settings);
+
+ g_slist_free (params);
+}
+
+static void
+account_widget_generic_destroy_cb (GtkWidget *widget,
+ GossipAccountWidgetGeneric *settings)
+{
+ g_object_unref (settings->account);
+ g_object_unref (settings->size_group);
+
+ g_free (settings);
+}
+
+GtkWidget *
+gossip_account_widget_generic_new (McAccount *account,
+ GtkWidget *label_name)
+{
+ GossipAccountWidgetGeneric *settings;
+
+ g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (GTK_IS_WIDGET (label_name), NULL);
+
+ settings = g_new0 (GossipAccountWidgetGeneric, 1);
+
+ settings->account = g_object_ref (account);
+
+ settings->table_settings = gtk_table_new (0, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (settings->table_settings), 6);
+ gtk_table_set_col_spacings (GTK_TABLE (settings->table_settings), 6);
+ settings->sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (settings->sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (settings->sw),
+ settings->table_settings);
+
+ settings->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ if (label_name) {
+ gtk_size_group_add_widget (settings->size_group, label_name);
+ }
+
+ accounts_widget_generic_setup (settings);
+
+ g_signal_connect (settings->sw, "destroy",
+ G_CALLBACK (account_widget_generic_destroy_cb),
+ settings);
+
+ gtk_widget_show_all (settings->sw);
+
+ return settings->sw;
+}
diff --git a/trunk/libempathy-gtk/gossip-account-widget-generic.h b/trunk/libempathy-gtk/gossip-account-widget-generic.h
new file mode 100644
index 000000000..4b3c7eba2
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-account-widget-generic.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_ACCOUNT_WIDGET_GENERIC_H__
+#define __GOSSIP_ACCOUNT_WIDGET_GENERIC_H__
+
+#include <gtk/gtk.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+G_BEGIN_DECLS
+
+GtkWidget *gossip_account_widget_generic_new (McAccount *account,
+ GtkWidget *label_name);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_ACCOUNT_WIDGET_GENERIC_H__ */
diff --git a/trunk/libempathy-gtk/gossip-account-widget-jabber.c b/trunk/libempathy-gtk/gossip-account-widget-jabber.c
new file mode 100644
index 000000000..e7334b4b5
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-account-widget-jabber.c
@@ -0,0 +1,281 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include "config.h"
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <libmissioncontrol/mc-profile.h>
+
+#include <libempathy/gossip-utils.h>
+
+#include "gossip-account-widget-jabber.h"
+#include "gossip-ui-utils.h"
+
+#define PORT_WITHOUT_SSL 5222
+#define PORT_WITH_SSL 5223
+
+typedef struct {
+ McAccount *account;
+
+ GtkWidget *vbox_settings;
+ GtkWidget *button_forget;
+ GtkWidget *entry_id;
+ GtkWidget *entry_password;
+ GtkWidget *entry_resource;
+ GtkWidget *entry_server;
+ GtkWidget *spinbutton_port;
+ GtkWidget *checkbutton_ssl;
+} GossipAccountWidgetJabber;
+
+static gboolean account_widget_jabber_entry_focus_cb (GtkWidget *widget,
+ GdkEventFocus *event,
+ GossipAccountWidgetJabber *settings);
+static void account_widget_jabber_entry_changed_cb (GtkWidget *widget,
+ GossipAccountWidgetJabber *settings);
+static void account_widget_jabber_checkbutton_toggled_cb (GtkWidget *widget,
+ GossipAccountWidgetJabber *settings);
+static void account_widget_jabber_value_changed_cb (GtkWidget *spinbutton,
+ GossipAccountWidgetJabber *settings);
+static void account_widget_jabber_button_forget_clicked_cb (GtkWidget *button,
+ GossipAccountWidgetJabber *settings);
+static void account_widget_jabber_destroy_cb (GtkWidget *widget,
+ GossipAccountWidgetJabber *settings);
+static void account_widget_jabber_setup (GossipAccountWidgetJabber *settings);
+
+static gboolean
+account_widget_jabber_entry_focus_cb (GtkWidget *widget,
+ GdkEventFocus *event,
+ GossipAccountWidgetJabber *settings)
+{
+ const gchar *param;
+ const gchar *str;
+
+ if (widget == settings->entry_password) {
+ param = "password";
+ }
+ else if (widget == settings->entry_resource) {
+ param = "resource";
+ }
+ else if (widget == settings->entry_server) {
+ param = "server";
+ }
+ else if (widget == settings->entry_id) {
+ param = "account";
+ } else {
+ return FALSE;
+ }
+
+ str = gtk_entry_get_text (GTK_ENTRY (widget));
+ if (G_STR_EMPTY (str)) {
+ gchar *value = NULL;
+
+ mc_account_get_param_string (settings->account, param, &value);
+ gtk_entry_set_text (GTK_ENTRY (widget), value ? value : "");
+ g_free (value);
+ } else {
+ mc_account_set_param_string (settings->account, param, str);
+ }
+
+ return FALSE;
+}
+
+static void
+account_widget_jabber_entry_changed_cb (GtkWidget *widget,
+ GossipAccountWidgetJabber *settings)
+{
+ if (widget == settings->entry_password) {
+ const gchar *str;
+
+ str = gtk_entry_get_text (GTK_ENTRY (widget));
+ gtk_widget_set_sensitive (settings->button_forget, !G_STR_EMPTY (str));
+ }
+}
+
+static void
+account_widget_jabber_checkbutton_toggled_cb (GtkWidget *widget,
+ GossipAccountWidgetJabber *settings)
+{
+ if (widget == settings->checkbutton_ssl) {
+ gint port = 0;
+ gboolean old_ssl;
+
+ mc_account_get_param_int (settings->account, "port", &port);
+ old_ssl = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
+
+ if (old_ssl) {
+ if (port == PORT_WITHOUT_SSL) {
+ port = PORT_WITH_SSL;
+ }
+ } else {
+ if (port == PORT_WITH_SSL) {
+ port = PORT_WITHOUT_SSL;
+ }
+ }
+
+ mc_account_set_param_int (settings->account, "port", port);
+ mc_account_set_param_boolean (settings->account, "old-ssl", old_ssl);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (settings->spinbutton_port), port);
+ }
+}
+
+static void
+account_widget_jabber_value_changed_cb (GtkWidget *spinbutton,
+ GossipAccountWidgetJabber *settings)
+{
+ if (spinbutton == settings->spinbutton_port) {
+ gdouble value;
+
+ value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (spinbutton));
+ mc_account_set_param_int (settings->account, "port", (gint) value);
+ }
+}
+
+static void
+account_widget_jabber_button_forget_clicked_cb (GtkWidget *button,
+ GossipAccountWidgetJabber *settings)
+{
+ mc_account_set_param_string (settings->account, "password", "");
+ gtk_entry_set_text (GTK_ENTRY (settings->entry_password), "");
+}
+
+static void
+account_widget_jabber_destroy_cb (GtkWidget *widget,
+ GossipAccountWidgetJabber *settings)
+{
+ g_object_unref (settings->account);
+ g_free (settings);
+}
+
+static void
+account_widget_jabber_setup (GossipAccountWidgetJabber *settings)
+{
+ gint port = 0;
+ gchar *id = NULL;
+ gchar *resource = NULL;
+ gchar *server = NULL;
+ gchar *password = NULL;
+ gboolean old_ssl = FALSE;
+
+ mc_account_get_param_int (settings->account, "port", &port);
+ mc_account_get_param_string (settings->account, "account", &id);
+ mc_account_get_param_string (settings->account, "resource", &resource);
+ mc_account_get_param_string (settings->account, "server", &server);
+ mc_account_get_param_string (settings->account, "password", &password);
+ mc_account_get_param_boolean (settings->account, "old-ssl", &old_ssl);
+
+ if (!id) {
+ McProfile *profile;
+ const gchar *server;
+
+ profile = mc_account_get_profile (settings->account);
+ server = mc_profile_get_default_account_domain (profile);
+ if (server) {
+ id = g_strconcat ("user@", server, NULL);
+ }
+ g_object_unref (profile);
+ }
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (settings->checkbutton_ssl), old_ssl);
+ gtk_entry_set_text (GTK_ENTRY (settings->entry_id), id ? id : "");
+ gtk_entry_set_text (GTK_ENTRY (settings->entry_password), password ? password : "");
+ gtk_entry_set_text (GTK_ENTRY (settings->entry_resource), resource ? resource : "");
+ gtk_entry_set_text (GTK_ENTRY (settings->entry_server), server ? server : "");
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (settings->spinbutton_port), port);
+
+ gtk_widget_set_sensitive (settings->button_forget, !G_STR_EMPTY (password));
+
+ g_free (id);
+ g_free (resource);
+ g_free (server);
+ g_free (password);
+}
+
+GtkWidget *
+gossip_account_widget_jabber_new (McAccount *account)
+{
+ GossipAccountWidgetJabber *settings;
+ GladeXML *glade;
+ GtkSizeGroup *size_group;
+ GtkWidget *label_id, *label_password;
+ GtkWidget *label_server, *label_resource, *label_port;
+
+ settings = g_new0 (GossipAccountWidgetJabber, 1);
+ settings->account = g_object_ref (account);
+
+ glade = gossip_glade_get_file ("gossip-account-widget-jabber.glade",
+ "vbox_jabber_settings",
+ NULL,
+ "vbox_jabber_settings", &settings->vbox_settings,
+ "button_forget", &settings->button_forget,
+ "label_id", &label_id,
+ "label_password", &label_password,
+ "label_resource", &label_resource,
+ "label_server", &label_server,
+ "label_port", &label_port,
+ "entry_id", &settings->entry_id,
+ "entry_password", &settings->entry_password,
+ "entry_resource", &settings->entry_resource,
+ "entry_server", &settings->entry_server,
+ "spinbutton_port", &settings->spinbutton_port,
+ "checkbutton_ssl", &settings->checkbutton_ssl,
+ NULL);
+
+ account_widget_jabber_setup (settings);
+
+ gossip_glade_connect (glade,
+ settings,
+ "vbox_jabber_settings", "destroy", account_widget_jabber_destroy_cb,
+ "button_forget", "clicked", account_widget_jabber_button_forget_clicked_cb,
+ "entry_password", "changed", account_widget_jabber_entry_changed_cb,
+ "spinbutton_port", "value-changed", account_widget_jabber_value_changed_cb,
+ "entry_id", "focus-out-event", account_widget_jabber_entry_focus_cb,
+ "entry_password", "focus-out-event", account_widget_jabber_entry_focus_cb,
+ "entry_resource", "focus-out-event", account_widget_jabber_entry_focus_cb,
+ "entry_server", "focus-out-event", account_widget_jabber_entry_focus_cb,
+ "checkbutton_ssl", "toggled", account_widget_jabber_checkbutton_toggled_cb,
+ NULL);
+
+ g_object_unref (glade);
+
+ /* Set up remaining widgets */
+ size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ gtk_size_group_add_widget (size_group, label_id);
+ gtk_size_group_add_widget (size_group, label_password);
+ gtk_size_group_add_widget (size_group, label_resource);
+ gtk_size_group_add_widget (size_group, label_server);
+ gtk_size_group_add_widget (size_group, label_port);
+
+ g_object_unref (size_group);
+
+ gtk_widget_show (settings->vbox_settings);
+
+ return settings->vbox_settings;
+}
+
diff --git a/trunk/libempathy-gtk/gossip-account-widget-jabber.glade b/trunk/libempathy-gtk/gossip-account-widget-jabber.glade
new file mode 100644
index 000000000..12eec7576
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-account-widget-jabber.glade
@@ -0,0 +1,335 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="account_jabber_settings">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">jabber account settings</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+
+ <child>
+ <widget class="GtkTable" id="vbox_jabber_settings">
+ <property name="n_rows">6</property>
+ <property name="n_columns">3</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="label_id">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Login I_D:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_id</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label_password">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Pass_word:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_password</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label_resource">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Reso_urce:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_resource</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label_server">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Server:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_server</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label_port">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Port:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">spinbutton_port</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry_resource">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry_server">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_ssl">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Use encryption (SS_L)</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkSpinButton" id="spinbutton_port">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="climb_rate">1</property>
+ <property name="digits">0</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">GTK_UPDATE_ALWAYS</property>
+ <property name="snap_to_ticks">False</property>
+ <property name="wrap">False</property>
+ <property name="adjustment">5222 0 65556 1 10 10</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="button_forget">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Forget password and clear the entry.</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+
+ <child>
+ <widget class="GtkImage" id="image834">
+ <property name="visible">True</property>
+ <property name="stock">gtk-clear</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry_password">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">False</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry_id">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/trunk/libempathy-gtk/gossip-account-widget-jabber.h b/trunk/libempathy-gtk/gossip-account-widget-jabber.h
new file mode 100644
index 000000000..0a967f7b6
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-account-widget-jabber.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __GOSSIP_ACCOUNT_WIDGET_JABBER_H__
+#define __GOSSIP_ACCOUNT_WIDGET_JABBER_H__
+
+#include <libmissioncontrol/mc-account.h>
+
+G_BEGIN_DECLS
+
+GtkWidget *gossip_account_widget_jabber_new (McAccount *account);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_ACCOUNT_WIDGET_JABBER_H__ */
diff --git a/trunk/libempathy-gtk/gossip-accounts-dialog.c b/trunk/libempathy-gtk/gossip-accounts-dialog.c
new file mode 100644
index 000000000..2a3a92764
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-accounts-dialog.c
@@ -0,0 +1,1038 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+#include <dbus/dbus-glib.h>
+
+#include <libmissioncontrol/mc-account.h>
+#include <libmissioncontrol/mc-profile.h>
+#include <libmissioncontrol/mission-control.h>
+#include <libmissioncontrol/mc-account-monitor.h>
+#include <libtelepathy/tp-constants.h>
+
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-paths.h>
+#include <libempathy/gossip-utils.h>
+#include <libempathy-gtk/gossip-ui-utils.h>
+
+#include "gossip-accounts-dialog.h"
+#include "gossip-profile-chooser.h"
+#include "gossip-account-widget-generic.h"
+#include "gossip-account-widget-jabber.h"
+
+#define DEBUG_DOMAIN "AccountDialog"
+
+/* Flashing delay for icons (milliseconds). */
+#define FLASH_TIMEOUT 500
+
+typedef struct {
+ GtkWidget *window;
+
+ GtkWidget *alignment_settings;
+
+ GtkWidget *vbox_details;
+ GtkWidget *frame_no_account;
+ GtkWidget *label_no_account;
+ GtkWidget *label_no_account_blurb;
+
+ GtkWidget *treeview;
+
+ GtkWidget *button_remove;
+ GtkWidget *button_connect;
+
+ GtkWidget *frame_new_account;
+ GtkWidget *combobox_profile;
+ GtkWidget *entry_name;
+ GtkWidget *table_new_account;
+ GtkWidget *button_create;
+ GtkWidget *button_back;
+
+ GtkWidget *image_type;
+ GtkWidget *label_name;
+ GtkWidget *label_type;
+ GtkWidget *settings_widget;
+
+ gboolean connecting_show;
+ guint connecting_id;
+ gboolean account_changed;
+
+ MissionControl *mc;
+ McAccountMonitor *monitor;
+} GossipAccountsDialog;
+
+enum {
+ COL_NAME,
+ COL_STATUS,
+ COL_ACCOUNT_POINTER,
+ COL_COUNT
+};
+
+static void accounts_dialog_setup (GossipAccountsDialog *dialog);
+static void accounts_dialog_update_account (GossipAccountsDialog *dialog,
+ McAccount *account);
+static void accounts_dialog_model_setup (GossipAccountsDialog *dialog);
+static void accounts_dialog_model_add_columns (GossipAccountsDialog *dialog);
+static void accounts_dialog_model_select_first (GossipAccountsDialog *dialog);
+static void accounts_dialog_model_pixbuf_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipAccountsDialog *dialog);
+static McAccount *accounts_dialog_model_get_selected (GossipAccountsDialog *dialog);
+static void accounts_dialog_model_set_selected (GossipAccountsDialog *dialog,
+ McAccount *account);
+static gboolean accounts_dialog_model_remove_selected (GossipAccountsDialog *dialog);
+static void accounts_dialog_model_selection_changed (GtkTreeSelection *selection,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_add_account (GossipAccountsDialog *dialog,
+ McAccount *account);
+static void accounts_dialog_account_added_cb (McAccountMonitor *monitor,
+ gchar *unique_name,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_account_removed_cb (McAccountMonitor *monitor,
+ gchar *unique_name,
+ GossipAccountsDialog *dialog);
+static gboolean accounts_dialog_row_changed_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data);
+static gboolean accounts_dialog_flash_connecting_cb (GossipAccountsDialog *dialog);
+static void accounts_dialog_status_changed_cb (MissionControl *mc,
+ TelepathyConnectionStatus status,
+ McPresence presence,
+ TelepathyConnectionStatusReason reason,
+ const gchar *unique_name,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_entry_name_changed_cb (GtkWidget *widget,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_button_create_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_button_back_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_button_connect_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_button_add_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_remove_response_cb (GtkWidget *dialog,
+ gint response,
+ McAccount *account);
+static void accounts_dialog_button_remove_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_treeview_row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_response_cb (GtkWidget *widget,
+ gint response,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_destroy_cb (GtkWidget *widget,
+ GossipAccountsDialog *dialog);
+
+static void
+accounts_dialog_setup (GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkListStore *store;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GList *accounts, *l;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
+ selection = gtk_tree_view_get_selection (view);
+
+ accounts = mc_accounts_list ();
+
+ for (l = accounts; l; l = l->next) {
+ McAccount *account;
+ const gchar *name;
+ TelepathyConnectionStatus status;
+
+ account = l->data;
+
+ name = mc_account_get_display_name (account);
+ if (!name) {
+ continue;
+ }
+
+ status = mission_control_get_connection_status (dialog->mc, account, NULL);
+
+ gtk_list_store_insert_with_values (store, &iter,
+ -1,
+ COL_NAME, name,
+ COL_STATUS, status,
+ COL_ACCOUNT_POINTER, account,
+ -1);
+
+ accounts_dialog_status_changed_cb (dialog->mc,
+ status,
+ MC_PRESENCE_UNSET,
+ TP_CONN_STATUS_REASON_NONE_SPECIFIED,
+ mc_account_get_unique_name (account),
+ dialog);
+
+ g_object_unref (account);
+ }
+
+ g_list_free (accounts);
+}
+
+static void
+accounts_dialog_update_connect_button (GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+ GtkWidget *image;
+ const gchar *stock_id;
+ const gchar *label;
+
+ account = accounts_dialog_model_get_selected (dialog);
+
+ if (!account) {
+ gtk_widget_set_sensitive (dialog->button_connect, FALSE);
+ return;
+ }
+
+ if (mc_account_is_enabled (account)) {
+ label = _("Disable");
+ stock_id = GTK_STOCK_DISCONNECT;
+ } else {
+ label = _("Enable");
+ stock_id = GTK_STOCK_CONNECT;
+ }
+
+ image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_BUTTON);
+
+ gtk_button_set_label (GTK_BUTTON (dialog->button_connect), label);
+ gtk_button_set_image (GTK_BUTTON (dialog->button_connect), image);
+}
+
+static void
+accounts_dialog_update_account (GossipAccountsDialog *dialog,
+ McAccount *account)
+{
+ if (dialog->settings_widget) {
+ gtk_widget_destroy (dialog->settings_widget);
+ dialog->settings_widget = NULL;
+ }
+
+ if (!account) {
+ GtkTreeView *view;
+ GtkTreeModel *model;
+
+ gtk_widget_show (dialog->frame_no_account);
+ gtk_widget_hide (dialog->vbox_details);
+
+ gtk_widget_set_sensitive (dialog->button_connect, FALSE);
+ gtk_widget_set_sensitive (dialog->button_remove, FALSE);
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ model = gtk_tree_view_get_model (view);
+
+ if (gtk_tree_model_iter_n_children (model, NULL) > 0) {
+ gtk_label_set_markup (GTK_LABEL (dialog->label_no_account),
+ _("<b>No Account Selected</b>"));
+ gtk_label_set_markup (GTK_LABEL (dialog->label_no_account_blurb),
+ _("To add a new account, you can click on the "
+ "'Add' button and a new entry will be created "
+ "for you to start configuring.\n"
+ "\n"
+ "If you do not want to add an account, simply "
+ "click on the account you want to configure in "
+ "the list on the left."));
+ } else {
+ gtk_label_set_markup (GTK_LABEL (dialog->label_no_account),
+ _("<b>No Accounts Configured</b>"));
+ gtk_label_set_markup (GTK_LABEL (dialog->label_no_account_blurb),
+ _("To add a new account, you can click on the "
+ "'Add' button and a new entry will be created "
+ "for you to start configuring."));
+ }
+ } else {
+ McProfile *profile;
+ const gchar *config_ui;
+
+ gtk_widget_hide (dialog->frame_no_account);
+ gtk_widget_show (dialog->vbox_details);
+
+ profile = mc_account_get_profile (account);
+ config_ui = mc_profile_get_configuration_ui (profile);
+
+ if (strcmp (config_ui, "jabber") == 0) {
+ dialog->settings_widget =
+ gossip_account_widget_jabber_new (account);
+ } else {
+ dialog->settings_widget =
+ gossip_account_widget_generic_new (account,
+ dialog->label_name);
+ }
+
+ gtk_widget_grab_focus (dialog->settings_widget);
+ }
+
+ if (dialog->settings_widget) {
+ gtk_container_add (GTK_CONTAINER (dialog->alignment_settings),
+ dialog->settings_widget);
+ }
+
+ if (account) {
+ McProfile *profile;
+
+ profile = mc_account_get_profile (account);
+ gtk_image_set_from_icon_name (GTK_IMAGE (dialog->image_type),
+ mc_profile_get_icon_name (profile),
+ GTK_ICON_SIZE_DIALOG);
+
+
+ gtk_label_set_text (GTK_LABEL (dialog->label_type),
+ mc_profile_get_display_name (profile));
+ gtk_label_set_text (GTK_LABEL (dialog->label_name),
+ mc_account_get_display_name (account));
+ }
+}
+
+static void
+accounts_dialog_model_setup (GossipAccountsDialog *dialog)
+{
+ GtkListStore *store;
+ GtkTreeSelection *selection;
+
+ store = gtk_list_store_new (COL_COUNT,
+ G_TYPE_STRING, /* name */
+ G_TYPE_UINT, /* status */
+ MC_TYPE_ACCOUNT); /* account */
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview),
+ GTK_TREE_MODEL (store));
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->treeview));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (accounts_dialog_model_selection_changed),
+ dialog);
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+ COL_NAME, GTK_SORT_ASCENDING);
+
+ accounts_dialog_model_add_columns (dialog);
+
+ g_object_unref (store);
+}
+
+static void
+accounts_dialog_model_add_columns (GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ gtk_tree_view_set_headers_visible (view, TRUE);
+
+ /* account name/status */
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_title (column, _("Accounts"));
+
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+ gtk_tree_view_column_set_cell_data_func (column, cell,
+ (GtkTreeCellDataFunc)
+ accounts_dialog_model_pixbuf_data_func,
+ dialog,
+ NULL);
+
+ cell = gtk_cell_renderer_text_new ();
+ g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_tree_view_column_pack_start (column, cell, TRUE);
+ gtk_tree_view_column_add_attribute (column,
+ cell,
+ "text", COL_NAME);
+
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_append_column (view, column);
+}
+
+static void
+accounts_dialog_model_select_first (GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ /* select first */
+ view = GTK_TREE_VIEW (dialog->treeview);
+ model = gtk_tree_view_get_model (view);
+
+ if (gtk_tree_model_get_iter_first (model, &iter)) {
+ selection = gtk_tree_view_get_selection (view);
+ gtk_tree_selection_select_iter (selection, &iter);
+ } else {
+ accounts_dialog_update_account (dialog, NULL);
+ }
+}
+
+static void
+accounts_dialog_model_pixbuf_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+ const gchar *icon_name;
+ GdkPixbuf *pixbuf;
+ TelepathyConnectionStatus status;
+
+ gtk_tree_model_get (model, iter,
+ COL_STATUS, &status,
+ COL_ACCOUNT_POINTER, &account,
+ -1);
+
+ icon_name = gossip_icon_name_from_account (account);
+ pixbuf = gossip_pixbuf_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON);
+
+ if (pixbuf) {
+ if (status == TP_CONN_STATUS_DISCONNECTED ||
+ (status == TP_CONN_STATUS_CONNECTING &&
+ !dialog->connecting_show)) {
+ GdkPixbuf *modded_pixbuf;
+
+ modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ TRUE,
+ 8,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf));
+
+ gdk_pixbuf_saturate_and_pixelate (pixbuf,
+ modded_pixbuf,
+ 1.0,
+ TRUE);
+ g_object_unref (pixbuf);
+ pixbuf = modded_pixbuf;
+ }
+ }
+
+ g_object_set (cell,
+ "visible", TRUE,
+ "pixbuf", pixbuf,
+ NULL);
+
+ g_object_unref (account);
+ if (pixbuf) {
+ g_object_unref (pixbuf);
+ }
+}
+
+static McAccount *
+accounts_dialog_model_get_selected (GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ McAccount *account;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ selection = gtk_tree_view_get_selection (view);
+
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ return NULL;
+ }
+
+ gtk_tree_model_get (model, &iter, COL_ACCOUNT_POINTER, &account, -1);
+
+ return account;
+}
+
+static void
+accounts_dialog_model_set_selected (GossipAccountsDialog *dialog,
+ McAccount *account)
+{
+ GtkTreeView *view;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean ok;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ model = gtk_tree_view_get_model (view);
+ selection = gtk_tree_view_get_selection (view);
+
+ for (ok = gtk_tree_model_get_iter_first (model, &iter);
+ ok;
+ ok = gtk_tree_model_iter_next (model, &iter)) {
+ McAccount *this_account;
+ gboolean equal;
+
+ gtk_tree_model_get (model, &iter,
+ COL_ACCOUNT_POINTER, &this_account,
+ -1);
+
+ equal = gossip_account_equal (this_account, account);
+ g_object_unref (this_account);
+
+ if (equal) {
+ gtk_tree_selection_select_iter (selection, &iter);
+ break;
+ }
+ }
+}
+
+static gboolean
+accounts_dialog_model_remove_selected (GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ selection = gtk_tree_view_get_selection (view);
+
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ return FALSE;
+ }
+
+ return gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+}
+
+static void
+accounts_dialog_model_selection_changed (GtkTreeSelection *selection,
+ GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean is_selection;
+
+ is_selection = gtk_tree_selection_get_selected (selection, &model, &iter);
+
+ gtk_widget_set_sensitive (dialog->button_remove, is_selection);
+ gtk_widget_set_sensitive (dialog->button_connect, is_selection);
+
+ accounts_dialog_update_connect_button (dialog);
+
+ account = accounts_dialog_model_get_selected (dialog);
+ accounts_dialog_update_account (dialog, account);
+
+ if (account) {
+ g_object_unref (account);
+ }
+}
+
+static void
+accounts_dialog_add_account (GossipAccountsDialog *dialog,
+ McAccount *account)
+{
+ TelepathyConnectionStatus status;
+ const gchar *name;
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gboolean ok;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ model = gtk_tree_view_get_model (view);
+ store = GTK_LIST_STORE (model);
+
+ for (ok = gtk_tree_model_get_iter_first (model, &iter);
+ ok;
+ ok = gtk_tree_model_iter_next (model, &iter)) {
+ McAccount *this_account;
+ gboolean equal;
+
+ gtk_tree_model_get (model, &iter,
+ COL_ACCOUNT_POINTER, &this_account,
+ -1);
+
+ equal = gossip_account_equal (this_account, account);
+ g_object_unref (this_account);
+
+ if (equal) {
+ return;
+ }
+ }
+
+ status = mission_control_get_connection_status (dialog->mc, account, NULL);
+ name = mc_account_get_display_name (account);
+
+ g_return_if_fail (name != NULL);
+
+ gossip_debug (DEBUG_DOMAIN, "Adding new account: %s", name);
+
+ gtk_list_store_insert_with_values (store, &iter,
+ -1,
+ COL_NAME, name,
+ COL_STATUS, status,
+ COL_ACCOUNT_POINTER, account,
+ -1);
+}
+
+static void
+accounts_dialog_account_added_cb (McAccountMonitor *monitor,
+ gchar *unique_name,
+ GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+
+ account = mc_account_lookup (unique_name);
+ accounts_dialog_add_account (dialog, account);
+ g_object_unref (account);
+}
+
+static void
+accounts_dialog_account_removed_cb (McAccountMonitor *monitor,
+ gchar *unique_name,
+ GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+
+ account = mc_account_lookup (unique_name);
+
+ accounts_dialog_model_set_selected (dialog, account);
+ accounts_dialog_model_remove_selected (dialog);
+
+ g_object_unref (account);
+}
+
+static gboolean
+accounts_dialog_row_changed_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ gtk_tree_model_row_changed (model, path, iter);
+
+ return FALSE;
+}
+
+static gboolean
+accounts_dialog_flash_connecting_cb (GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+
+ dialog->connecting_show = !dialog->connecting_show;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ model = gtk_tree_view_get_model (view);
+
+ gtk_tree_model_foreach (model, accounts_dialog_row_changed_foreach, NULL);
+
+ return TRUE;
+}
+
+static void
+accounts_dialog_status_changed_cb (MissionControl *mc,
+ TelepathyConnectionStatus status,
+ McPresence presence,
+ TelepathyConnectionStatusReason reason,
+ const gchar *unique_name,
+ GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean ok;
+ McAccount *account;
+ GList *accounts, *l;
+ gboolean found = FALSE;
+
+ /* Update the status in the model */
+ view = GTK_TREE_VIEW (dialog->treeview);
+ selection = gtk_tree_view_get_selection (view);
+ model = gtk_tree_view_get_model (view);
+ account = mc_account_lookup (unique_name);
+
+ gossip_debug (DEBUG_DOMAIN, "Status changed for account %s: "
+ "status=%d presence=%d",
+ unique_name, status, presence);
+
+ for (ok = gtk_tree_model_get_iter_first (model, &iter);
+ ok;
+ ok = gtk_tree_model_iter_next (model, &iter)) {
+ McAccount *this_account;
+ gboolean equal;
+
+ gtk_tree_model_get (model, &iter,
+ COL_ACCOUNT_POINTER, &this_account,
+ -1);
+
+ equal = gossip_account_equal (this_account, account);
+ g_object_unref (this_account);
+
+ if (equal) {
+ GtkTreePath *path;
+
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+ COL_STATUS, status,
+ -1);
+
+ path = gtk_tree_model_get_path (model, &iter);
+ gtk_tree_model_row_changed (model, path, &iter);
+ gtk_tree_path_free (path);
+
+ break;
+ }
+ }
+
+ g_object_unref (account);
+
+ /* Start to flash account if status is connecting */
+ if (status == TP_CONN_STATUS_CONNECTING) {
+ if (!dialog->connecting_id) {
+ dialog->connecting_id = g_timeout_add (FLASH_TIMEOUT,
+ (GSourceFunc) accounts_dialog_flash_connecting_cb,
+ dialog);
+ }
+
+ return;
+ }
+
+ /* Stop to flash if no account is connecting */
+ accounts = mc_accounts_list ();
+ for (l = accounts; l; l = l->next) {
+ McAccount *this_account;
+
+ this_account = l->data;
+
+ if (mission_control_get_connection_status (mc, this_account, NULL) == TP_CONN_STATUS_CONNECTING) {
+ found = TRUE;
+ break;
+ }
+
+ g_object_unref (this_account);
+ }
+ g_list_free (accounts);
+
+ if (!found && dialog->connecting_id) {
+ g_source_remove (dialog->connecting_id);
+ dialog->connecting_id = 0;
+ }
+
+ gtk_widget_show (dialog->window);
+}
+
+static void
+accounts_dialog_entry_name_changed_cb (GtkWidget *widget,
+ GossipAccountsDialog *dialog)
+{
+ const gchar *str;
+
+ str = gtk_entry_get_text (GTK_ENTRY (widget));
+ gtk_widget_set_sensitive (dialog->button_create, !G_STR_EMPTY (str));
+}
+
+static void
+accounts_dialog_button_create_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog)
+{
+ McProfile *profile;
+ McAccount *account;
+ const gchar *str;
+
+ /* Update widgets */
+ gtk_widget_show (dialog->vbox_details);
+ gtk_widget_hide (dialog->frame_no_account);
+ gtk_widget_hide (dialog->frame_new_account);
+
+ profile = gossip_profile_chooser_get_selected (dialog->combobox_profile);
+
+ /* Create account */
+ account = mc_account_create (profile);
+
+ str = gtk_entry_get_text (GTK_ENTRY (dialog->entry_name));
+ mc_account_set_display_name (account, str);
+
+ accounts_dialog_add_account (dialog, account);
+ accounts_dialog_model_set_selected (dialog, account);
+
+ g_object_unref (account);
+ g_object_unref (profile);
+}
+
+static void
+accounts_dialog_button_back_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+
+ gtk_widget_hide (dialog->vbox_details);
+ gtk_widget_hide (dialog->frame_no_account);
+ gtk_widget_hide (dialog->frame_new_account);
+
+ account = accounts_dialog_model_get_selected (dialog);
+ accounts_dialog_update_account (dialog, account);
+}
+
+static void
+accounts_dialog_button_connect_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+ gboolean enable;
+
+ account = accounts_dialog_model_get_selected (dialog);
+ enable = (!mc_account_is_enabled (account));
+ mc_account_set_enabled (account, enable);
+ accounts_dialog_update_connect_button (dialog);
+
+ g_object_unref (account);
+}
+
+static void
+accounts_dialog_button_add_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog)
+{
+ gtk_widget_hide (dialog->vbox_details);
+ gtk_widget_hide (dialog->frame_no_account);
+ gtk_widget_show (dialog->frame_new_account);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->combobox_profile), 0);
+ gtk_entry_set_text (GTK_ENTRY (dialog->entry_name), "");
+ gtk_widget_grab_focus (dialog->entry_name);
+}
+
+static void
+accounts_dialog_remove_response_cb (GtkWidget *dialog,
+ gint response,
+ McAccount *account)
+{
+ if (response == GTK_RESPONSE_YES) {
+ mc_account_delete (account);
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+static void
+accounts_dialog_button_remove_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+ GtkWidget *message_dialog;
+
+ account = accounts_dialog_model_get_selected (dialog);
+
+ if (!mc_account_is_complete (account)) {
+ accounts_dialog_model_remove_selected (dialog);
+ return;
+ }
+ message_dialog = gtk_message_dialog_new
+ (GTK_WINDOW (dialog->window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("You are about to remove your %s account!\n"
+ "Are you sure you want to proceed?"),
+ mc_account_get_display_name (account));
+
+ gtk_message_dialog_format_secondary_text
+ (GTK_MESSAGE_DIALOG (message_dialog),
+ _("Any associated conversations and chat rooms will NOT be "
+ "removed if you decide to proceed.\n"
+ "\n"
+ "Should you decide to add the account back at a later time, "
+ "they will still be available."));
+
+ gtk_dialog_add_button (GTK_DIALOG (message_dialog),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_NO);
+ gtk_dialog_add_button (GTK_DIALOG (message_dialog),
+ GTK_STOCK_REMOVE,
+ GTK_RESPONSE_YES);
+
+ g_signal_connect (message_dialog, "response",
+ G_CALLBACK (accounts_dialog_remove_response_cb),
+ account);
+
+ gtk_widget_show (message_dialog);
+}
+
+static void
+accounts_dialog_treeview_row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GossipAccountsDialog *dialog)
+{
+
+ accounts_dialog_button_connect_clicked_cb (dialog->button_connect,
+ dialog);
+}
+
+static void
+accounts_dialog_response_cb (GtkWidget *widget,
+ gint response,
+ GossipAccountsDialog *dialog)
+{
+ gtk_widget_destroy (widget);
+}
+
+static void
+accounts_dialog_destroy_cb (GtkWidget *widget,
+ GossipAccountsDialog *dialog)
+{
+ GList *accounts, *l;
+
+ /* Disconnect signals */
+ g_signal_handlers_disconnect_by_func (dialog->monitor,
+ accounts_dialog_account_added_cb,
+ dialog);
+ g_signal_handlers_disconnect_by_func (dialog->monitor,
+ accounts_dialog_account_removed_cb,
+ dialog);
+ dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (dialog->mc),
+ "AccountStatusChanged",
+ G_CALLBACK (accounts_dialog_status_changed_cb),
+ dialog);
+
+ /* Delete incomplete accounts */
+ accounts = mc_accounts_list ();
+ for (l = accounts; l; l = l->next) {
+ McAccount *account;
+
+ account = l->data;
+ if (!mc_account_is_complete (account)) {
+ /* FIXME: Warn the user the account is not complete
+ * and is going to be removed. */
+ mc_account_delete (account);
+ }
+
+ g_object_unref (account);
+ }
+ g_list_free (accounts);
+
+ if (dialog->connecting_id) {
+ g_source_remove (dialog->connecting_id);
+ }
+
+ g_object_unref (dialog->mc);
+ g_object_unref (dialog->monitor);
+
+ g_free (dialog);
+}
+
+GtkWidget *
+gossip_accounts_dialog_show (GtkWindow *parent)
+{
+ static GossipAccountsDialog *dialog = NULL;
+ GladeXML *glade;
+ GtkWidget *bbox;
+ GtkWidget *button_close;
+
+ if (dialog) {
+ gtk_window_present (GTK_WINDOW (dialog->window));
+ return dialog->window;
+ }
+
+ dialog = g_new0 (GossipAccountsDialog, 1);
+
+ glade = gossip_glade_get_file ("gossip-accounts-dialog.glade",
+ "accounts_dialog",
+ NULL,
+ "accounts_dialog", &dialog->window,
+ "vbox_details", &dialog->vbox_details,
+ "frame_no_account", &dialog->frame_no_account,
+ "label_no_account", &dialog->label_no_account,
+ "label_no_account_blurb", &dialog->label_no_account_blurb,
+ "alignment_settings", &dialog->alignment_settings,
+ "dialog-action_area", &bbox,
+ "treeview", &dialog->treeview,
+ "frame_new_account", &dialog->frame_new_account,
+ "entry_name", &dialog->entry_name,
+ "table_new_account", &dialog->table_new_account,
+ "button_create", &dialog->button_create,
+ "button_back", &dialog->button_back,
+ "image_type", &dialog->image_type,
+ "label_type", &dialog->label_type,
+ "label_name", &dialog->label_name,
+ "button_remove", &dialog->button_remove,
+ "button_connect", &dialog->button_connect,
+ "button_close", &button_close,
+ NULL);
+
+ gossip_glade_connect (glade,
+ dialog,
+ "accounts_dialog", "destroy", accounts_dialog_destroy_cb,
+ "accounts_dialog", "response", accounts_dialog_response_cb,
+ "button_create", "clicked", accounts_dialog_button_create_clicked_cb,
+ "button_back", "clicked", accounts_dialog_button_back_clicked_cb,
+ "entry_name", "changed", accounts_dialog_entry_name_changed_cb,
+ "treeview", "row-activated", accounts_dialog_treeview_row_activated_cb,
+ "button_connect", "clicked", accounts_dialog_button_connect_clicked_cb,
+ "button_add", "clicked", accounts_dialog_button_add_clicked_cb,
+ "button_remove", "clicked", accounts_dialog_button_remove_clicked_cb,
+ NULL);
+
+ g_object_add_weak_pointer (G_OBJECT (dialog->window), (gpointer) &dialog);
+
+ g_object_unref (glade);
+
+ /* Create profile chooser */
+ dialog->combobox_profile = gossip_profile_chooser_new ();
+ gtk_table_attach_defaults (GTK_TABLE (dialog->table_new_account),
+ dialog->combobox_profile,
+ 1, 2,
+ 0, 1);
+ gtk_widget_show (dialog->combobox_profile);
+
+ /* Set up signalling */
+ dialog->mc = gossip_mission_control_new ();
+ dialog->monitor = mc_account_monitor_new ();
+
+ /* FIXME: connect account-enabled/disabled too */
+ g_signal_connect (dialog->monitor, "account-created",
+ G_CALLBACK (accounts_dialog_account_added_cb),
+ dialog);
+ g_signal_connect (dialog->monitor, "account-deleted",
+ G_CALLBACK (accounts_dialog_account_removed_cb),
+ dialog);
+ dbus_g_proxy_connect_signal (DBUS_G_PROXY (dialog->mc), "AccountStatusChanged",
+ G_CALLBACK (accounts_dialog_status_changed_cb),
+ dialog, NULL);
+
+ accounts_dialog_model_setup (dialog);
+ accounts_dialog_setup (dialog);
+ accounts_dialog_model_select_first (dialog);
+
+ if (parent) {
+ gtk_window_set_transient_for (GTK_WINDOW (dialog->window),
+ GTK_WINDOW (parent));
+ }
+
+ gtk_widget_show (dialog->window);
+
+ return dialog->window;
+}
+
diff --git a/trunk/libempathy-gtk/gossip-accounts-dialog.glade b/trunk/libempathy-gtk/gossip-accounts-dialog.glade
new file mode 100644
index 000000000..58c94e694
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-accounts-dialog.glade
@@ -0,0 +1,757 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkDialog" id="accounts_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Accounts</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="button_close">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox146">
+ <property name="border_width">6</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox195">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow17">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="treeview">
+ <property name="height_request">200</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">False</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox196">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkButton" id="button_connect">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-connect</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox148">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkButton" id="button_add">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-add</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="button_remove">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-remove</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox214">
+ <property name="width_request">415</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox_details">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+
+ <child>
+ <widget class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">20</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox213">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkTable" id="table14">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+
+ <child>
+ <widget class="GtkImage" id="image_type">
+ <property name="visible">True</property>
+ <property name="stock">gtk-cut</property>
+ <property name="icon_size">6</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label_name">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Imendio </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">0</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label_type">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Jabber</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label598">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Account&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkFrame" id="frame2">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment_settings">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">20</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label599">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Settings&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkFrame" id="frame_new_account">
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment29">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">20</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox216">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkTable" id="table_new_account">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+
+ <child>
+ <widget class="GtkLabel" id="label640">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Type:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label638">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Name:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_name</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry_name">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">A unique name for this account to identify it personally to you.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox181">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkButton" id="button_create">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">0</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child>
+ <widget class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Cr_eate</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="button_back">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-go-back</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label643">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;New Account&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkFrame" id="frame_no_account">
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment21">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkLabel" id="label_no_account_blurb">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">To add a new account, you can click on the 'Add' button and a new entry will be created for you to started configuring.
+
+If you do not want to add an account, simply click on the account you want to configure in the list on the left.</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label_no_account">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;No Account Selected&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/trunk/libempathy-gtk/gossip-accounts-dialog.h b/trunk/libempathy-gtk/gossip-accounts-dialog.h
new file mode 100644
index 000000000..5b058e6a4
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-accounts-dialog.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_ACCOUNTS_DIALOG_H__
+#define __GOSSIP_ACCOUNTS_DIALOG_H__
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+GtkWidget *gossip_accounts_dialog_show (GtkWindow *parent);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_ACCOUNTS_DIALOG_H__ */
diff --git a/trunk/libempathy-gtk/gossip-cell-renderer-expander.c b/trunk/libempathy-gtk/gossip-cell-renderer-expander.c
new file mode 100644
index 000000000..e116ace7b
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-cell-renderer-expander.c
@@ -0,0 +1,482 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Kristian Rietveld <kris@imendio.com>
+ */
+
+/* To do:
+ * - should probably cancel animation if model changes
+ * - need to handle case where node-in-animation is removed
+ * - it only handles a single animation at a time; but I guess users
+ * aren't fast enough to trigger two or more animations at once anyway :P
+ * (could guard for this by just cancelling the "old" animation, and
+ * start the new one).
+ */
+
+#include <gtk/gtktreeview.h>
+
+#include "gossip-cell-renderer-expander.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CELL_RENDERER_EXPANDER, GossipCellRendererExpanderPriv))
+
+static void gossip_cell_renderer_expander_init (GossipCellRendererExpander *expander);
+static void gossip_cell_renderer_expander_class_init (GossipCellRendererExpanderClass *klass);
+static void gossip_cell_renderer_expander_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gossip_cell_renderer_expander_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gossip_cell_renderer_expander_finalize (GObject *object);
+static void gossip_cell_renderer_expander_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height);
+static void gossip_cell_renderer_expander_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags);
+static gboolean gossip_cell_renderer_expander_activate (GtkCellRenderer *cell,
+ GdkEvent *event,
+ GtkWidget *widget,
+ const gchar *path,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+
+enum {
+ PROP_0,
+ PROP_EXPANDER_STYLE,
+ PROP_EXPANDER_SIZE,
+ PROP_ACTIVATABLE
+};
+
+typedef struct _GossipCellRendererExpanderPriv GossipCellRendererExpanderPriv;
+
+struct _GossipCellRendererExpanderPriv {
+ GtkExpanderStyle expander_style;
+ gint expander_size;
+
+ GtkTreeView *animation_view;
+ GtkTreeRowReference *animation_node;
+ GtkExpanderStyle animation_style;
+ guint animation_timeout;
+ GdkRectangle animation_area;
+
+ guint activatable : 1;
+ guint animation_expanding : 1;
+};
+
+G_DEFINE_TYPE (GossipCellRendererExpander, gossip_cell_renderer_expander, GTK_TYPE_CELL_RENDERER)
+
+static void
+gossip_cell_renderer_expander_init (GossipCellRendererExpander *expander)
+{
+ GossipCellRendererExpanderPriv *priv;
+
+ priv = GET_PRIV (expander);
+
+ priv->expander_style = GTK_EXPANDER_COLLAPSED;
+ priv->expander_size = 12;
+ priv->activatable = TRUE;
+ priv->animation_node = NULL;
+
+ GTK_CELL_RENDERER (expander)->xpad = 2;
+ GTK_CELL_RENDERER (expander)->ypad = 2;
+ GTK_CELL_RENDERER (expander)->mode = GTK_CELL_RENDERER_MODE_ACTIVATABLE;
+}
+
+static void
+gossip_cell_renderer_expander_class_init (GossipCellRendererExpanderClass *klass)
+{
+ GObjectClass *object_class;
+ GtkCellRendererClass *cell_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ cell_class = GTK_CELL_RENDERER_CLASS (klass);
+
+ object_class->finalize = gossip_cell_renderer_expander_finalize;
+
+ object_class->get_property = gossip_cell_renderer_expander_get_property;
+ object_class->set_property = gossip_cell_renderer_expander_set_property;
+
+ cell_class->get_size = gossip_cell_renderer_expander_get_size;
+ cell_class->render = gossip_cell_renderer_expander_render;
+ cell_class->activate = gossip_cell_renderer_expander_activate;
+
+ g_object_class_install_property (object_class,
+ PROP_EXPANDER_STYLE,
+ g_param_spec_enum ("expander-style",
+ "Expander Style",
+ "Style to use when painting the expander",
+ GTK_TYPE_EXPANDER_STYLE,
+ GTK_EXPANDER_COLLAPSED,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_EXPANDER_SIZE,
+ g_param_spec_int ("expander-size",
+ "Expander Size",
+ "The size of the expander",
+ 0,
+ G_MAXINT,
+ 12,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_ACTIVATABLE,
+ g_param_spec_boolean ("activatable",
+ "Activatable",
+ "The expander can be activated",
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (GossipCellRendererExpanderPriv));
+}
+
+static void
+gossip_cell_renderer_expander_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+
+ expander = GOSSIP_CELL_RENDERER_EXPANDER (object);
+ priv = GET_PRIV (expander);
+
+ switch (param_id) {
+ case PROP_EXPANDER_STYLE:
+ g_value_set_enum (value, priv->expander_style);
+ break;
+
+ case PROP_EXPANDER_SIZE:
+ g_value_set_int (value, priv->expander_size);
+ break;
+
+ case PROP_ACTIVATABLE:
+ g_value_set_boolean (value, priv->activatable);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+gossip_cell_renderer_expander_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+
+ expander = GOSSIP_CELL_RENDERER_EXPANDER (object);
+ priv = GET_PRIV (expander);
+
+ switch (param_id) {
+ case PROP_EXPANDER_STYLE:
+ priv->expander_style = g_value_get_enum (value);
+ break;
+
+ case PROP_EXPANDER_SIZE:
+ priv->expander_size = g_value_get_int (value);
+ break;
+
+ case PROP_ACTIVATABLE:
+ priv->activatable = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+gossip_cell_renderer_expander_finalize (GObject *object)
+{
+ GossipCellRendererExpanderPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ if (priv->animation_timeout) {
+ g_source_remove (priv->animation_timeout);
+ priv->animation_timeout = 0;
+ }
+
+ if (priv->animation_node) {
+ gtk_tree_row_reference_free (priv->animation_node);
+ }
+
+ (* G_OBJECT_CLASS (gossip_cell_renderer_expander_parent_class)->finalize) (object);
+}
+
+GtkCellRenderer *
+gossip_cell_renderer_expander_new (void)
+{
+ return g_object_new (GOSSIP_TYPE_CELL_RENDERER_EXPANDER, NULL);
+}
+
+static void
+gossip_cell_renderer_expander_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+
+ expander = (GossipCellRendererExpander*) cell;
+ priv = GET_PRIV (expander);
+
+ if (cell_area) {
+ if (x_offset) {
+ *x_offset = cell->xalign * (cell_area->width - (priv->expander_size + (2 * cell->xpad)));
+ *x_offset = MAX (*x_offset, 0);
+ }
+
+ if (y_offset) {
+ *y_offset = cell->yalign * (cell_area->height - (priv->expander_size + (2 * cell->ypad)));
+ *y_offset = MAX (*y_offset, 0);
+ }
+ } else {
+ if (x_offset)
+ *x_offset = 0;
+
+ if (y_offset)
+ *y_offset = 0;
+ }
+
+ if (width)
+ *width = cell->xpad * 2 + priv->expander_size;
+
+ if (height)
+ *height = cell->ypad * 2 + priv->expander_size;
+}
+
+static void
+gossip_cell_renderer_expander_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+ GtkExpanderStyle expander_style;
+ gint x_offset, y_offset;
+
+ expander = (GossipCellRendererExpander*) cell;
+ priv = GET_PRIV (expander);
+
+ if (priv->animation_node) {
+ GtkTreePath *path;
+ GdkRectangle rect;
+
+ /* Not sure if I like this ... */
+ path = gtk_tree_row_reference_get_path (priv->animation_node);
+ gtk_tree_view_get_background_area (priv->animation_view, path,
+ NULL, &rect);
+ gtk_tree_path_free (path);
+
+ if (background_area->y == rect.y)
+ expander_style = priv->animation_style;
+ else
+ expander_style = priv->expander_style;
+ } else
+ expander_style = priv->expander_style;
+
+ gossip_cell_renderer_expander_get_size (cell, widget, cell_area,
+ &x_offset, &y_offset,
+ NULL, NULL);
+
+ gtk_paint_expander (widget->style,
+ window,
+ GTK_STATE_NORMAL,
+ expose_area,
+ widget,
+ "treeview",
+ cell_area->x + x_offset + cell->xpad + priv->expander_size / 2,
+ cell_area->y + y_offset + cell->ypad + priv->expander_size / 2,
+ expander_style);
+}
+
+static void
+invalidate_node (GtkTreeView *tree_view,
+ GtkTreePath *path)
+{
+ GdkWindow *bin_window;
+ GdkRectangle rect;
+
+ bin_window = gtk_tree_view_get_bin_window (tree_view);
+
+ gtk_tree_view_get_background_area (tree_view, path, NULL, &rect);
+
+ rect.x = 0;
+ rect.width = GTK_WIDGET (tree_view)->allocation.width;
+
+ gdk_window_invalidate_rect (bin_window, &rect, TRUE);
+}
+
+static gboolean
+do_animation (GossipCellRendererExpander *expander)
+{
+ GossipCellRendererExpanderPriv *priv;
+ GtkTreePath *path;
+ gboolean done = FALSE;
+
+ priv = GET_PRIV (expander);
+
+ if (priv->animation_expanding) {
+ if (priv->animation_style == GTK_EXPANDER_SEMI_COLLAPSED)
+ priv->animation_style = GTK_EXPANDER_SEMI_EXPANDED;
+ else if (priv->animation_style == GTK_EXPANDER_SEMI_EXPANDED) {
+ priv->animation_style = GTK_EXPANDER_EXPANDED;
+ done = TRUE;
+ }
+ } else {
+ if (priv->animation_style == GTK_EXPANDER_SEMI_EXPANDED)
+ priv->animation_style = GTK_EXPANDER_SEMI_COLLAPSED;
+ else if (priv->animation_style == GTK_EXPANDER_SEMI_COLLAPSED) {
+ priv->animation_style = GTK_EXPANDER_COLLAPSED;
+ done = TRUE;
+ }
+ }
+
+ path = gtk_tree_row_reference_get_path (priv->animation_node);
+ invalidate_node (priv->animation_view, path);
+ gtk_tree_path_free (path);
+
+ if (done) {
+ gtk_tree_row_reference_free (priv->animation_node);
+ priv->animation_node = NULL;
+ priv->animation_timeout = 0;
+ }
+
+ return !done;
+}
+
+static gboolean
+animation_timeout (gpointer data)
+{
+ gboolean retval;
+
+ GDK_THREADS_ENTER ();
+
+ retval = do_animation (data);
+
+ GDK_THREADS_LEAVE ();
+
+ return retval;
+}
+
+static void
+gossip_cell_renderer_expander_start_animation (GossipCellRendererExpander *expander,
+ GtkTreeView *tree_view,
+ GtkTreePath *path,
+ gboolean expanding,
+ GdkRectangle *background_area)
+{
+ GossipCellRendererExpanderPriv *priv;
+
+ priv = GET_PRIV (expander);
+
+ if (expanding) {
+ priv->animation_style = GTK_EXPANDER_SEMI_COLLAPSED;
+ } else {
+ priv->animation_style = GTK_EXPANDER_SEMI_EXPANDED;
+ }
+
+ invalidate_node (tree_view, path);
+
+ priv->animation_expanding = expanding;
+ priv->animation_view = tree_view;
+ priv->animation_node = gtk_tree_row_reference_new (gtk_tree_view_get_model (tree_view), path);
+ priv->animation_timeout = g_timeout_add (50, animation_timeout, expander);
+}
+
+static gboolean
+gossip_cell_renderer_expander_activate (GtkCellRenderer *cell,
+ GdkEvent *event,
+ GtkWidget *widget,
+ const gchar *path_string,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+ GtkTreePath *path;
+ gboolean animate;
+ gboolean expanding;
+
+ expander = GOSSIP_CELL_RENDERER_EXPANDER (cell);
+ priv = GET_PRIV (cell);
+
+ if (!GTK_IS_TREE_VIEW (widget) || !priv->activatable)
+ return FALSE;
+
+ path = gtk_tree_path_new_from_string (path_string);
+
+ if (gtk_tree_path_get_depth (path) > 1) {
+ gtk_tree_path_free (path);
+ return TRUE;
+ }
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (widget)),
+ "gtk-enable-animations", &animate,
+ NULL);
+
+ if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
+ gtk_tree_view_collapse_row (GTK_TREE_VIEW (widget), path);
+ expanding = FALSE;
+ } else {
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (widget), path, FALSE);
+ expanding = TRUE;
+ }
+
+ if (animate) {
+ gossip_cell_renderer_expander_start_animation (expander,
+ GTK_TREE_VIEW (widget),
+ path,
+ expanding,
+ background_area);
+ }
+
+ gtk_tree_path_free (path);
+
+ return TRUE;
+}
diff --git a/trunk/libempathy-gtk/gossip-cell-renderer-expander.h b/trunk/libempathy-gtk/gossip-cell-renderer-expander.h
new file mode 100644
index 000000000..7df474668
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-cell-renderer-expander.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Kristian Rietveld <kris@imendio.com>
+ */
+
+#ifndef __GOSSIP_CELL_RENDERER_EXPANDER_H__
+#define __GOSSIP_CELL_RENDERER_EXPANDER_H__
+
+#include <gtk/gtkcellrenderer.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CELL_RENDERER_EXPANDER (gossip_cell_renderer_expander_get_type ())
+#define GOSSIP_CELL_RENDERER_EXPANDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOSSIP_TYPE_CELL_RENDERER_EXPANDER, GossipCellRendererExpander))
+#define GOSSIP_CELL_RENDERER_EXPANDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GOSSIP_TYPE_CELL_RENDERER_EXPANDER, GossipCellRendererExpanderClass))
+#define GOSSIP_IS_CELL_RENDERER_EXPANDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOSSIP_TYPE_CELL_RENDERER_EXPANDER))
+#define GOSSIP_IS_CELL_RENDERER_EXPANDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOSSIP_TYPE_CELL_RENDERER_EXPANDER))
+#define GOSSIP_CELL_RENDERER_EXPANDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GOSSIP_TYPE_CELL_RENDERER_EXPANDER, GossipCellRendererExpanderClass))
+
+typedef struct _GossipCellRendererExpander GossipCellRendererExpander;
+typedef struct _GossipCellRendererExpanderClass GossipCellRendererExpanderClass;
+
+struct _GossipCellRendererExpander {
+ GtkCellRenderer parent;
+};
+
+struct _GossipCellRendererExpanderClass {
+ GtkCellRendererClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+GType gossip_cell_renderer_expander_get_type (void) G_GNUC_CONST;
+GtkCellRenderer *gossip_cell_renderer_expander_new (void);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CELL_RENDERER_EXPANDER_H__ */
diff --git a/trunk/libempathy-gtk/gossip-cell-renderer-text.c b/trunk/libempathy-gtk/gossip-cell-renderer-text.c
new file mode 100644
index 000000000..2b54eedf5
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-cell-renderer-text.c
@@ -0,0 +1,368 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gossip-cell-renderer-text.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CELL_RENDERER_TEXT, GossipCellRendererTextPriv))
+
+struct _GossipCellRendererTextPriv {
+ gchar *name;
+ gchar *status;
+ gboolean is_group;
+
+ gboolean is_valid;
+ gboolean is_selected;
+
+ gboolean show_status;
+};
+
+static void gossip_cell_renderer_text_class_init (GossipCellRendererTextClass *klass);
+static void gossip_cell_renderer_text_init (GossipCellRendererText *cell);
+static void cell_renderer_text_finalize (GObject *object);
+static void cell_renderer_text_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void cell_renderer_text_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void cell_renderer_text_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height);
+static void cell_renderer_text_render (GtkCellRenderer *cell,
+ GdkDrawable *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags);
+static void cell_renderer_text_update_text (GossipCellRendererText *cell,
+ GtkWidget *widget,
+ gboolean selected);
+
+/* Properties */
+enum {
+ PROP_0,
+ PROP_NAME,
+ PROP_STATUS,
+ PROP_IS_GROUP,
+ PROP_SHOW_STATUS,
+};
+
+G_DEFINE_TYPE (GossipCellRendererText, gossip_cell_renderer_text, GTK_TYPE_CELL_RENDERER_TEXT);
+
+static void
+gossip_cell_renderer_text_class_init (GossipCellRendererTextClass *klass)
+{
+ GObjectClass *object_class;
+ GtkCellRendererClass *cell_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ cell_class = GTK_CELL_RENDERER_CLASS (klass);
+
+ object_class->finalize = cell_renderer_text_finalize;
+
+ object_class->get_property = cell_renderer_text_get_property;
+ object_class->set_property = cell_renderer_text_set_property;
+
+ cell_class->get_size = cell_renderer_text_get_size;
+ cell_class->render = cell_renderer_text_render;
+
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "Name",
+ "Contact name",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_STATUS,
+ g_param_spec_string ("status",
+ "Status",
+ "Contact status string",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_IS_GROUP,
+ g_param_spec_boolean ("is_group",
+ "Is group",
+ "Whether this cell is a group",
+ FALSE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SHOW_STATUS,
+ g_param_spec_boolean ("show-status",
+ "Show status",
+ "Whether to show the status line",
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (GossipCellRendererTextPriv));
+}
+
+static void
+gossip_cell_renderer_text_init (GossipCellRendererText *cell)
+{
+ GossipCellRendererTextPriv *priv;
+
+ priv = GET_PRIV (cell);
+
+ g_object_set (cell,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ NULL);
+
+ priv->name = g_strdup ("");
+ priv->status = g_strdup ("");
+ priv->show_status = TRUE;
+}
+
+static void
+cell_renderer_text_finalize (GObject *object)
+{
+ GossipCellRendererText *cell;
+ GossipCellRendererTextPriv *priv;
+
+ cell = GOSSIP_CELL_RENDERER_TEXT (object);
+ priv = GET_PRIV (cell);
+
+ g_free (priv->name);
+ g_free (priv->status);
+
+ (G_OBJECT_CLASS (gossip_cell_renderer_text_parent_class)->finalize) (object);
+}
+
+static void
+cell_renderer_text_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GossipCellRendererText *cell;
+ GossipCellRendererTextPriv *priv;
+
+ cell = GOSSIP_CELL_RENDERER_TEXT (object);
+ priv = GET_PRIV (cell);
+
+ switch (param_id) {
+ case PROP_NAME:
+ g_value_set_string (value, priv->name);
+ break;
+ case PROP_STATUS:
+ g_value_set_string (value, priv->status);
+ break;
+ case PROP_IS_GROUP:
+ g_value_set_boolean (value, priv->is_group);
+ break;
+ case PROP_SHOW_STATUS:
+ g_value_set_boolean (value, priv->show_status);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+cell_renderer_text_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GossipCellRendererText *cell;
+ GossipCellRendererTextPriv *priv;
+ const gchar *str;
+
+ cell = GOSSIP_CELL_RENDERER_TEXT (object);
+ priv = GET_PRIV (cell);
+
+ switch (param_id) {
+ case PROP_NAME:
+ g_free (priv->name);
+ str = g_value_get_string (value);
+ priv->name = g_strdup (str ? str : "");
+ g_strdelimit (priv->name, "\n\r\t", ' ');
+ priv->is_valid = FALSE;
+ break;
+ case PROP_STATUS:
+ g_free (priv->status);
+ str = g_value_get_string (value);
+ priv->status = g_strdup (str ? str : "");
+ g_strdelimit (priv->status, "\n\r\t", ' ');
+ priv->is_valid = FALSE;
+ break;
+ case PROP_IS_GROUP:
+ priv->is_group = g_value_get_boolean (value);
+ priv->is_valid = FALSE;
+ break;
+ case PROP_SHOW_STATUS:
+ priv->show_status = g_value_get_boolean (value);
+ priv->is_valid = FALSE;
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+cell_renderer_text_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height)
+{
+ GossipCellRendererText *celltext;
+ GossipCellRendererTextPriv *priv;
+
+ celltext = GOSSIP_CELL_RENDERER_TEXT (cell);
+ priv = GET_PRIV (cell);
+
+ /* Only update if not already valid so we get the right size. */
+ cell_renderer_text_update_text (celltext, widget, priv->is_selected);
+
+ (GTK_CELL_RENDERER_CLASS (gossip_cell_renderer_text_parent_class)->get_size) (cell, widget,
+ cell_area,
+ x_offset, y_offset,
+ width, height);
+}
+
+static void
+cell_renderer_text_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags)
+{
+ GossipCellRendererText *celltext;
+
+ celltext = GOSSIP_CELL_RENDERER_TEXT (cell);
+
+ cell_renderer_text_update_text (celltext,
+ widget,
+ (flags & GTK_CELL_RENDERER_SELECTED));
+
+ (GTK_CELL_RENDERER_CLASS (gossip_cell_renderer_text_parent_class)->render) (
+ cell, window,
+ widget,
+ background_area,
+ cell_area,
+ expose_area, flags);
+}
+
+static void
+cell_renderer_text_update_text (GossipCellRendererText *cell,
+ GtkWidget *widget,
+ gboolean selected)
+{
+ GossipCellRendererTextPriv *priv;
+ PangoAttrList *attr_list;
+ PangoAttribute *attr_color, *attr_style, *attr_size;
+ GtkStyle *style;
+ gchar *str;
+
+ priv = GET_PRIV (cell);
+
+ if (priv->is_valid && priv->is_selected == selected) {
+ return;
+ }
+
+ if (priv->is_group) {
+ g_object_set (cell,
+ "visible", TRUE,
+ "weight", PANGO_WEIGHT_BOLD,
+ "text", priv->name,
+ "attributes", NULL,
+ "xpad", 1,
+ "ypad", 1,
+ NULL);
+
+ priv->is_selected = selected;
+ priv->is_valid = TRUE;
+ return;
+ }
+
+ style = gtk_widget_get_style (widget);
+
+ attr_list = pango_attr_list_new ();
+
+ attr_style = pango_attr_style_new (PANGO_STYLE_ITALIC);
+ attr_style->start_index = strlen (priv->name) + 1;
+ attr_style->end_index = -1;
+ pango_attr_list_insert (attr_list, attr_style);
+
+ if (!selected) {
+ GdkColor color;
+
+ color = style->text_aa[GTK_STATE_NORMAL];
+
+ attr_color = pango_attr_foreground_new (color.red, color.green, color.blue);
+ attr_color->start_index = attr_style->start_index;
+ attr_color->end_index = -1;
+ pango_attr_list_insert (attr_list, attr_color);
+ }
+
+ attr_size = pango_attr_size_new (pango_font_description_get_size (style->font_desc) / 1.2);
+
+ attr_size->start_index = attr_style->start_index;
+ attr_size->end_index = -1;
+ pango_attr_list_insert (attr_list, attr_size);
+
+ if (!priv->status || !priv->status[0] || !priv->show_status) {
+ str = g_strdup (priv->name);
+ } else {
+ str = g_strdup_printf ("%s\n%s", priv->name, priv->status);
+ }
+
+ g_object_set (cell,
+ "visible", TRUE,
+ "weight", PANGO_WEIGHT_NORMAL,
+ "text", str,
+ "attributes", attr_list,
+ "xpad", 0,
+ "ypad", 1,
+ NULL);
+
+ g_free (str);
+ pango_attr_list_unref (attr_list);
+
+ priv->is_selected = selected;
+ priv->is_valid = TRUE;
+}
+
+GtkCellRenderer *
+gossip_cell_renderer_text_new (void)
+{
+ return g_object_new (GOSSIP_TYPE_CELL_RENDERER_TEXT, NULL);
+}
diff --git a/trunk/libempathy-gtk/gossip-cell-renderer-text.h b/trunk/libempathy-gtk/gossip-cell-renderer-text.h
new file mode 100644
index 000000000..9b5a413b7
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-cell-renderer-text.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ */
+
+#ifndef __GOSSIP_CELL_RENDERER_TEXT_H__
+#define __GOSSIP_CELL_RENDERER_TEXT_H__
+
+#include <gtk/gtkcellrenderertext.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CELL_RENDERER_TEXT (gossip_cell_renderer_text_get_type ())
+#define GOSSIP_CELL_RENDERER_TEXT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CELL_RENDERER_TEXT, GossipCellRendererText))
+#define GOSSIP_CELL_RENDERER_TEXT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GOSSIP_TYPE_CELL_RENDERER_TEXT, GossipCellRendererTextClass))
+#define GOSSIP_IS_CELL_RENDERER_TEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CELL_RENDERER_TEXT))
+#define GOSSIP_IS_CELL_RENDERER_TEXT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CELL_RENDERER_TEXT))
+#define GOSSIP_CELL_RENDERER_TEXT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CELL_RENDERER_TEXT, GossipCellRendererTextClass))
+
+typedef struct _GossipCellRendererText GossipCellRendererText;
+typedef struct _GossipCellRendererTextClass GossipCellRendererTextClass;
+typedef struct _GossipCellRendererTextPriv GossipCellRendererTextPriv;
+
+struct _GossipCellRendererText {
+ GtkCellRendererText parent;
+
+ GossipCellRendererTextPriv *priv;
+};
+
+struct _GossipCellRendererTextClass {
+ GtkCellRendererTextClass parent_class;
+};
+
+GType gossip_cell_renderer_text_get_type (void) G_GNUC_CONST;
+GtkCellRenderer * gossip_cell_renderer_text_new (void);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CELL_RENDERER_TEXT_H__ */
diff --git a/trunk/libempathy-gtk/gossip-chat-manager.loT b/trunk/libempathy-gtk/gossip-chat-manager.loT
new file mode 100644
index 000000000..3d0d3ab0d
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-chat-manager.loT
@@ -0,0 +1,7 @@
+# gossip-chat-manager.lo - a libtool object file
+# Generated by ltmain.sh - GNU libtool 1.5.22 Debian 1.5.22-4 (1.1220.2.365 2005/12/18 22:14:06)
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# Name of the PIC object.
diff --git a/trunk/libempathy-gtk/gossip-chat-view.c b/trunk/libempathy-gtk/gossip-chat-view.c
new file mode 100644
index 000000000..24770f5de
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-chat-view.c
@@ -0,0 +1,2208 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <string.h>
+#include <time.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkimagemenuitem.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtksizegroup.h>
+#include <glade/glade.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+#include <libempathy/gossip-utils.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-conf.h>
+
+#include "gossip-chat-view.h"
+#include "gossip-chat.h"
+#include "gossip-preferences.h"
+#include "gossip-theme-manager.h"
+#include "gossip-ui-utils.h"
+
+#define DEBUG_DOMAIN "ChatView"
+
+/* Number of seconds between timestamps when using normal mode, 5 minutes. */
+#define TIMESTAMP_INTERVAL 300
+
+#define MAX_LINES 800
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHAT_VIEW, GossipChatViewPriv))
+
+typedef enum {
+ BLOCK_TYPE_NONE,
+ BLOCK_TYPE_SELF,
+ BLOCK_TYPE_OTHER,
+ BLOCK_TYPE_EVENT,
+ BLOCK_TYPE_TIME,
+ BLOCK_TYPE_INVITE
+} BlockType;
+
+struct _GossipChatViewPriv {
+ GtkTextBuffer *buffer;
+
+ gboolean irc_style;
+ time_t last_timestamp;
+ BlockType last_block_type;
+
+ gboolean allow_scrolling;
+ gboolean is_group_chat;
+
+ GtkTextMark *find_mark_previous;
+ GtkTextMark *find_mark_next;
+ gboolean find_wrapped;
+ gboolean find_last_direction;
+
+ /* This is for the group chat so we know if the "other" last contact
+ * changed, so we know whether to insert a header or not.
+ */
+ GossipContact *last_contact;
+
+ guint notify_system_fonts_id;
+ guint notify_show_avatars_id;
+};
+
+typedef struct {
+ GossipSmiley smiley;
+ const gchar *pattern;
+} GossipSmileyPattern;
+
+static const GossipSmileyPattern smileys[] = {
+ /* Forward smileys. */
+ { GOSSIP_SMILEY_NORMAL, ":)" },
+ { GOSSIP_SMILEY_WINK, ";)" },
+ { GOSSIP_SMILEY_WINK, ";-)" },
+ { GOSSIP_SMILEY_BIGEYE, "=)" },
+ { GOSSIP_SMILEY_NOSE, ":-)" },
+ { GOSSIP_SMILEY_CRY, ":'(" },
+ { GOSSIP_SMILEY_SAD, ":(" },
+ { GOSSIP_SMILEY_SAD, ":-(" },
+ { GOSSIP_SMILEY_SCEPTICAL, ":/" },
+ { GOSSIP_SMILEY_SCEPTICAL, ":\\" },
+ { GOSSIP_SMILEY_BIGSMILE, ":D" },
+ { GOSSIP_SMILEY_BIGSMILE, ":-D" },
+ { GOSSIP_SMILEY_INDIFFERENT, ":|" },
+ { GOSSIP_SMILEY_TOUNGE, ":p" },
+ { GOSSIP_SMILEY_TOUNGE, ":-p" },
+ { GOSSIP_SMILEY_TOUNGE, ":P" },
+ { GOSSIP_SMILEY_TOUNGE, ":-P" },
+ { GOSSIP_SMILEY_TOUNGE, ";p" },
+ { GOSSIP_SMILEY_TOUNGE, ";-p" },
+ { GOSSIP_SMILEY_TOUNGE, ";P" },
+ { GOSSIP_SMILEY_TOUNGE, ";-P" },
+ { GOSSIP_SMILEY_SHOCKED, ":o" },
+ { GOSSIP_SMILEY_SHOCKED, ":-o" },
+ { GOSSIP_SMILEY_SHOCKED, ":O" },
+ { GOSSIP_SMILEY_SHOCKED, ":-O" },
+ { GOSSIP_SMILEY_COOL, "8)" },
+ { GOSSIP_SMILEY_COOL, "B)" },
+ { GOSSIP_SMILEY_SORRY, "*|" },
+ { GOSSIP_SMILEY_KISS, ":*" },
+ { GOSSIP_SMILEY_SHUTUP, ":#" },
+ { GOSSIP_SMILEY_SHUTUP, ":-#" },
+ { GOSSIP_SMILEY_YAWN, "|O" },
+ { GOSSIP_SMILEY_CONFUSED, ":S" },
+ { GOSSIP_SMILEY_CONFUSED, ":s" },
+ { GOSSIP_SMILEY_ANGEL, "<)" },
+ { GOSSIP_SMILEY_OOOH, ":x" },
+ { GOSSIP_SMILEY_LOOKAWAY, "*)" },
+ { GOSSIP_SMILEY_LOOKAWAY, "*-)" },
+ { GOSSIP_SMILEY_BLUSH, "*S" },
+ { GOSSIP_SMILEY_BLUSH, "*s" },
+ { GOSSIP_SMILEY_BLUSH, "*$" },
+ { GOSSIP_SMILEY_COOLBIGSMILE, "8D" },
+ { GOSSIP_SMILEY_ANGRY, ":@" },
+ { GOSSIP_SMILEY_BOSS, "@)" },
+ { GOSSIP_SMILEY_MONKEY, "#)" },
+ { GOSSIP_SMILEY_SILLY, "O)" },
+ { GOSSIP_SMILEY_SICK, "+o(" },
+
+ /* Backward smileys. */
+ { GOSSIP_SMILEY_NORMAL, "(:" },
+ { GOSSIP_SMILEY_WINK, "(;" },
+ { GOSSIP_SMILEY_WINK, "(-;" },
+ { GOSSIP_SMILEY_BIGEYE, "(=" },
+ { GOSSIP_SMILEY_NOSE, "(-:" },
+ { GOSSIP_SMILEY_CRY, ")':" },
+ { GOSSIP_SMILEY_SAD, "):" },
+ { GOSSIP_SMILEY_SAD, ")-:" },
+ { GOSSIP_SMILEY_SCEPTICAL, "/:" },
+ { GOSSIP_SMILEY_SCEPTICAL, "//:" },
+ { GOSSIP_SMILEY_INDIFFERENT, "|:" },
+ { GOSSIP_SMILEY_TOUNGE, "d:" },
+ { GOSSIP_SMILEY_TOUNGE, "d-:" },
+ { GOSSIP_SMILEY_TOUNGE, "d;" },
+ { GOSSIP_SMILEY_TOUNGE, "d-;" },
+ { GOSSIP_SMILEY_SHOCKED, "o:" },
+ { GOSSIP_SMILEY_SHOCKED, "O:" },
+ { GOSSIP_SMILEY_COOL, "(8" },
+ { GOSSIP_SMILEY_COOL, "(B" },
+ { GOSSIP_SMILEY_SORRY, "|*" },
+ { GOSSIP_SMILEY_KISS, "*:" },
+ { GOSSIP_SMILEY_SHUTUP, "#:" },
+ { GOSSIP_SMILEY_SHUTUP, "#-:" },
+ { GOSSIP_SMILEY_YAWN, "O|" },
+ { GOSSIP_SMILEY_CONFUSED, "S:" },
+ { GOSSIP_SMILEY_CONFUSED, "s:" },
+ { GOSSIP_SMILEY_ANGEL, "(>" },
+ { GOSSIP_SMILEY_OOOH, "x:" },
+ { GOSSIP_SMILEY_LOOKAWAY, "(*" },
+ { GOSSIP_SMILEY_LOOKAWAY, "(-*" },
+ { GOSSIP_SMILEY_BLUSH, "S*" },
+ { GOSSIP_SMILEY_BLUSH, "s*" },
+ { GOSSIP_SMILEY_BLUSH, "$*" },
+ { GOSSIP_SMILEY_ANGRY, "@:" },
+ { GOSSIP_SMILEY_BOSS, "(@" },
+ { GOSSIP_SMILEY_MONKEY, "#)" },
+ { GOSSIP_SMILEY_SILLY, "(O" },
+ { GOSSIP_SMILEY_SICK, ")o+" }
+};
+
+static void gossip_chat_view_class_init (GossipChatViewClass *klass);
+static void gossip_chat_view_init (GossipChatView *view);
+static void chat_view_finalize (GObject *object);
+static gboolean chat_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time);
+static void chat_view_size_allocate (GtkWidget *widget,
+ GtkAllocation *alloc);
+static void chat_view_setup_tags (GossipChatView *view);
+static void chat_view_system_font_update (GossipChatView *view);
+static void chat_view_notify_system_font_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void chat_view_notify_show_avatars_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void chat_view_populate_popup (GossipChatView *view,
+ GtkMenu *menu,
+ gpointer user_data);
+static gboolean chat_view_event_cb (GossipChatView *view,
+ GdkEventMotion *event,
+ GtkTextTag *tag);
+static gboolean chat_view_url_event_cb (GtkTextTag *tag,
+ GObject *object,
+ GdkEvent *event,
+ GtkTextIter *iter,
+ GtkTextBuffer *buffer);
+static void chat_view_open_address_cb (GtkMenuItem *menuitem,
+ const gchar *url);
+static void chat_view_copy_address_cb (GtkMenuItem *menuitem,
+ const gchar *url);
+static void chat_view_clear_view_cb (GtkMenuItem *menuitem,
+ GossipChatView *view);
+static void chat_view_insert_text_with_emoticons (GtkTextBuffer *buf,
+ GtkTextIter *iter,
+ const gchar *str);
+static gboolean chat_view_is_scrolled_down (GossipChatView *view);
+static void chat_view_theme_changed_cb (GossipThemeManager *manager,
+ GossipChatView *view);
+static void chat_view_maybe_append_date_and_time (GossipChatView *view,
+ GossipMessage *msg);
+static void chat_view_append_spacing (GossipChatView *view);
+static void chat_view_append_text (GossipChatView *view,
+ const gchar *body,
+ const gchar *tag);
+static void chat_view_maybe_append_fancy_header (GossipChatView *view,
+ GossipMessage *msg);
+static void chat_view_append_irc_action (GossipChatView *view,
+ GossipMessage *msg);
+static void chat_view_append_fancy_action (GossipChatView *view,
+ GossipMessage *msg);
+static void chat_view_append_irc_message (GossipChatView *view,
+ GossipMessage *msg);
+static void chat_view_append_fancy_message (GossipChatView *view,
+ GossipMessage *msg);
+static GdkPixbuf *chat_view_pad_to_size (GdkPixbuf *pixbuf,
+ gint width,
+ gint height,
+ gint extra_padding_right);
+
+G_DEFINE_TYPE (GossipChatView, gossip_chat_view, GTK_TYPE_TEXT_VIEW);
+
+static void
+gossip_chat_view_class_init (GossipChatViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = chat_view_finalize;
+ widget_class->size_allocate = chat_view_size_allocate;
+ widget_class->drag_motion = chat_view_drag_motion;
+
+ g_type_class_add_private (object_class, sizeof (GossipChatViewPriv));
+}
+
+static void
+gossip_chat_view_init (GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+ gboolean show_avatars;
+
+ priv = GET_PRIV (view);
+
+ priv->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ priv->last_block_type = BLOCK_TYPE_NONE;
+ priv->last_timestamp = 0;
+
+ priv->allow_scrolling = TRUE;
+
+ priv->is_group_chat = FALSE;
+
+ g_object_set (view,
+ "wrap-mode", GTK_WRAP_WORD_CHAR,
+ "editable", FALSE,
+ "cursor-visible", FALSE,
+ NULL);
+
+ priv->notify_system_fonts_id =
+ gossip_conf_notify_add (gossip_conf_get (),
+ "/desktop/gnome/interface/document_font_name",
+ chat_view_notify_system_font_cb,
+ view);
+ chat_view_system_font_update (view);
+
+ priv->notify_show_avatars_id =
+ gossip_conf_notify_add (gossip_conf_get (),
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ chat_view_notify_show_avatars_cb,
+ view);
+
+ chat_view_setup_tags (view);
+
+ gossip_theme_manager_apply_saved (gossip_theme_manager_get (), view);
+
+ show_avatars = FALSE;
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ &show_avatars);
+
+ gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
+ view, show_avatars);
+
+ g_signal_connect (view,
+ "populate-popup",
+ G_CALLBACK (chat_view_populate_popup),
+ NULL);
+
+ g_signal_connect_object (gossip_theme_manager_get (),
+ "theme-changed",
+ G_CALLBACK (chat_view_theme_changed_cb),
+ view,
+ 0);
+}
+
+static void
+chat_view_finalize (GObject *object)
+{
+ GossipChatView *view;
+ GossipChatViewPriv *priv;
+
+ view = GOSSIP_CHAT_VIEW (object);
+ priv = GET_PRIV (view);
+
+ gossip_debug (DEBUG_DOMAIN, "finalize: %p", object);
+
+ gossip_conf_notify_remove (gossip_conf_get (), priv->notify_system_fonts_id);
+ gossip_conf_notify_remove (gossip_conf_get (), priv->notify_show_avatars_id);
+
+ if (priv->last_contact) {
+ g_object_unref (priv->last_contact);
+ }
+
+ G_OBJECT_CLASS (gossip_chat_view_parent_class)->finalize (object);
+}
+
+static gboolean
+chat_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ /* Don't handle drag motion, since we don't want the view to scroll as
+ * the result of dragging something across it.
+ */
+
+ return FALSE;
+}
+
+static void
+chat_view_size_allocate (GtkWidget *widget,
+ GtkAllocation *alloc)
+{
+ gboolean down;
+
+ down = chat_view_is_scrolled_down (GOSSIP_CHAT_VIEW (widget));
+
+ GTK_WIDGET_CLASS (gossip_chat_view_parent_class)->size_allocate (widget, alloc);
+
+ if (down) {
+ gossip_chat_view_scroll_down (GOSSIP_CHAT_VIEW (widget));
+ }
+}
+
+static void
+chat_view_setup_tags (GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+ GtkTextTag *tag;
+
+ priv = GET_PRIV (view);
+
+ gtk_text_buffer_create_tag (priv->buffer,
+ "cut",
+ NULL);
+
+ /* FIXME: Move to the theme and come up with something that looks a bit
+ * nicer.
+ */
+ gtk_text_buffer_create_tag (priv->buffer,
+ "highlight",
+ "background", "yellow",
+ NULL);
+
+ tag = gtk_text_buffer_create_tag (priv->buffer,
+ "link",
+ NULL);
+
+ g_signal_connect (tag,
+ "event",
+ G_CALLBACK (chat_view_url_event_cb),
+ priv->buffer);
+
+ g_signal_connect (view,
+ "motion-notify-event",
+ G_CALLBACK (chat_view_event_cb),
+ tag);
+}
+
+static void
+chat_view_system_font_update (GossipChatView *view)
+{
+ PangoFontDescription *font_description = NULL;
+ gchar *font_name;
+
+ if (gossip_conf_get_string (gossip_conf_get (),
+ "/desktop/gnome/interface/document_font_name",
+ &font_name) && font_name) {
+ font_description = pango_font_description_from_string (font_name);
+ g_free (font_name);
+ } else {
+ font_description = NULL;
+ }
+
+ gtk_widget_modify_font (GTK_WIDGET (view), font_description);
+
+ if (font_description) {
+ pango_font_description_free (font_description);
+ }
+}
+
+static void
+chat_view_notify_system_font_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ GossipChatView *view;
+ gboolean show_avatars = FALSE;
+
+ view = user_data;
+
+ chat_view_system_font_update (view);
+
+ /* Ugly, again, to adjust the vertical position of the nick... Will fix
+ * this when reworking the theme manager so that view register
+ * themselves with it instead of the other way around.
+ */
+ gossip_conf_get_bool (conf,
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ &show_avatars);
+
+ gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
+ view, show_avatars);
+}
+
+static void
+chat_view_notify_show_avatars_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ GossipChatView *view;
+ GossipChatViewPriv *priv;
+ gboolean show_avatars = FALSE;
+
+ view = user_data;
+ priv = GET_PRIV (view);
+
+ gossip_conf_get_bool (conf, key, &show_avatars);
+
+ gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
+ view, show_avatars);
+}
+
+static void
+chat_view_populate_popup (GossipChatView *view,
+ GtkMenu *menu,
+ gpointer user_data)
+{
+ GossipChatViewPriv *priv;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+ gint x, y;
+ GtkTextIter iter, start, end;
+ GtkWidget *item;
+ gchar *str = NULL;
+
+ priv = GET_PRIV (view);
+
+ /* Clear menu item */
+ if (gtk_text_buffer_get_char_count (priv->buffer) > 0) {
+ item = gtk_menu_item_new ();
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (chat_view_clear_view_cb),
+ view);
+ }
+
+ /* Link context menu items */
+ table = gtk_text_buffer_get_tag_table (priv->buffer);
+ tag = gtk_text_tag_table_lookup (table, "link");
+
+ gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
+
+ gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
+ GTK_TEXT_WINDOW_WIDGET,
+ x, y,
+ &x, &y);
+
+ gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
+
+ start = end = iter;
+
+ if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
+ gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
+ str = gtk_text_buffer_get_text (priv->buffer,
+ &start, &end, FALSE);
+ }
+
+ if (G_STR_EMPTY (str)) {
+ g_free (str);
+ return;
+ }
+
+ /* NOTE: Set data just to get the string freed when not needed. */
+ g_object_set_data_full (G_OBJECT (menu),
+ "url", str,
+ (GDestroyNotify) g_free);
+
+ item = gtk_menu_item_new ();
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (chat_view_copy_address_cb),
+ str);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (chat_view_open_address_cb),
+ str);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+}
+
+static gboolean
+chat_view_event_cb (GossipChatView *view,
+ GdkEventMotion *event,
+ GtkTextTag *tag)
+{
+ static GdkCursor *hand = NULL;
+ static GdkCursor *beam = NULL;
+ GtkTextWindowType type;
+ GtkTextIter iter;
+ GdkWindow *win;
+ gint x, y, buf_x, buf_y;
+
+ type = gtk_text_view_get_window_type (GTK_TEXT_VIEW (view),
+ event->window);
+
+ if (type != GTK_TEXT_WINDOW_TEXT) {
+ return FALSE;
+ }
+
+ /* Get where the pointer really is. */
+ win = gtk_text_view_get_window (GTK_TEXT_VIEW (view), type);
+ if (!win) {
+ return FALSE;
+ }
+
+ gdk_window_get_pointer (win, &x, &y, NULL);
+
+ /* Get the iter where the cursor is at */
+ gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view), type,
+ x, y,
+ &buf_x, &buf_y);
+
+ gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view),
+ &iter,
+ buf_x, buf_y);
+
+ if (gtk_text_iter_has_tag (&iter, tag)) {
+ if (!hand) {
+ hand = gdk_cursor_new (GDK_HAND2);
+ beam = gdk_cursor_new (GDK_XTERM);
+ }
+ gdk_window_set_cursor (win, hand);
+ } else {
+ if (!beam) {
+ beam = gdk_cursor_new (GDK_XTERM);
+ }
+ gdk_window_set_cursor (win, beam);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+chat_view_url_event_cb (GtkTextTag *tag,
+ GObject *object,
+ GdkEvent *event,
+ GtkTextIter *iter,
+ GtkTextBuffer *buffer)
+{
+ GtkTextIter start, end;
+ gchar *str;
+
+ /* If the link is being selected, don't do anything. */
+ gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
+ if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) {
+ return FALSE;
+ }
+
+ if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) {
+ start = end = *iter;
+
+ if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
+ gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
+ str = gtk_text_buffer_get_text (buffer,
+ &start,
+ &end,
+ FALSE);
+
+ gossip_url_show (str);
+ g_free (str);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+chat_view_open_address_cb (GtkMenuItem *menuitem, const gchar *url)
+{
+ gossip_url_show (url);
+}
+
+static void
+chat_view_copy_address_cb (GtkMenuItem *menuitem, const gchar *url)
+{
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ gtk_clipboard_set_text (clipboard, url, -1);
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
+ gtk_clipboard_set_text (clipboard, url, -1);
+}
+
+static void
+chat_view_clear_view_cb (GtkMenuItem *menuitem, GossipChatView *view)
+{
+ gossip_chat_view_clear (view);
+}
+
+static void
+chat_view_insert_text_with_emoticons (GtkTextBuffer *buf,
+ GtkTextIter *iter,
+ const gchar *str)
+{
+ const gchar *p;
+ gunichar c, prev_c;
+ gint i;
+ gint match;
+ gint submatch;
+ gboolean use_smileys = FALSE;
+
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_SHOW_SMILEYS,
+ &use_smileys);
+
+ if (!use_smileys) {
+ gtk_text_buffer_insert (buf, iter, str, -1);
+ return;
+ }
+
+ while (*str) {
+ gint smileys_index[G_N_ELEMENTS (smileys)];
+ GdkPixbuf *pixbuf;
+ gint len;
+ const gchar *start;
+
+ memset (smileys_index, 0, sizeof (smileys_index));
+
+ match = -1;
+ submatch = -1;
+ p = str;
+ prev_c = 0;
+
+ while (*p) {
+ c = g_utf8_get_char (p);
+
+ if (match != -1 && g_unichar_isspace (c)) {
+ break;
+ } else {
+ match = -1;
+ }
+
+ if (submatch != -1 || prev_c == 0 || g_unichar_isspace (prev_c)) {
+ submatch = -1;
+
+ for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
+ /* Only try to match if we already have
+ * a beginning match for the pattern, or
+ * if it's the first character in the
+ * pattern, if it's not in the middle of
+ * a word.
+ */
+ if (((smileys_index[i] == 0 && (prev_c == 0 || g_unichar_isspace (prev_c))) ||
+ smileys_index[i] > 0) &&
+ smileys[i].pattern[smileys_index[i]] == c) {
+ submatch = i;
+
+ smileys_index[i]++;
+ if (!smileys[i].pattern[smileys_index[i]]) {
+ match = i;
+ }
+ } else {
+ smileys_index[i] = 0;
+ }
+ }
+ }
+
+ prev_c = c;
+ p = g_utf8_next_char (p);
+ }
+
+ if (match == -1) {
+ gtk_text_buffer_insert (buf, iter, str, -1);
+ return;
+ }
+
+ start = p - strlen (smileys[match].pattern);
+
+ if (start > str) {
+ len = start - str;
+ gtk_text_buffer_insert (buf, iter, str, len);
+ }
+
+ pixbuf = gossip_chat_view_get_smiley_image (smileys[match].smiley);
+ gtk_text_buffer_insert_pixbuf (buf, iter, pixbuf);
+
+ gtk_text_buffer_insert (buf, iter, " ", 1);
+
+ str = g_utf8_find_next_char (p, NULL);
+ }
+}
+
+static gboolean
+chat_view_is_scrolled_down (GossipChatView *view)
+{
+ GtkWidget *sw;
+
+ sw = gtk_widget_get_parent (GTK_WIDGET (view));
+ if (GTK_IS_SCROLLED_WINDOW (sw)) {
+ GtkAdjustment *vadj;
+
+ vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw));
+
+ if (vadj->value + vadj->page_size / 2 < vadj->upper - vadj->page_size) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+chat_view_maybe_trim_buffer (GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+ GtkTextIter top, bottom;
+ gint line;
+ gint remove;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+
+ priv = GET_PRIV (view);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &bottom);
+ line = gtk_text_iter_get_line (&bottom);
+ if (line < MAX_LINES) {
+ return;
+ }
+
+ remove = line - MAX_LINES;
+ gtk_text_buffer_get_start_iter (priv->buffer, &top);
+
+ bottom = top;
+ if (!gtk_text_iter_forward_lines (&bottom, remove)) {
+ return;
+ }
+
+ /* Track backwords to a place where we can safely cut, we don't do it in
+ * the middle of a tag.
+ */
+ table = gtk_text_buffer_get_tag_table (priv->buffer);
+ tag = gtk_text_tag_table_lookup (table, "cut");
+ if (!tag) {
+ return;
+ }
+
+ if (!gtk_text_iter_forward_to_tag_toggle (&bottom, tag)) {
+ return;
+ }
+
+ if (!gtk_text_iter_equal (&top, &bottom)) {
+ gtk_text_buffer_delete (priv->buffer, &top, &bottom);
+ }
+}
+
+static void
+chat_view_maybe_append_date_and_time (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ const gchar *tag;
+ time_t timestamp;
+ GDate *date, *last_date;
+ GtkTextIter iter;
+ gboolean append_date, append_time;
+ GString *str;
+
+ priv = GET_PRIV (view);
+
+ if (priv->irc_style) {
+ tag = "irc-time";
+ } else {
+ tag = "fancy-time";
+ }
+
+ if (priv->last_block_type == BLOCK_TYPE_TIME) {
+ return;
+ }
+
+ str = g_string_new (NULL);
+
+ timestamp = 0;
+ if (msg) {
+ timestamp = gossip_message_get_timestamp (msg);
+ }
+
+ if (timestamp <= 0) {
+ timestamp = gossip_time_get_current ();
+ }
+
+ date = g_date_new ();
+ g_date_set_time (date, timestamp);
+
+ last_date = g_date_new ();
+ g_date_set_time (last_date, priv->last_timestamp);
+
+ append_date = FALSE;
+ append_time = FALSE;
+
+ if (g_date_compare (date, last_date) > 0) {
+ append_date = TRUE;
+ append_time = TRUE;
+ }
+
+ if (priv->last_timestamp + TIMESTAMP_INTERVAL < timestamp) {
+ append_time = TRUE;
+ }
+
+ if (append_time || append_date) {
+ chat_view_append_spacing (view);
+
+ g_string_append (str, "- ");
+ }
+
+ if (append_date) {
+ gchar buf[256];
+
+ g_date_strftime (buf, 256, _("%A %d %B %Y"), date);
+ g_string_append (str, buf);
+
+ if (append_time) {
+ g_string_append (str, ", ");
+ }
+ }
+
+ g_date_free (date);
+ g_date_free (last_date);
+
+ if (append_time) {
+ gchar *tmp;
+
+ tmp = gossip_time_to_string_local (timestamp, GOSSIP_TIME_FORMAT_DISPLAY_SHORT);
+ g_string_append (str, tmp);
+ g_free (tmp);
+ }
+
+ if (append_time || append_date) {
+ g_string_append (str, " -\n");
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ str->str, -1,
+ tag,
+ NULL);
+
+ priv->last_block_type = BLOCK_TYPE_TIME;
+ priv->last_timestamp = timestamp;
+ }
+
+ g_string_free (str, TRUE);
+}
+
+static void
+chat_view_append_spacing (GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+ const gchar *tag;
+ GtkTextIter iter;
+
+ priv = GET_PRIV (view);
+
+ if (priv->irc_style) {
+ tag = "irc-spacing";
+ } else {
+ tag = "fancy-spacing";
+ }
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ "\n",
+ -1,
+ "cut",
+ tag,
+ NULL);
+}
+
+static void
+chat_view_append_text (GossipChatView *view,
+ const gchar *body,
+ const gchar *tag)
+{
+ GossipChatViewPriv *priv;
+ GtkTextIter start_iter, end_iter;
+ GtkTextMark *mark;
+ GtkTextIter iter;
+ gint num_matches, i;
+ GArray *start, *end;
+ const gchar *link_tag;
+
+ priv = GET_PRIV (view);
+
+ if (priv->irc_style) {
+ link_tag = "irc-link";
+ } else {
+ link_tag = "fancy-link";
+ }
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &start_iter);
+ mark = gtk_text_buffer_create_mark (priv->buffer, NULL, &start_iter, TRUE);
+
+ start = g_array_new (FALSE, FALSE, sizeof (gint));
+ end = g_array_new (FALSE, FALSE, sizeof (gint));
+
+ num_matches = gossip_regex_match (GOSSIP_REGEX_ALL, body, start, end);
+
+ if (num_matches == 0) {
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ chat_view_insert_text_with_emoticons (priv->buffer, &iter, body);
+ } else {
+ gint last = 0;
+ gint s = 0, e = 0;
+ gchar *tmp;
+
+ for (i = 0; i < num_matches; i++) {
+ s = g_array_index (start, gint, i);
+ e = g_array_index (end, gint, i);
+
+ if (s > last) {
+ tmp = gossip_substring (body, last, s);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ chat_view_insert_text_with_emoticons (priv->buffer,
+ &iter,
+ tmp);
+ g_free (tmp);
+ }
+
+ tmp = gossip_substring (body, s, e);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ tmp,
+ -1,
+ link_tag,
+ "link",
+ NULL);
+
+ g_free (tmp);
+
+ last = e;
+ }
+
+ if (e < strlen (body)) {
+ tmp = gossip_substring (body, e, strlen (body));
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ chat_view_insert_text_with_emoticons (priv->buffer,
+ &iter,
+ tmp);
+ g_free (tmp);
+ }
+ }
+
+ g_array_free (start, TRUE);
+ g_array_free (end, TRUE);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert (priv->buffer, &iter, "\n", 1);
+
+ /* Apply the style to the inserted text. */
+ gtk_text_buffer_get_iter_at_mark (priv->buffer, &start_iter, mark);
+ gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
+
+ gtk_text_buffer_apply_tag_by_name (priv->buffer,
+ tag,
+ &start_iter,
+ &end_iter);
+
+ gtk_text_buffer_delete_mark (priv->buffer, mark);
+}
+
+static void
+chat_view_maybe_append_fancy_header (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ GossipContact *sender;
+ GossipContact *my_contact;
+ const gchar *name;
+ gboolean header;
+ GtkTextIter iter;
+ gchar *tmp;
+ const gchar *tag;
+ const gchar *avatar_tag;
+ const gchar *line_top_tag;
+ const gchar *line_bottom_tag;
+ gboolean from_self;
+ GdkPixbuf *pixbuf = NULL;
+ GdkPixbuf *avatar = NULL;
+
+ priv = GET_PRIV (view);
+
+ sender = gossip_message_get_sender (msg);
+ my_contact = gossip_contact_get_user (sender);
+ name = gossip_contact_get_name (sender);
+ from_self = gossip_contact_equal (sender, my_contact);
+
+ gossip_debug (DEBUG_DOMAIN, "Maybe add fancy header");
+
+ if (from_self) {
+ tag = "fancy-header-self";
+ line_top_tag = "fancy-line-top-self";
+ line_bottom_tag = "fancy-line-bottom-self";
+ } else {
+ tag = "fancy-header-other";
+ line_top_tag = "fancy-line-top-other";
+ line_bottom_tag = "fancy-line-bottom-other";
+ }
+
+ header = FALSE;
+
+ /* Only insert a header if the previously inserted block is not the same
+ * as this one. This catches all the different cases:
+ */
+ if (priv->last_block_type != BLOCK_TYPE_SELF &&
+ priv->last_block_type != BLOCK_TYPE_OTHER) {
+ header = TRUE;
+ }
+ else if (from_self && priv->last_block_type == BLOCK_TYPE_OTHER) {
+ header = TRUE;
+ }
+ else if (!from_self && priv->last_block_type == BLOCK_TYPE_SELF) {
+ header = TRUE;
+ }
+ else if (!from_self &&
+ (!priv->last_contact ||
+ !gossip_contact_equal (sender, priv->last_contact))) {
+ header = TRUE;
+ }
+
+ if (!header) {
+ return;
+ }
+
+ chat_view_append_spacing (view);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ "\n",
+ -1,
+ line_top_tag,
+ NULL);
+
+ /* FIXME: we should have a cash of avatar pixbufs */
+ pixbuf = gossip_pixbuf_avatar_from_contact_scaled (sender, 32, 32);
+ if (pixbuf) {
+ avatar = chat_view_pad_to_size (pixbuf, 32, 32, 6);
+ g_object_unref (pixbuf);
+ }
+
+ if (avatar) {
+ GtkTextIter start;
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_pixbuf (priv->buffer, &iter, avatar);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ start = iter;
+ gtk_text_iter_backward_char (&start);
+
+ if (from_self) {
+ gtk_text_buffer_apply_tag_by_name (priv->buffer,
+ "fancy-avatar-self",
+ &start, &iter);
+ avatar_tag = "fancy-header-self-avatar";
+ } else {
+ gtk_text_buffer_apply_tag_by_name (priv->buffer,
+ "fancy-avatar-other",
+ &start, &iter);
+ avatar_tag = "fancy-header-other-avatar";
+ }
+
+ g_object_unref (avatar);
+ } else {
+ avatar_tag = NULL;
+ }
+
+ tmp = g_strdup_printf ("%s\n", name);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ tmp,
+ -1,
+ tag,
+ avatar_tag,
+ NULL);
+ g_free (tmp);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ "\n",
+ -1,
+ line_bottom_tag,
+ NULL);
+}
+
+static void
+chat_view_append_irc_action (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ GossipContact *my_contact;
+ GossipContact *sender;
+ const gchar *name;
+ GtkTextIter iter;
+ const gchar *body;
+ gchar *tmp;
+ const gchar *tag;
+
+ priv = GET_PRIV (view);
+
+ gossip_debug (DEBUG_DOMAIN, "Add IRC action");
+
+ sender = gossip_message_get_sender (msg);
+ my_contact = gossip_contact_get_user (sender);
+ name = gossip_contact_get_name (sender);
+
+ /* Skip the "/me ". */
+ if (gossip_contact_equal (sender, my_contact)) {
+ tag = "irc-action-self";
+ } else {
+ tag = "irc-action-other";
+ }
+
+ if (priv->last_block_type != BLOCK_TYPE_SELF &&
+ priv->last_block_type != BLOCK_TYPE_OTHER) {
+ chat_view_append_spacing (view);
+ }
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+
+ tmp = g_strdup_printf (" * %s ", name);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ tmp,
+ -1,
+ "cut",
+ tag,
+ NULL);
+ g_free (tmp);
+
+ body = gossip_message_get_body (msg);
+ chat_view_append_text (view, body, tag);
+}
+
+static void
+chat_view_append_fancy_action (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ GossipContact *sender;
+ GossipContact *my_contact;
+ const gchar *name;
+ const gchar *body;
+ GtkTextIter iter;
+ gchar *tmp;
+ const gchar *tag;
+ const gchar *line_tag;
+
+ priv = GET_PRIV (view);
+
+ gossip_debug (DEBUG_DOMAIN, "Add fancy action");
+
+ sender = gossip_message_get_sender (msg);
+ my_contact = gossip_contact_get_user (sender);
+ name = gossip_contact_get_name (sender);
+
+ if (gossip_contact_equal (sender, my_contact)) {
+ tag = "fancy-action-self";
+ line_tag = "fancy-line-self";
+ } else {
+ tag = "fancy-action-other";
+ line_tag = "fancy-line-other";
+ }
+
+ tmp = g_strdup_printf (" * %s ", name);
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ tmp,
+ -1,
+ tag,
+ NULL);
+ g_free (tmp);
+
+ body = gossip_message_get_body (msg);
+ chat_view_append_text (view, body, tag);
+}
+
+static void
+chat_view_append_irc_message (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ GossipContact *sender;
+ GossipContact *my_contact;
+ const gchar *name;
+ const gchar *body;
+ const gchar *nick_tag;
+ const gchar *body_tag;
+ GtkTextIter iter;
+ gchar *tmp;
+
+ priv = GET_PRIV (view);
+
+ gossip_debug (DEBUG_DOMAIN, "Add IRC message");
+
+ body = gossip_message_get_body (msg);
+ sender = gossip_message_get_sender (msg);
+ my_contact = gossip_contact_get_user (sender);
+ name = gossip_contact_get_name (sender);
+
+ if (gossip_contact_equal (sender, my_contact)) {
+ nick_tag = "irc-nick-self";
+ body_tag = "irc-body-self";
+ } else {
+ if (gossip_chat_should_highlight_nick (msg)) {
+ nick_tag = "irc-nick-highlight";
+ } else {
+ nick_tag = "irc-nick-other";
+ }
+
+ body_tag = "irc-body-other";
+ }
+
+ if (priv->last_block_type != BLOCK_TYPE_SELF &&
+ priv->last_block_type != BLOCK_TYPE_OTHER) {
+ chat_view_append_spacing (view);
+ }
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+
+ /* The nickname. */
+ tmp = g_strdup_printf ("%s: ", name);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ tmp,
+ -1,
+ "cut",
+ nick_tag,
+ NULL);
+ g_free (tmp);
+
+ /* The text body. */
+ chat_view_append_text (view, body, body_tag);
+}
+
+static void
+chat_view_append_fancy_message (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ GossipContact *sender;
+ GossipContact *my_contact;
+ const gchar *body;
+ const gchar *tag;
+
+ priv = GET_PRIV (view);
+
+ sender = gossip_message_get_sender (msg);
+ my_contact = gossip_contact_get_user (sender);
+
+ if (gossip_contact_equal (sender, my_contact)) {
+ tag = "fancy-body-self";
+ } else {
+ tag = "fancy-body-other";
+
+ /* FIXME: Might want to support nick highlighting here... */
+ }
+
+ body = gossip_message_get_body (msg);
+ chat_view_append_text (view, body, tag);
+}
+
+static void
+chat_view_theme_changed_cb (GossipThemeManager *manager,
+ GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+ gboolean show_avatars = FALSE;
+ gboolean theme_rooms = FALSE;
+
+ priv = GET_PRIV (view);
+
+ priv->last_block_type = BLOCK_TYPE_NONE;
+
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
+ &theme_rooms);
+ if (!theme_rooms && priv->is_group_chat) {
+ gossip_theme_manager_apply (manager, view, NULL);
+ } else {
+ gossip_theme_manager_apply_saved (manager, view);
+ }
+
+ /* Needed for now to update the "rise" property of the names to get it
+ * vertically centered.
+ */
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ &show_avatars);
+ gossip_theme_manager_update_show_avatars (manager, view, show_avatars);
+}
+
+/* Pads a pixbuf to the specified size, by centering it in a larger transparent
+ * pixbuf. Returns a new ref.
+ */
+static GdkPixbuf *
+chat_view_pad_to_size (GdkPixbuf *pixbuf,
+ gint width,
+ gint height,
+ gint extra_padding_right)
+{
+ gint src_width, src_height;
+ GdkPixbuf *padded;
+ gint x_offset, y_offset;
+
+ src_width = gdk_pixbuf_get_width (pixbuf);
+ src_height = gdk_pixbuf_get_height (pixbuf);
+
+ x_offset = (width - src_width) / 2;
+ y_offset = (height - src_height) / 2;
+
+ padded = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
+ TRUE, /* alpha */
+ gdk_pixbuf_get_bits_per_sample (pixbuf),
+ width + extra_padding_right,
+ height);
+
+ gdk_pixbuf_fill (padded, 0);
+
+ gdk_pixbuf_copy_area (pixbuf,
+ 0, /* source coords */
+ 0,
+ src_width,
+ src_height,
+ padded,
+ x_offset, /* dest coords */
+ y_offset);
+
+ return padded;
+}
+
+GossipChatView *
+gossip_chat_view_new (void)
+{
+ return g_object_new (GOSSIP_TYPE_CHAT_VIEW, NULL);
+}
+
+/* The name is optional, if NULL, the sender for msg is used. */
+void
+gossip_chat_view_append_message (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ GossipContact *sender;
+ GossipContact *my_contact;
+ const gchar *body;
+ gboolean scroll_down;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+ g_return_if_fail (GOSSIP_IS_MESSAGE (msg));
+
+ priv = GET_PRIV (view);
+
+ body = gossip_message_get_body (msg);
+ if (!body) {
+ return;
+ }
+
+ scroll_down = chat_view_is_scrolled_down (view);
+
+ chat_view_maybe_trim_buffer (view);
+ chat_view_maybe_append_date_and_time (view, msg);
+
+ sender = gossip_message_get_sender (msg);
+
+ if (!priv->irc_style) {
+ chat_view_maybe_append_fancy_header (view, msg);
+ }
+
+ if (gossip_message_get_type (msg) == GOSSIP_MESSAGE_TYPE_ACTION) {
+ if (priv->irc_style) {
+ chat_view_append_irc_action (view, msg);
+ } else {
+ chat_view_append_fancy_action (view, msg);
+ }
+ } else {
+ if (priv->irc_style) {
+ chat_view_append_irc_message (view, msg);
+ } else {
+ chat_view_append_fancy_message (view, msg);
+ }
+ }
+
+ my_contact = gossip_contact_get_user (sender);
+
+ /* Reset the last inserted contact. */
+ if (priv->last_contact) {
+ g_object_unref (priv->last_contact);
+ }
+
+ if (gossip_contact_equal (my_contact, sender)) {
+ priv->last_block_type = BLOCK_TYPE_SELF;
+ priv->last_contact = NULL;
+ } else {
+ priv->last_block_type = BLOCK_TYPE_OTHER;
+ priv->last_contact = g_object_ref (sender);
+ }
+
+ if (scroll_down) {
+ gossip_chat_view_scroll_down (view);
+ }
+}
+
+void
+gossip_chat_view_append_event (GossipChatView *view,
+ const gchar *str)
+{
+ GossipChatViewPriv *priv;
+ gboolean bottom;
+ GtkTextIter iter;
+ gchar *msg;
+ const gchar *tag;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+ g_return_if_fail (!G_STR_EMPTY (str));
+
+ priv = GET_PRIV (view);
+
+ bottom = chat_view_is_scrolled_down (view);
+
+ chat_view_maybe_trim_buffer (view);
+
+ if (priv->irc_style) {
+ tag = "irc-event";
+ msg = g_strdup_printf (" - %s\n", str);
+ } else {
+ tag = "fancy-event";
+ msg = g_strdup_printf (" - %s\n", str);
+ }
+
+ if (priv->last_block_type != BLOCK_TYPE_EVENT) {
+ /* Comment out for now. */
+ /*chat_view_append_spacing (view);*/
+ }
+
+ chat_view_maybe_append_date_and_time (view, NULL);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer, &iter,
+ msg, -1,
+ tag,
+ NULL);
+ g_free (msg);
+
+ if (bottom) {
+ gossip_chat_view_scroll_down (view);
+ }
+
+ priv->last_block_type = BLOCK_TYPE_EVENT;
+}
+
+void
+gossip_chat_view_append_button (GossipChatView *view,
+ const gchar *message,
+ GtkWidget *button1,
+ GtkWidget *button2)
+{
+ GossipChatViewPriv *priv;
+ GtkTextChildAnchor *anchor;
+ GtkTextIter iter;
+ gboolean bottom;
+ const gchar *tag;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+ g_return_if_fail (button1 != NULL);
+
+ priv = GET_PRIV (view);
+
+ if (priv->irc_style) {
+ tag = "irc-invite";
+ } else {
+ tag = "fancy-invite";
+ }
+
+ bottom = chat_view_is_scrolled_down (view);
+
+ chat_view_maybe_append_date_and_time (view, NULL);
+
+ if (message) {
+ chat_view_append_text (view, message, tag);
+ }
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+
+ anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
+ gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button1, anchor);
+ gtk_widget_show (button1);
+
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ " ",
+ 1,
+ tag,
+ NULL);
+
+ if (button2) {
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+
+ anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
+ gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button2, anchor);
+ gtk_widget_show (button2);
+
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ " ",
+ 1,
+ tag,
+ NULL);
+ }
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ "\n\n",
+ 2,
+ tag,
+ NULL);
+
+ if (bottom) {
+ gossip_chat_view_scroll_down (view);
+ }
+
+ priv->last_block_type = BLOCK_TYPE_INVITE;
+}
+
+void
+gossip_chat_view_scroll (GossipChatView *view,
+ gboolean allow_scrolling)
+{
+ GossipChatViewPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ priv = GET_PRIV (view);
+
+ priv->allow_scrolling = allow_scrolling;
+
+ gossip_debug (DEBUG_DOMAIN, "Scrolling %s",
+ allow_scrolling ? "enabled" : "disabled");
+}
+
+void
+gossip_chat_view_scroll_down (GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ GtkTextMark *mark;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ priv = GET_PRIV (view);
+
+ if (!priv->allow_scrolling) {
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Scrolling down");
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+ mark = gtk_text_buffer_create_mark (buffer,
+ NULL,
+ &iter,
+ FALSE);
+
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ mark,
+ 0.0,
+ FALSE,
+ 0,
+ 0);
+
+ gtk_text_buffer_delete_mark (buffer, mark);
+}
+
+gboolean
+gossip_chat_view_get_selection_bounds (GossipChatView *view,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ GtkTextBuffer *buffer;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ return gtk_text_buffer_get_selection_bounds (buffer, start, end);
+}
+
+void
+gossip_chat_view_clear (GossipChatView *view)
+{
+ GtkTextBuffer *buffer;
+ GossipChatViewPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ gtk_text_buffer_set_text (buffer, "", -1);
+
+ /* We set these back to the initial values so we get
+ * timestamps when clearing the window to know when
+ * conversations start.
+ */
+ priv = GET_PRIV (view);
+
+ priv->last_block_type = BLOCK_TYPE_NONE;
+ priv->last_timestamp = 0;
+}
+
+gboolean
+gossip_chat_view_find_previous (GossipChatView *view,
+ const gchar *search_criteria,
+ gboolean new_search)
+{
+ GossipChatViewPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter_at_mark;
+ GtkTextIter iter_match_start;
+ GtkTextIter iter_match_end;
+ gboolean found;
+ gboolean from_start = FALSE;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
+ g_return_val_if_fail (search_criteria != NULL, FALSE);
+
+ priv = GET_PRIV (view);
+
+ buffer = priv->buffer;
+
+ if (G_STR_EMPTY (search_criteria)) {
+ if (priv->find_mark_previous) {
+ gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
+
+ gtk_text_buffer_move_mark (buffer,
+ priv->find_mark_previous,
+ &iter_at_mark);
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ priv->find_mark_previous,
+ 0.0,
+ TRUE,
+ 0.0,
+ 0.0);
+ gtk_text_buffer_select_range (buffer,
+ &iter_at_mark,
+ &iter_at_mark);
+ }
+
+ return FALSE;
+ }
+
+ if (new_search) {
+ from_start = TRUE;
+ }
+
+ if (priv->find_mark_previous) {
+ gtk_text_buffer_get_iter_at_mark (buffer,
+ &iter_at_mark,
+ priv->find_mark_previous);
+ } else {
+ gtk_text_buffer_get_end_iter (buffer, &iter_at_mark);
+ from_start = TRUE;
+ }
+
+ priv->find_last_direction = FALSE;
+
+ found = gossip_text_iter_backward_search (&iter_at_mark,
+ search_criteria,
+ &iter_match_start,
+ &iter_match_end,
+ NULL);
+
+ if (!found) {
+ gboolean result = FALSE;
+
+ if (from_start) {
+ return result;
+ }
+
+ /* Here we wrap around. */
+ if (!new_search && !priv->find_wrapped) {
+ priv->find_wrapped = TRUE;
+ result = gossip_chat_view_find_previous (view,
+ search_criteria,
+ FALSE);
+ priv->find_wrapped = FALSE;
+ }
+
+ return result;
+ }
+
+ /* Set new mark and show on screen */
+ if (!priv->find_mark_previous) {
+ priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
+ &iter_match_start,
+ TRUE);
+ } else {
+ gtk_text_buffer_move_mark (buffer,
+ priv->find_mark_previous,
+ &iter_match_start);
+ }
+
+ if (!priv->find_mark_next) {
+ priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
+ &iter_match_end,
+ TRUE);
+ } else {
+ gtk_text_buffer_move_mark (buffer,
+ priv->find_mark_next,
+ &iter_match_end);
+ }
+
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ priv->find_mark_previous,
+ 0.0,
+ TRUE,
+ 0.5,
+ 0.5);
+
+ gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
+ gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
+
+ return TRUE;
+}
+
+gboolean
+gossip_chat_view_find_next (GossipChatView *view,
+ const gchar *search_criteria,
+ gboolean new_search)
+{
+ GossipChatViewPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter_at_mark;
+ GtkTextIter iter_match_start;
+ GtkTextIter iter_match_end;
+ gboolean found;
+ gboolean from_start = FALSE;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
+ g_return_val_if_fail (search_criteria != NULL, FALSE);
+
+ priv = GET_PRIV (view);
+
+ buffer = priv->buffer;
+
+ if (G_STR_EMPTY (search_criteria)) {
+ if (priv->find_mark_next) {
+ gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
+
+ gtk_text_buffer_move_mark (buffer,
+ priv->find_mark_next,
+ &iter_at_mark);
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ priv->find_mark_next,
+ 0.0,
+ TRUE,
+ 0.0,
+ 0.0);
+ gtk_text_buffer_select_range (buffer,
+ &iter_at_mark,
+ &iter_at_mark);
+ }
+
+ return FALSE;
+ }
+
+ if (new_search) {
+ from_start = TRUE;
+ }
+
+ if (priv->find_mark_next) {
+ gtk_text_buffer_get_iter_at_mark (buffer,
+ &iter_at_mark,
+ priv->find_mark_next);
+ } else {
+ gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
+ from_start = TRUE;
+ }
+
+ priv->find_last_direction = TRUE;
+
+ found = gossip_text_iter_forward_search (&iter_at_mark,
+ search_criteria,
+ &iter_match_start,
+ &iter_match_end,
+ NULL);
+
+ if (!found) {
+ gboolean result = FALSE;
+
+ if (from_start) {
+ return result;
+ }
+
+ /* Here we wrap around. */
+ if (!new_search && !priv->find_wrapped) {
+ priv->find_wrapped = TRUE;
+ result = gossip_chat_view_find_next (view,
+ search_criteria,
+ FALSE);
+ priv->find_wrapped = FALSE;
+ }
+
+ return result;
+ }
+
+ /* Set new mark and show on screen */
+ if (!priv->find_mark_next) {
+ priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
+ &iter_match_end,
+ TRUE);
+ } else {
+ gtk_text_buffer_move_mark (buffer,
+ priv->find_mark_next,
+ &iter_match_end);
+ }
+
+ if (!priv->find_mark_previous) {
+ priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
+ &iter_match_start,
+ TRUE);
+ } else {
+ gtk_text_buffer_move_mark (buffer,
+ priv->find_mark_previous,
+ &iter_match_start);
+ }
+
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ priv->find_mark_next,
+ 0.0,
+ TRUE,
+ 0.5,
+ 0.5);
+
+ gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
+ gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
+
+ return TRUE;
+}
+
+
+void
+gossip_chat_view_find_abilities (GossipChatView *view,
+ const gchar *search_criteria,
+ gboolean *can_do_previous,
+ gboolean *can_do_next)
+{
+ GossipChatViewPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter_at_mark;
+ GtkTextIter iter_match_start;
+ GtkTextIter iter_match_end;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+ g_return_if_fail (search_criteria != NULL);
+ g_return_if_fail (can_do_previous != NULL && can_do_next != NULL);
+
+ priv = GET_PRIV (view);
+
+ buffer = priv->buffer;
+
+ if (can_do_previous) {
+ if (priv->find_mark_previous) {
+ gtk_text_buffer_get_iter_at_mark (buffer,
+ &iter_at_mark,
+ priv->find_mark_previous);
+ } else {
+ gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
+ }
+
+ *can_do_previous = gossip_text_iter_backward_search (&iter_at_mark,
+ search_criteria,
+ &iter_match_start,
+ &iter_match_end,
+ NULL);
+ }
+
+ if (can_do_next) {
+ if (priv->find_mark_next) {
+ gtk_text_buffer_get_iter_at_mark (buffer,
+ &iter_at_mark,
+ priv->find_mark_next);
+ } else {
+ gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
+ }
+
+ *can_do_next = gossip_text_iter_forward_search (&iter_at_mark,
+ search_criteria,
+ &iter_match_start,
+ &iter_match_end,
+ NULL);
+ }
+}
+
+void
+gossip_chat_view_highlight (GossipChatView *view,
+ const gchar *text)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ GtkTextIter iter_start;
+ GtkTextIter iter_end;
+ GtkTextIter iter_match_start;
+ GtkTextIter iter_match_end;
+ gboolean found;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ gtk_text_buffer_get_start_iter (buffer, &iter);
+
+ gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
+ gtk_text_buffer_remove_tag_by_name (buffer, "highlight",
+ &iter_start,
+ &iter_end);
+
+ if (G_STR_EMPTY (text)) {
+ return;
+ }
+
+ while (1) {
+ found = gossip_text_iter_forward_search (&iter,
+ text,
+ &iter_match_start,
+ &iter_match_end,
+ NULL);
+
+ if (!found) {
+ break;
+ }
+
+ gtk_text_buffer_apply_tag_by_name (buffer, "highlight",
+ &iter_match_start,
+ &iter_match_end);
+
+ iter = iter_match_end;
+ gtk_text_iter_forward_char (&iter);
+ }
+}
+
+void
+gossip_chat_view_copy_clipboard (GossipChatView *view)
+{
+ GtkTextBuffer *buffer;
+ GtkClipboard *clipboard;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+
+ gtk_text_buffer_copy_clipboard (buffer, clipboard);
+}
+
+gboolean
+gossip_chat_view_get_irc_style (GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
+
+ priv = GET_PRIV (view);
+
+ return priv->irc_style;
+}
+
+void
+gossip_chat_view_set_irc_style (GossipChatView *view,
+ gboolean irc_style)
+{
+ GossipChatViewPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ priv = GET_PRIV (view);
+
+ priv->irc_style = irc_style;
+}
+
+void
+gossip_chat_view_set_margin (GossipChatView *view,
+ gint margin)
+{
+ GossipChatViewPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ priv = GET_PRIV (view);
+
+ g_object_set (view,
+ "left-margin", margin,
+ "right-margin", margin,
+ NULL);
+}
+
+GdkPixbuf *
+gossip_chat_view_get_smiley_image (GossipSmiley smiley)
+{
+ static GdkPixbuf *pixbufs[GOSSIP_SMILEY_COUNT];
+ static gboolean inited = FALSE;
+
+ if (!inited) {
+ gint i;
+
+ for (i = 0; i < GOSSIP_SMILEY_COUNT; i++) {
+ pixbufs[i] = gossip_pixbuf_from_smiley (i, GTK_ICON_SIZE_MENU);
+ }
+
+ inited = TRUE;
+ }
+
+ return pixbufs[smiley];
+}
+
+const gchar *
+gossip_chat_view_get_smiley_text (GossipSmiley smiley)
+{
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
+ if (smileys[i].smiley != smiley) {
+ continue;
+ }
+
+ return smileys[i].pattern;
+ }
+
+ return NULL;
+}
+
+GtkWidget *
+gossip_chat_view_get_smiley_menu (GCallback callback,
+ gpointer user_data,
+ GtkTooltips *tooltips)
+{
+ GtkWidget *menu;
+ gint x;
+ gint y;
+ gint i;
+
+ g_return_val_if_fail (callback != NULL, NULL);
+
+ menu = gtk_menu_new ();
+
+ for (i = 0, x = 0, y = 0; i < GOSSIP_SMILEY_COUNT; i++) {
+ GtkWidget *item;
+ GtkWidget *image;
+ GdkPixbuf *pixbuf;
+ const gchar *smiley_text;
+
+ pixbuf = gossip_chat_view_get_smiley_image (i);
+ if (!pixbuf) {
+ continue;
+ }
+
+ image = gtk_image_new_from_pixbuf (pixbuf);
+
+ item = gtk_image_menu_item_new_with_label ("");
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+
+ gtk_menu_attach (GTK_MENU (menu), item,
+ x, x + 1, y, y + 1);
+
+ smiley_text = gossip_chat_view_get_smiley_text (i);
+
+ gtk_tooltips_set_tip (tooltips,
+ item,
+ smiley_text,
+ NULL);
+
+ g_object_set_data (G_OBJECT (item), "smiley_text", (gpointer) smiley_text);
+ g_signal_connect (item, "activate", callback, user_data);
+
+ if (x > 3) {
+ y++;
+ x = 0;
+ } else {
+ x++;
+ }
+ }
+
+ gtk_widget_show_all (menu);
+
+ return menu;
+}
+
+/* FIXME: Do we really need this? Better to do it internally only at setup time,
+ * we will never change it on the fly.
+ */
+void
+gossip_chat_view_set_is_group_chat (GossipChatView *view,
+ gboolean is_group_chat)
+{
+ GossipChatViewPriv *priv;
+ gboolean theme_rooms = FALSE;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ priv = GET_PRIV (view);
+
+ priv->is_group_chat = is_group_chat;
+
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
+ &theme_rooms);
+
+ if (!theme_rooms && is_group_chat) {
+ gossip_theme_manager_apply (gossip_theme_manager_get (),
+ view,
+ NULL);
+ } else {
+ gossip_theme_manager_apply_saved (gossip_theme_manager_get (),
+ view);
+ }
+}
diff --git a/trunk/libempathy-gtk/gossip-chat-view.h b/trunk/libempathy-gtk/gossip-chat-view.h
new file mode 100644
index 000000000..2a7b11472
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-chat-view.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_CHAT_VIEW_H__
+#define __GOSSIP_CHAT_VIEW_H__
+
+#include <gtk/gtktextview.h>
+#include <gtk/gtktooltips.h>
+
+#include <libempathy/gossip-contact.h>
+#include <libempathy/gossip-message.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CHAT_VIEW (gossip_chat_view_get_type ())
+#define GOSSIP_CHAT_VIEW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CHAT_VIEW, GossipChatView))
+#define GOSSIP_CHAT_VIEW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GOSSIP_TYPE_CHAT_VIEW, GossipChatViewClass))
+#define GOSSIP_IS_CHAT_VIEW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CHAT_VIEW))
+#define GOSSIP_IS_CHAT_VIEW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CHAT_VIEW))
+#define GOSSIP_CHAT_VIEW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CHAT_VIEW, GossipChatViewClass))
+
+typedef struct _GossipChatView GossipChatView;
+typedef struct _GossipChatViewClass GossipChatViewClass;
+typedef struct _GossipChatViewPriv GossipChatViewPriv;
+
+struct _GossipChatView {
+ GtkTextView parent;
+};
+
+struct _GossipChatViewClass {
+ GtkTextViewClass parent_class;
+};
+
+typedef enum {
+ GOSSIP_SMILEY_NORMAL, /* :) */
+ GOSSIP_SMILEY_WINK, /* ;) */
+ GOSSIP_SMILEY_BIGEYE, /* =) */
+ GOSSIP_SMILEY_NOSE, /* :-) */
+ GOSSIP_SMILEY_CRY, /* :'( */
+ GOSSIP_SMILEY_SAD, /* :( */
+ GOSSIP_SMILEY_SCEPTICAL, /* :/ */
+ GOSSIP_SMILEY_BIGSMILE, /* :D */
+ GOSSIP_SMILEY_INDIFFERENT, /* :| */
+ GOSSIP_SMILEY_TOUNGE, /* :p */
+ GOSSIP_SMILEY_SHOCKED, /* :o */
+ GOSSIP_SMILEY_COOL, /* 8) */
+ GOSSIP_SMILEY_SORRY, /* *| */
+ GOSSIP_SMILEY_KISS, /* :* */
+ GOSSIP_SMILEY_SHUTUP, /* :# */
+ GOSSIP_SMILEY_YAWN, /* |O */
+ GOSSIP_SMILEY_CONFUSED, /* :$ */
+ GOSSIP_SMILEY_ANGEL, /* <) */
+ GOSSIP_SMILEY_OOOH, /* :x */
+ GOSSIP_SMILEY_LOOKAWAY, /* *) */
+ GOSSIP_SMILEY_BLUSH, /* *S */
+ GOSSIP_SMILEY_COOLBIGSMILE, /* 8D */
+ GOSSIP_SMILEY_ANGRY, /* :@ */
+ GOSSIP_SMILEY_BOSS, /* @) */
+ GOSSIP_SMILEY_MONKEY, /* #) */
+ GOSSIP_SMILEY_SILLY, /* O) */
+ GOSSIP_SMILEY_SICK, /* +o( */
+
+ GOSSIP_SMILEY_COUNT
+} GossipSmiley;
+
+GType gossip_chat_view_get_type (void) G_GNUC_CONST;
+GossipChatView *gossip_chat_view_new (void);
+void gossip_chat_view_append_message (GossipChatView *view,
+ GossipMessage *msg);
+void gossip_chat_view_append_event (GossipChatView *view,
+ const gchar *str);
+void gossip_chat_view_append_button (GossipChatView *view,
+ const gchar *message,
+ GtkWidget *button1,
+ GtkWidget *button2);
+void gossip_chat_view_set_margin (GossipChatView *view,
+ gint margin);
+void gossip_chat_view_scroll (GossipChatView *view,
+ gboolean allow_scrolling);
+void gossip_chat_view_scroll_down (GossipChatView *view);
+gboolean gossip_chat_view_get_selection_bounds (GossipChatView *view,
+ GtkTextIter *start,
+ GtkTextIter *end);
+void gossip_chat_view_clear (GossipChatView *view);
+gboolean gossip_chat_view_find_previous (GossipChatView *view,
+ const gchar *search_criteria,
+ gboolean new_search);
+gboolean gossip_chat_view_find_next (GossipChatView *view,
+ const gchar *search_criteria,
+ gboolean new_search);
+void gossip_chat_view_find_abilities (GossipChatView *view,
+ const gchar *search_criteria,
+ gboolean *can_do_previous,
+ gboolean *can_do_next);
+void gossip_chat_view_highlight (GossipChatView *view,
+ const gchar *text);
+void gossip_chat_view_copy_clipboard (GossipChatView *view);
+gboolean gossip_chat_view_get_irc_style (GossipChatView *view);
+void gossip_chat_view_set_irc_style (GossipChatView *view,
+ gboolean irc_style);
+void gossip_chat_view_set_margin (GossipChatView *view,
+ gint margin);
+GdkPixbuf * gossip_chat_view_get_smiley_image (GossipSmiley smiley);
+const gchar * gossip_chat_view_get_smiley_text (GossipSmiley smiley);
+GtkWidget * gossip_chat_view_get_smiley_menu (GCallback callback,
+ gpointer user_data,
+ GtkTooltips *tooltips);
+void gossip_chat_view_set_is_group_chat (GossipChatView *view,
+ gboolean is_group_chat);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CHAT_VIEW_H__ */
diff --git a/trunk/libempathy-gtk/gossip-chat-window.c b/trunk/libempathy-gtk/gossip-chat-window.c
new file mode 100644
index 000000000..6979e450e
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-chat-window.c
@@ -0,0 +1,1910 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+
+#include <libempathy/empathy-contact-manager.h>
+#include <libempathy/empathy-contact-list.h>
+#include <libempathy/gossip-chatroom-manager.h>
+#include <libempathy/gossip-contact.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-message.h>
+#include <libempathy/gossip-conf.h>
+#include <libempathy/gossip-utils.h>
+
+#include "gossip-chat-window.h"
+#include "empathy-images.h"
+//#include "gossip-add-contact-dialog.h"
+//#include "gossip-chat-invite.h"
+//#include "gossip-contact-info-dialog.h"
+//#include "gossip-log-window.h"
+#include "gossip-new-chatroom-dialog.h"
+#include "gossip-preferences.h"
+#include "gossip-private-chat.h"
+#include "gossip-group-chat.h"
+//#include "gossip-sound.h"
+#include "gossip-ui-utils.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHAT_WINDOW, GossipChatWindowPriv))
+
+#define DEBUG_DOMAIN "ChatWindow"
+
+#define URGENCY_TIMEOUT 60*1000
+
+struct _GossipChatWindowPriv {
+ GList *chats;
+ GList *chats_new_msg;
+ GList *chats_composing;
+
+ GossipChat *current_chat;
+
+ gboolean page_added;
+ gboolean dnd_same_window;
+
+ guint urgency_timeout_id;
+
+ GtkWidget *dialog;
+ GtkWidget *notebook;
+
+ GtkTooltips *tooltips;
+
+ /* Menu items. */
+ GtkWidget *menu_conv_clear;
+ GtkWidget *menu_conv_insert_smiley;
+ GtkWidget *menu_conv_log;
+ GtkWidget *menu_conv_separator;
+ GtkWidget *menu_conv_add_contact;
+ GtkWidget *menu_conv_info;
+ GtkWidget *menu_conv_close;
+
+ GtkWidget *menu_room;
+ GtkWidget *menu_room_set_topic;
+ GtkWidget *menu_room_join_new;
+ GtkWidget *menu_room_invite;
+ GtkWidget *menu_room_add;
+ GtkWidget *menu_room_show_contacts;
+
+ GtkWidget *menu_edit_cut;
+ GtkWidget *menu_edit_copy;
+ GtkWidget *menu_edit_paste;
+
+ GtkWidget *menu_tabs_next;
+ GtkWidget *menu_tabs_prev;
+ GtkWidget *menu_tabs_left;
+ GtkWidget *menu_tabs_right;
+ GtkWidget *menu_tabs_detach;
+
+ guint save_geometry_id;
+};
+
+static void gossip_chat_window_class_init (GossipChatWindowClass *klass);
+static void gossip_chat_window_init (GossipChatWindow *window);
+static void gossip_chat_window_finalize (GObject *object);
+static void chat_window_accel_cb (GtkAccelGroup *accelgroup,
+ GObject *object,
+ guint key,
+ GdkModifierType mod,
+ GossipChatWindow *window);
+static void chat_window_close_clicked_cb (GtkWidget *button,
+ GossipChat *chat);
+static GtkWidget *chat_window_create_label (GossipChatWindow *window,
+ GossipChat *chat);
+static void chat_window_update_status (GossipChatWindow *window,
+ GossipChat *chat);
+static void chat_window_update_title (GossipChatWindow *window,
+ GossipChat *chat);
+static void chat_window_update_menu (GossipChatWindow *window);
+static gboolean chat_window_save_geometry_timeout_cb (GossipChatWindow *window);
+static gboolean chat_window_configure_event_cb (GtkWidget *widget,
+ GdkEventConfigure *event,
+ GossipChatWindow *window);
+static void chat_window_conv_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_clear_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_info_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_add_contact_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_log_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_show_contacts_toggled_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_edit_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_insert_smiley_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_close_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_room_set_topic_activate_cb(GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_room_join_new_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_room_invite_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_room_add_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_cut_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_copy_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_paste_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_tabs_left_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_tabs_right_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_detach_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static gboolean chat_window_delete_event_cb (GtkWidget *dialog,
+ GdkEvent *event,
+ GossipChatWindow *window);
+static void chat_window_status_changed_cb (GossipChat *chat,
+ GossipChatWindow *window);
+static void chat_window_update_tooltip (GossipChatWindow *window,
+ GossipChat *chat);
+static void chat_window_name_changed_cb (GossipChat *chat,
+ const gchar *name,
+ GossipChatWindow *window);
+static void chat_window_composing_cb (GossipChat *chat,
+ gboolean is_composing,
+ GossipChatWindow *window);
+static void chat_window_new_message_cb (GossipChat *chat,
+ GossipMessage *message,
+ gboolean is_backlog,
+ GossipChatWindow *window);
+static GtkNotebook* chat_window_detach_hook (GtkNotebook *source,
+ GtkWidget *page,
+ gint x,
+ gint y,
+ gpointer user_data);
+static void chat_window_page_switched_cb (GtkNotebook *notebook,
+ GtkNotebookPage *page,
+ gint page_num,
+ GossipChatWindow *window);
+static void chat_window_page_reordered_cb (GtkNotebook *notebook,
+ GtkWidget *widget,
+ guint page_num,
+ GossipChatWindow *window);
+static void chat_window_page_added_cb (GtkNotebook *notebook,
+ GtkWidget *child,
+ guint page_num,
+ GossipChatWindow *window);
+static void chat_window_page_removed_cb (GtkNotebook *notebook,
+ GtkWidget *child,
+ guint page_num,
+ GossipChatWindow *window);
+static gboolean chat_window_focus_in_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ GossipChatWindow *window);
+static void chat_window_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ GossipChatWindow *window);
+static void chat_window_set_urgency_hint (GossipChatWindow *window,
+ gboolean urgent);
+
+
+static GList *chat_windows = NULL;
+
+static const guint tab_accel_keys[] = {
+ GDK_1, GDK_2, GDK_3, GDK_4, GDK_5,
+ GDK_6, GDK_7, GDK_8, GDK_9, GDK_0
+};
+
+typedef enum {
+ DND_DRAG_TYPE_CONTACT_ID,
+ DND_DRAG_TYPE_TAB
+} DndDragType;
+
+static const GtkTargetEntry drag_types_dest[] = {
+ { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
+ { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
+};
+
+G_DEFINE_TYPE (GossipChatWindow, gossip_chat_window, G_TYPE_OBJECT);
+
+static void
+gossip_chat_window_class_init (GossipChatWindowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gossip_chat_window_finalize;
+
+ g_type_class_add_private (object_class, sizeof (GossipChatWindowPriv));
+
+ /* Set up a style for the close button with no focus padding. */
+ gtk_rc_parse_string (
+ "style \"gossip-close-button-style\"\n"
+ "{\n"
+ " GtkWidget::focus-padding = 0\n"
+ " xthickness = 0\n"
+ " ythickness = 0\n"
+ "}\n"
+ "widget \"*.gossip-close-button\" style \"gossip-close-button-style\"");
+
+ gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL);
+}
+
+static void
+gossip_chat_window_init (GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GladeXML *glade;
+ GtkAccelGroup *accel_group;
+ GtkWidget *image;
+ GClosure *closure;
+ GtkWidget *menu_conv;
+ GtkWidget *menu;
+ gint i;
+ GtkWidget *chat_vbox;
+
+ priv = GET_PRIV (window);
+
+ priv->tooltips = g_object_ref_sink (gtk_tooltips_new ());
+
+ glade = gossip_glade_get_file ("gossip-chat.glade",
+ "chat_window",
+ NULL,
+ "chat_window", &priv->dialog,
+ "chat_vbox", &chat_vbox,
+ "menu_conv", &menu_conv,
+ "menu_conv_clear", &priv->menu_conv_clear,
+ "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
+ "menu_conv_log", &priv->menu_conv_log,
+ "menu_conv_separator", &priv->menu_conv_separator,
+ "menu_conv_add_contact", &priv->menu_conv_add_contact,
+ "menu_conv_info", &priv->menu_conv_info,
+ "menu_conv_close", &priv->menu_conv_close,
+ "menu_room", &priv->menu_room,
+ "menu_room_set_topic", &priv->menu_room_set_topic,
+ "menu_room_join_new", &priv->menu_room_join_new,
+ "menu_room_invite", &priv->menu_room_invite,
+ "menu_room_add", &priv->menu_room_add,
+ "menu_room_show_contacts", &priv->menu_room_show_contacts,
+ "menu_edit_cut", &priv->menu_edit_cut,
+ "menu_edit_copy", &priv->menu_edit_copy,
+ "menu_edit_paste", &priv->menu_edit_paste,
+ "menu_tabs_next", &priv->menu_tabs_next,
+ "menu_tabs_prev", &priv->menu_tabs_prev,
+ "menu_tabs_left", &priv->menu_tabs_left,
+ "menu_tabs_right", &priv->menu_tabs_right,
+ "menu_tabs_detach", &priv->menu_tabs_detach,
+ NULL);
+
+ gossip_glade_connect (glade,
+ window,
+ "chat_window", "configure-event", chat_window_configure_event_cb,
+ "menu_conv", "activate", chat_window_conv_activate_cb,
+ "menu_conv_clear", "activate", chat_window_clear_activate_cb,
+ "menu_conv_log", "activate", chat_window_log_activate_cb,
+ "menu_conv_add_contact", "activate", chat_window_add_contact_activate_cb,
+ "menu_conv_info", "activate", chat_window_info_activate_cb,
+ "menu_conv_close", "activate", chat_window_close_activate_cb,
+ "menu_room_set_topic", "activate", chat_window_room_set_topic_activate_cb,
+ "menu_room_join_new", "activate", chat_window_room_join_new_activate_cb,
+ "menu_room_invite", "activate", chat_window_room_invite_activate_cb,
+ "menu_room_add", "activate", chat_window_room_add_activate_cb,
+ "menu_edit", "activate", chat_window_edit_activate_cb,
+ "menu_edit_cut", "activate", chat_window_cut_activate_cb,
+ "menu_edit_copy", "activate", chat_window_copy_activate_cb,
+ "menu_edit_paste", "activate", chat_window_paste_activate_cb,
+ "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
+ "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
+ "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
+ NULL);
+
+ g_object_unref (glade);
+
+ priv->notebook = gtk_notebook_new ();
+ gtk_notebook_set_group_id (GTK_NOTEBOOK (priv->notebook), 1);
+ gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
+ gtk_widget_show (priv->notebook);
+
+ /* Set up accels */
+ accel_group = gtk_accel_group_new ();
+ gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
+
+ for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
+ closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
+ window,
+ NULL);
+ gtk_accel_group_connect (accel_group,
+ tab_accel_keys[i],
+ GDK_MOD1_MASK,
+ 0,
+ closure);
+ }
+
+ g_object_unref (accel_group);
+
+ /* Set the contact information menu item image to the Gossip
+ * stock image
+ */
+ image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (priv->menu_conv_info));
+ gtk_image_set_from_icon_name (GTK_IMAGE (image),
+ EMPATHY_IMAGE_CONTACT_INFORMATION,
+ GTK_ICON_SIZE_MENU);
+
+ /* Set up smiley menu */
+ menu = gossip_chat_view_get_smiley_menu (
+ G_CALLBACK (chat_window_insert_smiley_activate_cb),
+ window,
+ priv->tooltips);
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (priv->menu_conv_insert_smiley), menu);
+
+ /* Set up signals we can't do with glade since we may need to
+ * block/unblock them at some later stage.
+ */
+
+ g_signal_connect (priv->dialog,
+ "delete_event",
+ G_CALLBACK (chat_window_delete_event_cb),
+ window);
+
+ g_signal_connect (priv->menu_room_show_contacts,
+ "toggled",
+ G_CALLBACK (chat_window_show_contacts_toggled_cb),
+ window);
+
+ g_signal_connect_swapped (priv->menu_tabs_prev,
+ "activate",
+ G_CALLBACK (gtk_notebook_prev_page),
+ priv->notebook);
+ g_signal_connect_swapped (priv->menu_tabs_next,
+ "activate",
+ G_CALLBACK (gtk_notebook_next_page),
+ priv->notebook);
+
+ g_signal_connect (priv->dialog,
+ "focus_in_event",
+ G_CALLBACK (chat_window_focus_in_event_cb),
+ window);
+ g_signal_connect_after (priv->notebook,
+ "switch_page",
+ G_CALLBACK (chat_window_page_switched_cb),
+ window);
+ g_signal_connect (priv->notebook,
+ "page_reordered",
+ G_CALLBACK (chat_window_page_reordered_cb),
+ window);
+ g_signal_connect (priv->notebook,
+ "page_added",
+ G_CALLBACK (chat_window_page_added_cb),
+ window);
+ g_signal_connect (priv->notebook,
+ "page_removed",
+ G_CALLBACK (chat_window_page_removed_cb),
+ window);
+
+ /* Set up drag and drop */
+ gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
+ GTK_DEST_DEFAULT_ALL,
+ drag_types_dest,
+ G_N_ELEMENTS (drag_types_dest),
+ GDK_ACTION_MOVE);
+
+ g_signal_connect (priv->notebook,
+ "drag-data-received",
+ G_CALLBACK (chat_window_drag_data_received),
+ window);
+
+ chat_windows = g_list_prepend (chat_windows, window);
+
+ /* Set up private details */
+ priv->chats = NULL;
+ priv->chats_new_msg = NULL;
+ priv->chats_composing = NULL;
+ priv->current_chat = NULL;
+}
+
+/* Returns the window to open a new tab in if there is only one window
+ * visble, otherwise, returns NULL indicating that a new window should
+ * be added.
+ */
+GossipChatWindow *
+gossip_chat_window_get_default (void)
+{
+ GList *l;
+ gboolean separate_windows = TRUE;
+
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_UI_SEPARATE_CHAT_WINDOWS,
+ &separate_windows);
+
+ if (separate_windows) {
+ /* Always create a new window */
+ return NULL;
+ }
+
+ for (l = chat_windows; l; l = l->next) {
+ GossipChatWindow *chat_window;
+ GtkWidget *dialog;
+ GdkWindow *window;
+ gboolean visible;
+
+ chat_window = l->data;
+
+ dialog = gossip_chat_window_get_dialog (chat_window);
+ window = dialog->window;
+
+ g_object_get (dialog,
+ "visible", &visible,
+ NULL);
+
+ visible = visible && !(gdk_window_get_state (window) & GDK_WINDOW_STATE_ICONIFIED);
+
+ if (visible) {
+ /* Found a visible window on this desktop */
+ return chat_window;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+gossip_chat_window_finalize (GObject *object)
+{
+ GossipChatWindow *window;
+ GossipChatWindowPriv *priv;
+
+ window = GOSSIP_CHAT_WINDOW (object);
+ priv = GET_PRIV (window);
+
+ gossip_debug (DEBUG_DOMAIN, "Finalized: %p", object);
+
+ if (priv->save_geometry_id != 0) {
+ g_source_remove (priv->save_geometry_id);
+ }
+
+ if (priv->urgency_timeout_id != 0) {
+ g_source_remove (priv->urgency_timeout_id);
+ }
+
+ chat_windows = g_list_remove (chat_windows, window);
+ gtk_widget_destroy (priv->dialog);
+
+ g_object_unref (priv->tooltips);
+
+ G_OBJECT_CLASS (gossip_chat_window_parent_class)->finalize (object);
+}
+
+static void
+chat_window_accel_cb (GtkAccelGroup *accelgroup,
+ GObject *object,
+ guint key,
+ GdkModifierType mod,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gint num = -1;
+ gint i;
+
+ priv = GET_PRIV (window);
+
+ for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
+ if (tab_accel_keys[i] == key) {
+ num = i;
+ break;
+ }
+ }
+
+ if (num != -1) {
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
+ }
+}
+
+static void
+chat_window_close_clicked_cb (GtkWidget *button,
+ GossipChat *chat)
+{
+ GossipChatWindow *window;
+
+ window = gossip_chat_get_window (chat);
+ gossip_chat_window_remove_chat (window, chat);
+}
+
+static void
+chat_window_close_button_style_set_cb (GtkWidget *button,
+ GtkStyle *previous_style,
+ gpointer user_data)
+{
+ gint h, w;
+
+ gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
+ GTK_ICON_SIZE_MENU, &w, &h);
+
+ gtk_widget_set_size_request (button, w, h);
+}
+
+static GtkWidget *
+chat_window_create_label (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ GtkWidget *hbox;
+ GtkWidget *name_label;
+ GtkWidget *status_image;
+ GtkWidget *close_button;
+ GtkWidget *close_image;
+ GtkWidget *event_box;
+ GtkWidget *event_box_hbox;
+ PangoAttrList *attr_list;
+ PangoAttribute *attr;
+
+ priv = GET_PRIV (window);
+
+ /* The spacing between the button and the label. */
+ hbox = gtk_hbox_new (FALSE, 0);
+
+ event_box = gtk_event_box_new ();
+ gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
+
+ name_label = gtk_label_new (gossip_chat_get_name (chat));
+ gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
+
+ attr_list = pango_attr_list_new ();
+ attr = pango_attr_scale_new (1/1.2);
+ attr->start_index = 0;
+ attr->end_index = -1;
+ pango_attr_list_insert (attr_list, attr);
+ gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
+ pango_attr_list_unref (attr_list);
+
+ gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
+ gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
+ g_object_set_data (G_OBJECT (chat), "chat-window-tab-label", name_label);
+
+ status_image = gtk_image_new ();
+
+ /* Spacing between the icon and label. */
+ event_box_hbox = gtk_hbox_new (FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
+
+ g_object_set_data (G_OBJECT (chat), "chat-window-tab-image", status_image);
+ g_object_set_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget", event_box);
+
+ close_button = gtk_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
+
+ /* We don't want focus/keynav for the button to avoid clutter, and
+ * Ctrl-W works anyway.
+ */
+ GTK_WIDGET_UNSET_FLAGS (close_button, GTK_CAN_FOCUS);
+ GTK_WIDGET_UNSET_FLAGS (close_button, GTK_CAN_DEFAULT);
+
+ /* Set the name to make the special rc style match. */
+ gtk_widget_set_name (close_button, "gossip-close-button");
+
+ close_image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
+
+ gtk_container_add (GTK_CONTAINER (close_button), close_image);
+
+ gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
+ gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
+ gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
+
+ /* React to theme changes and also used to setup the initial size
+ * correctly.
+ */
+ g_signal_connect (close_button,
+ "style-set",
+ G_CALLBACK (chat_window_close_button_style_set_cb),
+ chat);
+
+ g_signal_connect (close_button,
+ "clicked",
+ G_CALLBACK (chat_window_close_clicked_cb),
+ chat);
+
+ /* Set up tooltip */
+ chat_window_update_tooltip (window, chat);
+
+ gtk_widget_show_all (hbox);
+
+ return hbox;
+}
+
+static void
+chat_window_update_status (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ GtkImage *image;
+ const gchar *icon_name = NULL;
+
+ priv = GET_PRIV (window);
+
+ if (g_list_find (priv->chats_new_msg, chat)) {
+ icon_name = EMPATHY_IMAGE_MESSAGE;
+ }
+ else if (g_list_find (priv->chats_composing, chat)) {
+ icon_name = EMPATHY_IMAGE_TYPING;
+ }
+ else {
+ icon_name = gossip_chat_get_status_icon_name (chat);
+ }
+ image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
+ gtk_image_set_from_icon_name (image, icon_name, GTK_ICON_SIZE_MENU);
+
+ chat_window_update_title (window, chat);
+ chat_window_update_tooltip (window, chat);
+}
+
+static void
+chat_window_update_title (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ const gchar *str;
+ gchar *title;
+ gint n_chats;
+
+ priv = GET_PRIV (window);
+
+ n_chats = g_list_length (priv->chats);
+ if (n_chats == 1) {
+ if (priv->chats_new_msg) {
+ title = g_strdup_printf (
+ "%s - %s",
+ gossip_chat_get_name (priv->current_chat),
+ _("New Message"));
+ }
+ else if (gossip_chat_is_group_chat (priv->current_chat)) {
+ title = g_strdup_printf (
+ "%s - %s",
+ gossip_chat_get_name (priv->current_chat),
+ _("Chat Room"));
+ } else {
+ title = g_strdup_printf (
+ "%s - %s",
+ gossip_chat_get_name (priv->current_chat),
+ _("Conversation"));
+ }
+ } else {
+ if (priv->chats_new_msg) {
+ GString *names;
+ GList *l;
+ gint n_messages = 0;
+
+ names = g_string_new (NULL);
+
+ for (l = priv->chats_new_msg; l; l = l->next) {
+ n_messages++;
+ g_string_append (names,
+ gossip_chat_get_name (l->data));
+ if (l->next) {
+ g_string_append (names, ", ");
+ }
+ }
+
+ str = ngettext ("New Message", "New Messages", n_messages);
+ title = g_strdup_printf ("%s - %s", names->str, str);
+ g_string_free (names, TRUE);
+ } else {
+ str = ngettext ("Conversation", "Conversations (%d)", n_chats);
+ title = g_strdup_printf (str, n_chats);
+ }
+ }
+
+ gtk_window_set_title (GTK_WINDOW (priv->dialog), title);
+ g_free (title);
+
+ if (priv->chats_new_msg) {
+ gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
+ EMPATHY_IMAGE_MESSAGE);
+ } else {
+ gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
+ }
+}
+
+static void
+chat_window_update_menu (GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gboolean first_page;
+ gboolean last_page;
+ gboolean is_connected;
+ gint num_pages;
+ gint page_num;
+
+ priv = GET_PRIV (window);
+
+ /* Notebook pages */
+ page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
+ num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
+ first_page = (page_num == 0);
+ last_page = (page_num == (num_pages - 1));
+
+ gtk_widget_set_sensitive (priv->menu_tabs_next, !last_page);
+ gtk_widget_set_sensitive (priv->menu_tabs_prev, !first_page);
+ gtk_widget_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
+ gtk_widget_set_sensitive (priv->menu_tabs_left, !first_page);
+ gtk_widget_set_sensitive (priv->menu_tabs_right, !last_page);
+
+ is_connected = gossip_chat_is_connected (priv->current_chat);
+
+ if (gossip_chat_is_group_chat (priv->current_chat)) {
+ GossipGroupChat *group_chat;
+ GossipChatroom *chatroom;
+ GossipChatroomManager *manager;
+ gboolean show_contacts;
+
+ group_chat = GOSSIP_GROUP_CHAT (priv->current_chat);
+
+ /* Show / Hide widgets */
+ gtk_widget_show (priv->menu_room);
+
+ gtk_widget_hide (priv->menu_conv_add_contact);
+ gtk_widget_hide (priv->menu_conv_info);
+ gtk_widget_hide (priv->menu_conv_separator);
+
+ /* Can we add this room to our favourites and are we
+ * connected to the room?
+ */
+ manager = gossip_chatroom_manager_new ();
+ chatroom = gossip_chatroom_manager_find (manager,
+ priv->current_chat->account,
+ gossip_chat_get_id (priv->current_chat));
+ g_object_unref (manager);
+
+ gtk_widget_set_sensitive (priv->menu_room_add, chatroom == NULL);
+ gtk_widget_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
+ gtk_widget_set_sensitive (priv->menu_room_join_new, is_connected);
+ gtk_widget_set_sensitive (priv->menu_room_invite, is_connected);
+
+ /* We need to block the signal here because all we are
+ * really trying to do is check or uncheck the menu
+ * item. If we don't do this we get funny behaviour
+ * with 2 or more group chat windows where showing
+ * contacts doesn't do anything.
+ */
+ show_contacts = gossip_group_chat_get_show_contacts (group_chat);
+
+ g_signal_handlers_block_by_func (priv->menu_room_show_contacts,
+ chat_window_show_contacts_toggled_cb,
+ window);
+
+ g_object_set (priv->menu_room_show_contacts,
+ "active", show_contacts,
+ NULL);
+
+ g_signal_handlers_unblock_by_func (priv->menu_room_show_contacts,
+ chat_window_show_contacts_toggled_cb,
+ window);
+ } else {
+ GossipPrivateChat *chat;
+ GossipSubscription subscription;
+ GossipContact *contact;
+
+ chat = GOSSIP_PRIVATE_CHAT (priv->current_chat);
+
+ /* Show / Hide widgets */
+ gtk_widget_hide (priv->menu_room);
+
+ contact = gossip_private_chat_get_contact (chat);
+ subscription = gossip_contact_get_subscription (contact);
+ if (!(subscription & GOSSIP_SUBSCRIPTION_FROM)) {
+ gtk_widget_show (priv->menu_conv_add_contact);
+ } else {
+ gtk_widget_hide (priv->menu_conv_add_contact);
+ }
+
+ gtk_widget_show (priv->menu_conv_separator);
+ gtk_widget_show (priv->menu_conv_info);
+
+ /* Are we connected? */
+ gtk_widget_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
+ gtk_widget_set_sensitive (priv->menu_conv_add_contact, is_connected);
+ gtk_widget_set_sensitive (priv->menu_conv_info, is_connected);
+ }
+}
+
+static void
+chat_window_insert_smiley_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChat *chat;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ const gchar *smiley;
+
+ priv = GET_PRIV (window);
+
+ chat = priv->current_chat;
+
+ smiley = g_object_get_data (G_OBJECT (menuitem), "smiley_text");
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+ gtk_text_buffer_insert (buffer, &iter,
+ smiley, -1);
+}
+
+static void
+chat_window_clear_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ gossip_chat_clear (priv->current_chat);
+}
+
+static void
+chat_window_add_contact_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ //GossipContact *contact;
+
+ priv = GET_PRIV (window);
+
+ //contact = gossip_chat_get_contact (priv->current_chat);
+
+ // FIXME: gossip_add_contact_dialog_show (NULL, contact);
+}
+
+static void
+chat_window_log_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+/* FIXME:
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ if (gossip_chat_is_group_chat (priv->current_chat)) {
+ GossipGroupChat *group_chat;
+ GossipChatroom *chatroom;
+
+ group_chat = GOSSIP_GROUP_CHAT (priv->current_chat);
+ chatroom = gossip_group_chat_get_chatroom (group_chat);
+ gossip_log_window_show (NULL, chatroom);
+ } else {
+ GossipContact *contact;
+
+ contact = gossip_chat_get_contact (priv->current_chat);
+ gossip_log_window_show (contact, NULL);
+ }
+*/
+}
+
+static void
+chat_window_info_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ //GossipContact *contact;
+
+ priv = GET_PRIV (window);
+/*FIXME:
+ contact = gossip_chat_get_contact (priv->current_chat);
+
+ gossip_contact_info_dialog_show (contact,
+ GTK_WINDOW (priv->dialog));*/
+}
+
+static gboolean
+chat_window_save_geometry_timeout_cb (GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gint x, y, w, h;
+
+ priv = GET_PRIV (window);
+
+ gtk_window_get_size (GTK_WINDOW (priv->dialog), &w, &h);
+ gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
+
+ gossip_chat_save_geometry (priv->current_chat, x, y, w, h);
+
+ priv->save_geometry_id = 0;
+
+ return FALSE;
+}
+
+static gboolean
+chat_window_configure_event_cb (GtkWidget *widget,
+ GdkEventConfigure *event,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ /* Only save geometry information if there is ONE chat visible. */
+ if (g_list_length (priv->chats) > 1) {
+ return FALSE;
+ }
+
+ if (priv->save_geometry_id != 0) {
+ g_source_remove (priv->save_geometry_id);
+ }
+
+ priv->save_geometry_id =
+ g_timeout_add (500,
+ (GSourceFunc) chat_window_save_geometry_timeout_cb,
+ window);
+
+ return FALSE;
+}
+
+static void
+chat_window_conv_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gboolean log_exists = FALSE;
+
+ priv = GET_PRIV (window);
+/* FIXME:
+ if (gossip_chat_is_group_chat (priv->current_chat)) {
+ GossipGroupChat *group_chat;
+ GossipChatroom *chatroom;
+
+ group_chat = GOSSIP_GROUP_CHAT (priv->current_chat);
+ chatroom = gossip_group_chat_get_chatroom (group_chat);
+ if (chatroom) {
+ log_exists = gossip_log_exists_for_chatroom (chatroom);
+ }
+ } else {
+ GossipContact *contact;
+
+ contact = gossip_chat_get_contact (priv->current_chat);
+ if (contact) {
+ log_exists = gossip_log_exists_for_contact (contact);
+ }
+ }
+*/
+ gtk_widget_set_sensitive (priv->menu_conv_log, log_exists);
+}
+
+static void
+chat_window_show_contacts_toggled_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gboolean show;
+
+ priv = GET_PRIV (window);
+
+ g_return_if_fail (priv->current_chat != NULL);
+
+ show = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (priv->menu_room_show_contacts));
+ gossip_group_chat_set_show_contacts (GOSSIP_GROUP_CHAT (priv->current_chat), show);
+}
+
+static void
+chat_window_close_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ g_return_if_fail (priv->current_chat != NULL);
+
+ gossip_chat_window_remove_chat (window, priv->current_chat);
+}
+
+static void
+chat_window_room_set_topic_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ if (gossip_chat_is_group_chat (priv->current_chat)) {
+ GossipGroupChat *group_chat;
+
+ group_chat = GOSSIP_GROUP_CHAT (priv->current_chat);
+ gossip_group_chat_set_topic (group_chat);
+ }
+}
+
+static void
+chat_window_room_join_new_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ gossip_new_chatroom_dialog_show (GTK_WINDOW (priv->dialog));
+}
+
+static void
+chat_window_room_invite_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+/* FIXME:
+ GossipChatWindowPriv *priv;
+ GossipContact *own_contact;
+ GossipChatroomId id = 0;
+
+ priv = GET_PRIV (window);
+ own_contact = gossip_chat_get_own_contact (priv->current_chat);
+
+ if (gossip_chat_is_group_chat (priv->current_chat)) {
+ GossipGroupChat *group_chat;
+
+ group_chat = GOSSIP_GROUP_CHAT (priv->current_chat);
+ id = gossip_group_chat_get_chatroom_id (group_chat);
+ }
+
+ gossip_chat_invite_dialog_show (own_contact, id);
+*/
+}
+
+static void
+chat_window_room_add_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChatroomManager *manager;
+ GossipChatroom *chatroom;
+
+ priv = GET_PRIV (window);
+
+ g_return_if_fail (priv->current_chat != NULL);
+
+ if (!gossip_chat_is_group_chat (priv->current_chat)) {
+ return;
+ }
+
+ chatroom = gossip_chatroom_new_full (priv->current_chat->account,
+ gossip_chat_get_id (priv->current_chat),
+ gossip_chat_get_name (priv->current_chat),
+ FALSE);
+
+ manager = gossip_chatroom_manager_new ();
+ gossip_chatroom_manager_add (manager, chatroom);
+ chat_window_update_menu (window);
+
+ g_object_unref (chatroom);
+ g_object_unref (manager);
+}
+
+static void
+chat_window_edit_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GtkClipboard *clipboard;
+ GtkTextBuffer *buffer;
+ gboolean text_available;
+
+ priv = GET_PRIV (window);
+
+ g_return_if_fail (priv->current_chat != NULL);
+
+ if (!gossip_chat_is_connected (priv->current_chat)) {
+ gtk_widget_set_sensitive (priv->menu_edit_copy, FALSE);
+ gtk_widget_set_sensitive (priv->menu_edit_cut, FALSE);
+ gtk_widget_set_sensitive (priv->menu_edit_paste, FALSE);
+ return;
+ }
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
+ if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
+ gtk_widget_set_sensitive (priv->menu_edit_copy, TRUE);
+ gtk_widget_set_sensitive (priv->menu_edit_cut, TRUE);
+ } else {
+ gboolean selection;
+
+ selection = gossip_chat_view_get_selection_bounds (priv->current_chat->view,
+ NULL, NULL);
+
+ gtk_widget_set_sensitive (priv->menu_edit_cut, FALSE);
+ gtk_widget_set_sensitive (priv->menu_edit_copy, selection);
+ }
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ text_available = gtk_clipboard_wait_is_text_available (clipboard);
+ gtk_widget_set_sensitive (priv->menu_edit_paste, text_available);
+}
+
+static void
+chat_window_cut_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_WINDOW (window));
+
+ priv = GET_PRIV (window);
+
+ gossip_chat_cut (priv->current_chat);
+}
+
+static void
+chat_window_copy_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_WINDOW (window));
+
+ priv = GET_PRIV (window);
+
+ gossip_chat_copy (priv->current_chat);
+}
+
+static void
+chat_window_paste_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_WINDOW (window));
+
+ priv = GET_PRIV (window);
+
+ gossip_chat_paste (priv->current_chat);
+}
+
+static void
+chat_window_tabs_left_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChat *chat;
+ gint index;
+
+ priv = GET_PRIV (window);
+
+ chat = priv->current_chat;
+ index = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
+ if (index <= 0) {
+ return;
+ }
+
+ gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
+ gossip_chat_get_widget (chat),
+ index - 1);
+
+ chat_window_update_menu (window);
+ chat_window_update_status (window, chat);
+}
+
+static void
+chat_window_tabs_right_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChat *chat;
+ gint index;
+
+ priv = GET_PRIV (window);
+
+ chat = priv->current_chat;
+ index = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
+
+ gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
+ gossip_chat_get_widget (chat),
+ index + 1);
+
+ chat_window_update_menu (window);
+ chat_window_update_status (window, chat);
+}
+
+static void
+chat_window_detach_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChatWindow *new_window;
+ GossipChat *chat;
+
+ priv = GET_PRIV (window);
+
+ chat = priv->current_chat;
+ new_window = gossip_chat_window_new ();
+
+ gossip_chat_window_move_chat (window, new_window, chat);
+
+ priv = GET_PRIV (new_window);
+ gtk_widget_show (priv->dialog);
+}
+
+static gboolean
+chat_window_delete_event_cb (GtkWidget *dialog,
+ GdkEvent *event,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GList *list;
+ GList *l;
+
+ priv = GET_PRIV (window);
+
+ gossip_debug (DEBUG_DOMAIN, "Delete event received");
+
+ list = g_list_copy (priv->chats);
+
+ for (l = list; l; l = l->next) {
+ gossip_chat_window_remove_chat (window, l->data);
+ }
+
+ g_list_free (list);
+
+ return TRUE;
+}
+
+static void
+chat_window_status_changed_cb (GossipChat *chat,
+ GossipChatWindow *window)
+{
+ chat_window_update_menu (window);
+ chat_window_update_status (window, chat);
+}
+
+static void
+chat_window_update_tooltip (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ GtkWidget *widget;
+ gchar *current_tooltip;
+ gchar *str;
+
+ priv = GET_PRIV (window);
+
+ current_tooltip = gossip_chat_get_tooltip (chat);
+
+ if (g_list_find (priv->chats_composing, chat)) {
+ str = g_strconcat (current_tooltip, "\n", _("Typing a message."), NULL);
+ g_free (current_tooltip);
+ } else {
+ str = current_tooltip;
+ }
+
+ widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
+ gtk_tooltips_set_tip (priv->tooltips,
+ widget,
+ str,
+ NULL);
+
+ g_free (str);
+}
+
+static void
+chat_window_name_changed_cb (GossipChat *chat,
+ const gchar *name,
+ GossipChatWindow *window)
+{
+ GtkLabel *label;
+
+ label = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
+
+ gtk_label_set_text (label, name);
+}
+
+static void
+chat_window_composing_cb (GossipChat *chat,
+ gboolean is_composing,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ if (is_composing && !g_list_find (priv->chats_composing, chat)) {
+ priv->chats_composing = g_list_prepend (priv->chats_composing, chat);
+ } else {
+ priv->chats_composing = g_list_remove (priv->chats_composing, chat);
+ }
+
+ chat_window_update_status (window, chat);
+}
+
+static void
+chat_window_new_message_cb (GossipChat *chat,
+ GossipMessage *message,
+ gboolean is_backlog,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gboolean has_focus;
+ gboolean needs_urgency;
+
+ priv = GET_PRIV (window);
+
+ has_focus = gossip_chat_window_has_focus (window);
+
+ if (has_focus && priv->current_chat == chat) {
+ gossip_debug (DEBUG_DOMAIN, "New message, we have focus");
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "New message, no focus");
+
+ needs_urgency = FALSE;
+ if (gossip_chat_is_group_chat (chat)) {
+ if (!is_backlog &&
+ gossip_chat_should_highlight_nick (message)) {
+ gossip_debug (DEBUG_DOMAIN, "Highlight this nick");
+ needs_urgency = TRUE;
+ }
+ } else {
+ needs_urgency = TRUE;
+ }
+
+ if (needs_urgency && !has_focus) {
+ chat_window_set_urgency_hint (window, TRUE);
+ }
+
+ if (!is_backlog &&
+ !g_list_find (priv->chats_new_msg, chat)) {
+ priv->chats_new_msg = g_list_prepend (priv->chats_new_msg, chat);
+ chat_window_update_status (window, chat);
+ }
+}
+
+static GtkNotebook *
+chat_window_detach_hook (GtkNotebook *source,
+ GtkWidget *page,
+ gint x,
+ gint y,
+ gpointer user_data)
+{
+ GossipChatWindowPriv *priv;
+ GossipChatWindow *window, *new_window;
+ GossipChat *chat;
+
+ chat = g_object_get_data (G_OBJECT (page), "chat");
+ window = gossip_chat_get_window (chat);
+
+ new_window = gossip_chat_window_new ();
+ priv = GET_PRIV (new_window);
+
+ gossip_debug (DEBUG_DOMAIN, "Detach hook called");
+
+ gossip_chat_window_move_chat (window, new_window, chat);
+
+ gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
+ gtk_widget_show (priv->dialog);
+
+ return NULL;
+}
+
+static void
+chat_window_page_switched_cb (GtkNotebook *notebook,
+ GtkNotebookPage *page,
+ gint page_num,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChat *chat;
+ GtkWidget *child;
+
+ gossip_debug (DEBUG_DOMAIN, "Page switched");
+
+ priv = GET_PRIV (window);
+
+ child = gtk_notebook_get_nth_page (notebook, page_num);
+ chat = g_object_get_data (G_OBJECT (child), "chat");
+
+ if (priv->page_added) {
+ priv->page_added = FALSE;
+ gossip_chat_scroll_down (chat);
+ }
+ else if (priv->current_chat == chat) {
+ return;
+ }
+
+ priv->current_chat = chat;
+ priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
+
+ chat_window_update_menu (window);
+ chat_window_update_status (window, chat);
+}
+
+static void
+chat_window_page_reordered_cb (GtkNotebook *notebook,
+ GtkWidget *widget,
+ guint page_num,
+ GossipChatWindow *window)
+{
+ gossip_debug (DEBUG_DOMAIN, "Page reordered");
+
+ chat_window_update_menu (window);
+}
+
+static void
+chat_window_page_added_cb (GtkNotebook *notebook,
+ GtkWidget *child,
+ guint page_num,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChat *chat;
+
+ priv = GET_PRIV (window);
+
+ /* If we just received DND to the same window, we don't want
+ * to do anything here like removing the tab and then readding
+ * it, so we return here and in "page-added".
+ */
+ if (priv->dnd_same_window) {
+ gossip_debug (DEBUG_DOMAIN, "Page added (back to the same window)");
+ priv->dnd_same_window = FALSE;
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Page added");
+
+ /* Get chat object */
+ chat = g_object_get_data (G_OBJECT (child), "chat");
+
+ /* Set the chat window */
+ gossip_chat_set_window (chat, window);
+
+ /* Connect chat signals for this window */
+ g_signal_connect (chat, "status-changed",
+ G_CALLBACK (chat_window_status_changed_cb),
+ window);
+ g_signal_connect (chat, "name-changed",
+ G_CALLBACK (chat_window_name_changed_cb),
+ window);
+ g_signal_connect (chat, "composing",
+ G_CALLBACK (chat_window_composing_cb),
+ window);
+ g_signal_connect (chat, "new-message",
+ G_CALLBACK (chat_window_new_message_cb),
+ window);
+
+ /* Set flag so we know to perform some special operations on
+ * switch page due to the new page being added.
+ */
+ priv->page_added = TRUE;
+
+ /* Get list of chats up to date */
+ priv->chats = g_list_append (priv->chats, chat);
+}
+
+static void
+chat_window_page_removed_cb (GtkNotebook *notebook,
+ GtkWidget *child,
+ guint page_num,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChat *chat;
+
+ priv = GET_PRIV (window);
+
+ /* If we just received DND to the same window, we don't want
+ * to do anything here like removing the tab and then readding
+ * it, so we return here and in "page-added".
+ */
+ if (priv->dnd_same_window) {
+ gossip_debug (DEBUG_DOMAIN, "Page removed (and will be readded to same window)");
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Page removed");
+
+ /* Get chat object */
+ chat = g_object_get_data (G_OBJECT (child), "chat");
+
+ /* Unset the window associated with a chat */
+ gossip_chat_set_window (chat, NULL);
+
+ /* Disconnect all signal handlers for this chat and this window */
+ g_signal_handlers_disconnect_by_func (chat,
+ G_CALLBACK (chat_window_status_changed_cb),
+ window);
+ g_signal_handlers_disconnect_by_func (chat,
+ G_CALLBACK (chat_window_name_changed_cb),
+ window);
+ g_signal_handlers_disconnect_by_func (chat,
+ G_CALLBACK (chat_window_composing_cb),
+ window);
+ g_signal_handlers_disconnect_by_func (chat,
+ G_CALLBACK (chat_window_new_message_cb),
+ window);
+
+ /* Keep list of chats up to date */
+ priv->chats = g_list_remove (priv->chats, chat);
+ priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
+ priv->chats_composing = g_list_remove (priv->chats_composing, chat);
+
+ if (priv->chats == NULL) {
+ g_object_unref (window);
+ } else {
+ chat_window_update_menu (window);
+ chat_window_update_title (window, NULL);
+ }
+}
+
+static gboolean
+chat_window_focus_in_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ gossip_debug (DEBUG_DOMAIN, "Focus in event, updating title");
+
+ priv = GET_PRIV (window);
+
+ priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat);
+
+ chat_window_set_urgency_hint (window, FALSE);
+
+ /* Update the title, since we now mark all unread messages as read. */
+ chat_window_update_status (window, priv->current_chat);
+
+ return FALSE;
+}
+
+static void
+chat_window_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ GossipChatWindow *window)
+{
+ /* FIXME: DnD of contact do not seems to work... */
+ if (info == DND_DRAG_TYPE_CONTACT_ID) {
+ EmpathyContactManager *manager;
+ GossipContact *contact;
+ GossipChat *chat;
+ GossipChatWindow *old_window;
+ McAccount *account;
+ const gchar *id = NULL;
+
+ if (selection) {
+ id = (const gchar*) selection->data;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "DND contact from roster with id:'%s'", id);
+
+ manager = empathy_contact_manager_new ();
+ contact = empathy_contact_list_find (EMPATHY_CONTACT_LIST (manager), id);
+ g_object_unref (manager);
+
+ if (!contact) {
+ gossip_debug (DEBUG_DOMAIN, "DND contact from roster not found");
+ return;
+ }
+
+ account = gossip_contact_get_account (contact);
+ chat = gossip_chat_window_find_chat (account, id);
+ old_window = gossip_chat_get_window (chat);
+
+ if (old_window) {
+ if (old_window == window) {
+ gtk_drag_finish (context, TRUE, FALSE, time);
+ return;
+ }
+
+ gossip_chat_window_move_chat (old_window, window, chat);
+ } else {
+ gossip_chat_window_add_chat (window, chat);
+ }
+
+ /* Added to take care of any outstanding chat events */
+ gossip_chat_present (chat);
+
+ /* We should return TRUE to remove the data when doing
+ * GDK_ACTION_MOVE, but we don't here otherwise it has
+ * weird consequences, and we handle that internally
+ * anyway with add_chat() and remove_chat().
+ */
+ gtk_drag_finish (context, TRUE, FALSE, time);
+ }
+ else if (info == DND_DRAG_TYPE_TAB) {
+ GossipChat *chat = NULL;
+ GossipChatWindow *old_window;
+ GtkWidget **child = NULL;
+
+ gossip_debug (DEBUG_DOMAIN, "DND tab");
+
+ if (selection) {
+ child = (void*) selection->data;
+ }
+
+ if (child) {
+ chat = g_object_get_data (G_OBJECT (*child), "chat");
+ }
+
+ old_window = gossip_chat_get_window (chat);
+ if (old_window) {
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ if (old_window == window) {
+ gossip_debug (DEBUG_DOMAIN, "DND tab (within same window)");
+ priv->dnd_same_window = TRUE;
+ gtk_drag_finish (context, TRUE, FALSE, time);
+ return;
+ }
+
+ priv->dnd_same_window = FALSE;
+ }
+
+ /* We should return TRUE to remove the data when doing
+ * GDK_ACTION_MOVE, but we don't here otherwise it has
+ * weird consequences, and we handle that internally
+ * anyway with add_chat() and remove_chat().
+ */
+ gtk_drag_finish (context, TRUE, FALSE, time);
+ } else {
+ gossip_debug (DEBUG_DOMAIN, "DND from unknown source");
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ }
+}
+
+static gboolean
+chat_window_urgency_timeout_func (GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ gossip_debug (DEBUG_DOMAIN, "Turning off urgency hint");
+ gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), FALSE);
+
+ priv->urgency_timeout_id = 0;
+
+ return FALSE;
+}
+
+static void
+chat_window_set_urgency_hint (GossipChatWindow *window,
+ gboolean urgent)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ if (!urgent) {
+ /* Remove any existing hint and timeout. */
+ if (priv->urgency_timeout_id) {
+ gossip_debug (DEBUG_DOMAIN, "Turning off urgency hint");
+ gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), FALSE);
+ g_source_remove (priv->urgency_timeout_id);
+ priv->urgency_timeout_id = 0;
+ }
+ return;
+ }
+
+ /* Add a new hint and renew any exising timeout or add a new one. */
+ if (priv->urgency_timeout_id) {
+ g_source_remove (priv->urgency_timeout_id);
+ } else {
+ gossip_debug (DEBUG_DOMAIN, "Turning on urgency hint");
+ gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), TRUE);
+ }
+
+ priv->urgency_timeout_id = g_timeout_add (
+ URGENCY_TIMEOUT,
+ (GSourceFunc) chat_window_urgency_timeout_func,
+ window);
+}
+
+GossipChatWindow *
+gossip_chat_window_new (void)
+{
+ return GOSSIP_CHAT_WINDOW (g_object_new (GOSSIP_TYPE_CHAT_WINDOW, NULL));
+}
+
+GtkWidget *
+gossip_chat_window_get_dialog (GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ g_return_val_if_fail (window != NULL, NULL);
+
+ priv = GET_PRIV (window);
+
+ return priv->dialog;
+}
+
+void
+gossip_chat_window_add_chat (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ GtkWidget *label;
+ GtkWidget *child;
+
+ priv = GET_PRIV (window);
+
+ /* Reference the chat object */
+ g_object_ref (chat);
+
+ /* Set the chat window */
+ gossip_chat_set_window (chat, window);
+
+ if (g_list_length (priv->chats) == 0) {
+ gint x, y, w, h;
+
+ gossip_chat_load_geometry (chat, &x, &y, &w, &h);
+
+ if (x >= 0 && y >= 0) {
+ /* Let the window manager position it if we don't have
+ * good x, y coordinates.
+ */
+ gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
+ }
+
+ if (w > 0 && h > 0) {
+ /* Use the defaults from the glade file if we don't have
+ * good w, h geometry.
+ */
+ gtk_window_resize (GTK_WINDOW (priv->dialog), w, h);
+ }
+ }
+
+ child = gossip_chat_get_widget (chat);
+ label = chat_window_create_label (window, chat);
+
+ gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), child, label);
+ gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
+ gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
+ gtk_notebook_set_tab_label_packing (GTK_NOTEBOOK (priv->notebook), child,
+ TRUE, TRUE, GTK_PACK_START);
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Chat added (%d references)",
+ G_OBJECT (chat)->ref_count);
+}
+
+void
+gossip_chat_window_remove_chat (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ gint position;
+
+ priv = GET_PRIV (window);
+
+ position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
+ gossip_chat_get_widget (chat));
+ gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Chat removed (%d references)",
+ G_OBJECT (chat)->ref_count - 1);
+
+ g_object_unref (chat);
+}
+
+void
+gossip_chat_window_move_chat (GossipChatWindow *old_window,
+ GossipChatWindow *new_window,
+ GossipChat *chat)
+{
+ GtkWidget *widget;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_WINDOW (old_window));
+ g_return_if_fail (GOSSIP_IS_CHAT_WINDOW (new_window));
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ widget = gossip_chat_get_widget (chat);
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Chat moving with widget:%p (%d references)",
+ widget,
+ G_OBJECT (widget)->ref_count);
+
+ /* We reference here to make sure we don't loose the widget
+ * and the GossipChat object during the move.
+ */
+ g_object_ref (chat);
+ g_object_ref (widget);
+
+ gossip_chat_window_remove_chat (old_window, chat);
+ gossip_chat_window_add_chat (new_window, chat);
+
+ g_object_unref (widget);
+ g_object_unref (chat);
+}
+
+void
+gossip_chat_window_switch_to_chat (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ gint page_num;
+
+ priv = GET_PRIV (window);
+
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
+ gossip_chat_get_widget (chat));
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
+ page_num);
+}
+
+gboolean
+gossip_chat_window_has_focus (GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gboolean has_focus;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_WINDOW (window), FALSE);
+
+ priv = GET_PRIV (window);
+
+ g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
+
+ return has_focus;
+}
+
+GossipChat *
+gossip_chat_window_find_chat (McAccount *account,
+ const gchar *id)
+{
+ GList *l;
+
+ for (l = chat_windows; l; l = l->next) {
+ GossipChatWindowPriv *priv;
+ GossipChatWindow *window;
+ GList *ll;
+
+ window = l->data;
+ priv = GET_PRIV (window);
+
+ for (ll = priv->chats; ll; ll = ll->next) {
+ GossipChat *chat;
+
+ chat = ll->data;
+
+ if (gossip_account_equal (account, chat->account) &&
+ strcmp (id, gossip_chat_get_id (chat)) == 0) {
+ return chat;
+ }
+ }
+ }
+
+ return NULL;
+}
+
diff --git a/trunk/libempathy-gtk/gossip-chat-window.h b/trunk/libempathy-gtk/gossip-chat-window.h
new file mode 100644
index 000000000..b58e5dac3
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-chat-window.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __GOSSIP_CHAT_WINDOW_H__
+#define __GOSSIP_CHAT_WINDOW_H__
+
+#include <glib-object.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CHAT_WINDOW (gossip_chat_window_get_type ())
+#define GOSSIP_CHAT_WINDOW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CHAT_WINDOW, GossipChatWindow))
+#define GOSSIP_CHAT_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_CHAT_WINDOW, GossipChatWindowClass))
+#define GOSSIP_IS_CHAT_WINDOW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CHAT_WINDOW))
+#define GOSSIP_IS_CHAT_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CHAT_WINDOW))
+#define GOSSIP_CHAT_WINDOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CHAT_WINDOW, GossipChatWindowClass))
+
+typedef struct _GossipChatWindow GossipChatWindow;
+typedef struct _GossipChatWindowClass GossipChatWindowClass;
+typedef struct _GossipChatWindowPriv GossipChatWindowPriv;
+
+#include "gossip-chat.h"
+
+struct _GossipChatWindow {
+ GObject parent;
+};
+
+struct _GossipChatWindowClass {
+ GObjectClass parent_class;
+};
+
+GType gossip_chat_window_get_type (void);
+GossipChatWindow *gossip_chat_window_get_default (void);
+
+GossipChatWindow *gossip_chat_window_new (void);
+
+GtkWidget * gossip_chat_window_get_dialog (GossipChatWindow *window);
+
+void gossip_chat_window_add_chat (GossipChatWindow *window,
+ GossipChat *chat);
+void gossip_chat_window_remove_chat (GossipChatWindow *window,
+ GossipChat *chat);
+void gossip_chat_window_move_chat (GossipChatWindow *old_window,
+ GossipChatWindow *new_window,
+ GossipChat *chat);
+void gossip_chat_window_switch_to_chat (GossipChatWindow *window,
+ GossipChat *chat);
+gboolean gossip_chat_window_has_focus (GossipChatWindow *window);
+GossipChat * gossip_chat_window_find_chat (McAccount *account,
+ const gchar *id);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CHAT_WINDOW_H__ */
diff --git a/trunk/libempathy-gtk/gossip-chat.c b/trunk/libempathy-gtk/gossip-chat.c
new file mode 100644
index 000000000..a29c31818
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-chat.c
@@ -0,0 +1,1539 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <libempathy/empathy-contact-manager.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-utils.h>
+#include <libempathy/gossip-conf.h>
+#include <libempathy/empathy-marshal.h>
+
+#include "gossip-chat.h"
+#include "gossip-chat-window.h"
+#include "gossip-geometry.h"
+#include "gossip-preferences.h"
+#include "gossip-spell.h"
+//#include "gossip-spell-dialog.h"
+#include "gossip-ui-utils.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHAT, GossipChatPriv))
+
+#define DEBUG_DOMAIN "Chat"
+
+#define CHAT_DIR_CREATE_MODE (S_IRUSR | S_IWUSR | S_IXUSR)
+#define CHAT_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
+
+#define IS_ENTER(v) (v == GDK_Return || v == GDK_ISO_Enter || v == GDK_KP_Enter)
+
+#define MAX_INPUT_HEIGHT 150
+
+#define COMPOSING_STOP_TIMEOUT 5
+
+struct _GossipChatPriv {
+ EmpathyContactManager *manager;
+ EmpathyTpChat *tp_chat;
+ GossipChatWindow *window;
+ GtkTooltips *tooltips;
+ guint composing_stop_timeout_id;
+ gboolean sensitive;
+ gchar *id;
+ GSList *sent_messages;
+ gint sent_messages_index;
+ GList *compositors;
+ guint scroll_idle_id;
+ /* Used to automatically shrink a window that has temporarily
+ * grown due to long input.
+ */
+ gint padding_height;
+ gint default_window_height;
+ gint last_input_height;
+ gboolean vscroll_visible;
+};
+
+typedef struct {
+ GossipChat *chat;
+ gchar *word;
+
+ GtkTextIter start;
+ GtkTextIter end;
+} GossipChatSpell;
+
+static void gossip_chat_class_init (GossipChatClass *klass);
+static void gossip_chat_init (GossipChat *chat);
+static void chat_finalize (GObject *object);
+static void chat_destroy_cb (EmpathyTpChat *tp_chat,
+ GossipChat *chat);
+static void chat_send (GossipChat *chat,
+ const gchar *msg);
+static void chat_input_text_view_send (GossipChat *chat);
+static void chat_message_received_cb (EmpathyTpChat *tp_chat,
+ GossipMessage *message,
+ GossipChat *chat);
+void chat_sent_message_add (GossipChat *chat,
+ const gchar *str);
+const gchar * chat_sent_message_get_next (GossipChat *chat);
+const gchar * chat_sent_message_get_last (GossipChat *chat);
+static gboolean chat_input_key_press_event_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ GossipChat *chat);
+static void chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
+ GossipChat *chat);
+static gboolean chat_text_view_focus_in_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ GossipChat *chat);
+static void chat_text_view_scroll_hide_cb (GtkWidget *widget,
+ GossipChat *chat);
+static void chat_text_view_size_allocate_cb (GtkWidget *widget,
+ GtkAllocation *allocation,
+ GossipChat *chat);
+static void chat_text_view_realize_cb (GtkWidget *widget,
+ GossipChat *chat);
+static void chat_text_populate_popup_cb (GtkTextView *view,
+ GtkMenu *menu,
+ GossipChat *chat);
+static void chat_text_check_word_spelling_cb (GtkMenuItem *menuitem,
+ GossipChatSpell *chat_spell);
+static GossipChatSpell *chat_spell_new (GossipChat *chat,
+ const gchar *word,
+ GtkTextIter start,
+ GtkTextIter end);
+static void chat_spell_free (GossipChatSpell *chat_spell);
+static void chat_composing_start (GossipChat *chat);
+static void chat_composing_stop (GossipChat *chat);
+static void chat_composing_remove_timeout (GossipChat *chat);
+static gboolean chat_composing_stop_timeout_cb (GossipChat *chat);
+static void chat_state_changed_cb (EmpathyTpChat *tp_chat,
+ GossipContact *contact,
+ TelepathyChannelChatState state,
+ GossipChat *chat);
+static gboolean chat_scroll_down_idle_func (GossipChat *chat);
+
+enum {
+ COMPOSING,
+ NEW_MESSAGE,
+ NAME_CHANGED,
+ STATUS_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GossipChat, gossip_chat, G_TYPE_OBJECT);
+
+static void
+gossip_chat_class_init (GossipChatClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = chat_finalize;
+
+ signals[COMPOSING] =
+ g_signal_new ("composing",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE,
+ 1, G_TYPE_BOOLEAN);
+
+ signals[NEW_MESSAGE] =
+ g_signal_new ("new-message",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ empathy_marshal_VOID__OBJECT_BOOLEAN,
+ G_TYPE_NONE,
+ 2, GOSSIP_TYPE_MESSAGE, G_TYPE_BOOLEAN);
+
+ signals[NAME_CHANGED] =
+ g_signal_new ("name-changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1, G_TYPE_POINTER);
+
+ signals[STATUS_CHANGED] =
+ g_signal_new ("status-changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_type_class_add_private (object_class, sizeof (GossipChatPriv));
+}
+
+static void
+gossip_chat_init (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkTextBuffer *buffer;
+
+ chat->view = gossip_chat_view_new ();
+ chat->input_text_view = gtk_text_view_new ();
+
+ chat->is_first_char = TRUE;
+
+ g_object_set (chat->input_text_view,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ "pixels-inside-wrap", 1,
+ "right-margin", 2,
+ "left-margin", 2,
+ "wrap-mode", GTK_WRAP_WORD_CHAR,
+ NULL);
+
+ priv = GET_PRIV (chat);
+
+ priv->manager = empathy_contact_manager_new ();
+ priv->tooltips = g_object_ref_sink (gtk_tooltips_new ());
+ priv->default_window_height = -1;
+ priv->vscroll_visible = FALSE;
+ priv->sensitive = TRUE;
+ priv->sent_messages = NULL;
+ priv->sent_messages_index = -1;
+
+ g_signal_connect (chat->input_text_view,
+ "key_press_event",
+ G_CALLBACK (chat_input_key_press_event_cb),
+ chat);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+ g_signal_connect (buffer,
+ "changed",
+ G_CALLBACK (chat_input_text_buffer_changed_cb),
+ chat);
+ g_signal_connect (chat->view,
+ "focus_in_event",
+ G_CALLBACK (chat_text_view_focus_in_event_cb),
+ chat);
+
+ g_signal_connect (chat->input_text_view,
+ "size_allocate",
+ G_CALLBACK (chat_text_view_size_allocate_cb),
+ chat);
+
+ g_signal_connect (chat->input_text_view,
+ "realize",
+ G_CALLBACK (chat_text_view_realize_cb),
+ chat);
+
+ g_signal_connect (GTK_TEXT_VIEW (chat->input_text_view),
+ "populate_popup",
+ G_CALLBACK (chat_text_populate_popup_cb),
+ chat);
+
+ /* create misspelt words identification tag */
+ gtk_text_buffer_create_tag (buffer,
+ "misspelled",
+ "underline", PANGO_UNDERLINE_ERROR,
+ NULL);
+
+
+
+ /* Turn off scrolling temporarily */
+ gossip_chat_view_scroll (chat->view, FALSE);
+#if 0
+FIXME:
+ /* Add messages from last conversation */
+ log_manager = gossip_session_get_log_manager (gossip_app_get_session ());
+ messages = gossip_log_get_last_for_contact (log_manager, priv->contact);
+ num_messages = g_list_length (messages);
+
+ for (l = messages, i = 0; l; l = l->next, i++) {
+ message = l->data;
+
+ if (num_messages - i > 10) {
+ continue;
+ }
+
+ sender = gossip_message_get_sender (message);
+ if (gossip_contact_equal (priv->own_contact, sender)) {
+ gossip_chat_view_append_message_from_self (view,
+ message,
+ priv->own_contact,
+ priv->own_avatar);
+ } else {
+ gossip_chat_view_append_message_from_other (view,
+ message,
+ sender,
+ priv->other_avatar);
+ }
+ }
+
+ g_list_foreach (messages, (GFunc) g_object_unref, NULL);
+ g_list_free (messages);
+#endif
+ /* Turn back on scrolling */
+ gossip_chat_view_scroll (chat->view, TRUE);
+
+ /* Scroll to the most recent messages, we reference the chat
+ * for the duration of the scroll func.
+ */
+ priv->scroll_idle_id = g_idle_add ((GSourceFunc) chat_scroll_down_idle_func,
+ g_object_ref (chat));
+
+}
+
+static void
+chat_finalize (GObject *object)
+{
+ GossipChat *chat;
+ GossipChatPriv *priv;
+
+ chat = GOSSIP_CHAT (object);
+ priv = GET_PRIV (chat);
+
+ gossip_debug (DEBUG_DOMAIN, "Finalized: %p", object);
+
+ g_slist_foreach (priv->sent_messages, (GFunc) g_free, NULL);
+ g_slist_free (priv->sent_messages);
+
+ g_list_foreach (priv->compositors, (GFunc) g_object_unref, NULL);
+ g_list_free (priv->compositors);
+
+ chat_composing_remove_timeout (chat);
+ g_object_unref (chat->account);
+ g_object_unref (priv->manager);
+ g_object_unref (priv->tooltips);
+
+ if (priv->tp_chat) {
+ g_object_unref (priv->tp_chat);
+ }
+
+ if (priv->scroll_idle_id) {
+ g_source_remove (priv->scroll_idle_id);
+ }
+
+ g_free (priv->id);
+
+ G_OBJECT_CLASS (gossip_chat_parent_class)->finalize (object);
+}
+
+static void
+chat_destroy_cb (EmpathyTpChat *tp_chat,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkWidget *widget;
+
+ priv = GET_PRIV (chat);
+
+ if (priv->tp_chat) {
+ g_object_unref (priv->tp_chat);
+ priv->tp_chat = NULL;
+ }
+
+ gossip_chat_view_append_event (chat->view, _("Disconnected"));
+
+ widget = gossip_chat_get_widget (chat);
+ gtk_widget_set_sensitive (widget, FALSE);
+ priv->sensitive = FALSE;
+}
+
+static void
+chat_send (GossipChat *chat,
+ const gchar *msg)
+{
+ GossipChatPriv *priv;
+ //GossipLogManager *log_manager;
+ GossipMessage *message;
+ GossipContact *own_contact;
+
+ priv = GET_PRIV (chat);
+
+ if (G_STR_EMPTY (msg)) {
+ return;
+ }
+
+ chat_sent_message_add (chat, msg);
+
+ if (g_str_has_prefix (msg, "/clear")) {
+ gossip_chat_view_clear (chat->view);
+ return;
+ }
+
+ /* FIXME: add here something to let group/privrate chat handle
+ * some special messages */
+
+ /* FIXME: gossip_app_set_not_away ();*/
+
+ own_contact = empathy_contact_manager_get_user (priv->manager, chat->account);
+ message = gossip_message_new (msg);
+ gossip_message_set_sender (message, own_contact);
+
+ //FIXME: log_manager = gossip_session_get_log_manager (gossip_app_get_session ());
+ //gossip_log_message_for_contact (log_manager, message, FALSE);
+
+ empathy_tp_chat_send (priv->tp_chat, message);
+
+ g_object_unref (message);
+}
+
+static void
+chat_input_text_view_send (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextIter start, end;
+ gchar *msg;
+
+ priv = GET_PRIV (chat);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+ msg = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+ /* clear the input field */
+ gtk_text_buffer_set_text (buffer, "", -1);
+
+ chat_send (chat, msg);
+
+ g_free (msg);
+
+ chat->is_first_char = TRUE;
+}
+
+static void
+chat_message_received_cb (EmpathyTpChat *tp_chat,
+ GossipMessage *message,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ //GossipLogManager *log_manager;
+ GossipContact *sender;
+
+ priv = GET_PRIV (chat);
+
+ sender = gossip_message_get_sender (message);
+ gossip_debug (DEBUG_DOMAIN, "Appending message ('%s')",
+ gossip_contact_get_name (sender));
+
+/*FIXME:
+ log_manager = gossip_session_get_log_manager (gossip_app_get_session ());
+ gossip_log_message_for_contact (log_manager, message, TRUE);
+*/
+ gossip_chat_view_append_message (chat->view, message);
+
+ if (gossip_chat_should_play_sound (chat)) {
+ // FIXME: gossip_sound_play (GOSSIP_SOUND_CHAT);
+ }
+
+ g_signal_emit (chat, signals[NEW_MESSAGE], 0, message, FALSE);
+}
+
+void
+chat_sent_message_add (GossipChat *chat,
+ const gchar *str)
+{
+ GossipChatPriv *priv;
+ GSList *list;
+ GSList *item;
+
+ priv = GET_PRIV (chat);
+
+ /* Save the sent message in our repeat buffer */
+ list = priv->sent_messages;
+
+ /* Remove any other occurances of this msg */
+ while ((item = g_slist_find_custom (list, str, (GCompareFunc) strcmp)) != NULL) {
+ list = g_slist_remove_link (list, item);
+ g_free (item->data);
+ g_slist_free1 (item);
+ }
+
+ /* Trim the list to the last 10 items */
+ while (g_slist_length (list) > 10) {
+ item = g_slist_last (list);
+ if (item) {
+ list = g_slist_remove_link (list, item);
+ g_free (item->data);
+ g_slist_free1 (item);
+ }
+ }
+
+ /* Add new message */
+ list = g_slist_prepend (list, g_strdup (str));
+
+ /* Set list and reset the index */
+ priv->sent_messages = list;
+ priv->sent_messages_index = -1;
+}
+
+const gchar *
+chat_sent_message_get_next (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ gint max;
+
+ priv = GET_PRIV (chat);
+
+ if (!priv->sent_messages) {
+ gossip_debug (DEBUG_DOMAIN,
+ "No sent messages, next message is NULL");
+ return NULL;
+ }
+
+ max = g_slist_length (priv->sent_messages) - 1;
+
+ if (priv->sent_messages_index < max) {
+ priv->sent_messages_index++;
+ }
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Returning next message index:%d",
+ priv->sent_messages_index);
+
+ return g_slist_nth_data (priv->sent_messages, priv->sent_messages_index);
+}
+
+const gchar *
+chat_sent_message_get_last (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ priv = GET_PRIV (chat);
+
+ if (!priv->sent_messages) {
+ gossip_debug (DEBUG_DOMAIN,
+ "No sent messages, last message is NULL");
+ return NULL;
+ }
+
+ if (priv->sent_messages_index >= 0) {
+ priv->sent_messages_index--;
+ }
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Returning last message index:%d",
+ priv->sent_messages_index);
+
+ return g_slist_nth_data (priv->sent_messages, priv->sent_messages_index);
+}
+
+static gboolean
+chat_input_key_press_event_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkAdjustment *adj;
+ gdouble val;
+ GtkWidget *text_view_sw;
+
+ priv = GET_PRIV (chat);
+
+ if (event->keyval == GDK_Tab && !(event->state & GDK_CONTROL_MASK)) {
+ return TRUE;
+ }
+
+ /* Catch ctrl+up/down so we can traverse messages we sent */
+ if ((event->state & GDK_CONTROL_MASK) &&
+ (event->keyval == GDK_Up ||
+ event->keyval == GDK_Down)) {
+ GtkTextBuffer *buffer;
+ const gchar *str;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+
+ if (event->keyval == GDK_Up) {
+ str = chat_sent_message_get_next (chat);
+ } else {
+ str = chat_sent_message_get_last (chat);
+ }
+
+ g_signal_handlers_block_by_func (buffer,
+ chat_input_text_buffer_changed_cb,
+ chat);
+ gtk_text_buffer_set_text (buffer, str ? str : "", -1);
+ g_signal_handlers_unblock_by_func (buffer,
+ chat_input_text_buffer_changed_cb,
+ chat);
+
+ return TRUE;
+ }
+
+ /* Catch enter but not ctrl/shift-enter */
+ if (IS_ENTER (event->keyval) && !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
+ GtkTextView *view;
+
+ /* This is to make sure that kinput2 gets the enter. And if
+ * it's handled there we shouldn't send on it. This is because
+ * kinput2 uses Enter to commit letters. See:
+ * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
+ */
+
+ view = GTK_TEXT_VIEW (chat->input_text_view);
+ if (gtk_im_context_filter_keypress (view->im_context, event)) {
+ GTK_TEXT_VIEW (chat->input_text_view)->need_im_reset = TRUE;
+ return TRUE;
+ }
+
+ chat_input_text_view_send (chat);
+ return TRUE;
+ }
+
+ text_view_sw = gtk_widget_get_parent (GTK_WIDGET (chat->view));
+
+ if (IS_ENTER (event->keyval) && (event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
+ /* Newline for shift-enter. */
+ return FALSE;
+ }
+ else if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
+ event->keyval == GDK_Page_Up) {
+ adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
+ gtk_adjustment_set_value (adj, adj->value - adj->page_size);
+
+ return TRUE;
+ }
+ else if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
+ event->keyval == GDK_Page_Down) {
+ adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
+ val = MIN (adj->value + adj->page_size, adj->upper - adj->page_size);
+ gtk_adjustment_set_value (adj, val);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+chat_text_view_focus_in_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ GossipChat *chat)
+{
+ gtk_widget_grab_focus (chat->input_text_view);
+
+ return TRUE;
+}
+
+static void
+chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkTextIter start, end;
+ gchar *str;
+ gboolean spell_checker = FALSE;
+
+ priv = GET_PRIV (chat);
+
+ if (gtk_text_buffer_get_char_count (buffer) == 0) {
+ chat_composing_stop (chat);
+ } else {
+ chat_composing_start (chat);
+ }
+
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_ENABLED,
+ &spell_checker);
+
+ if (chat->is_first_char) {
+ GtkRequisition req;
+ gint window_height;
+ GtkWidget *dialog;
+ GtkAllocation *allocation;
+
+ /* Save the window's size */
+ dialog = gossip_chat_window_get_dialog (priv->window);
+ gtk_window_get_size (GTK_WINDOW (dialog),
+ NULL, &window_height);
+
+ gtk_widget_size_request (chat->input_text_view, &req);
+
+ allocation = &GTK_WIDGET (chat->view)->allocation;
+
+ priv->default_window_height = window_height;
+ priv->last_input_height = req.height;
+ priv->padding_height = window_height - req.height - allocation->height;
+
+ chat->is_first_char = FALSE;
+ }
+
+ gtk_text_buffer_get_start_iter (buffer, &start);
+
+ if (!spell_checker) {
+ gtk_text_buffer_get_end_iter (buffer, &end);
+ gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
+ return;
+ }
+
+ if (!gossip_spell_supported ()) {
+ return;
+ }
+
+ /* NOTE: this is really inefficient, we shouldn't have to
+ reiterate the whole buffer each time and check each work
+ every time. */
+ while (TRUE) {
+ gboolean correct = FALSE;
+
+ /* if at start */
+ if (gtk_text_iter_is_start (&start)) {
+ end = start;
+
+ if (!gtk_text_iter_forward_word_end (&end)) {
+ /* no whole word yet */
+ break;
+ }
+ } else {
+ if (!gtk_text_iter_forward_word_end (&end)) {
+ /* must be the end of the buffer */
+ break;
+ }
+
+ start = end;
+ gtk_text_iter_backward_word_start (&start);
+ }
+
+ str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+ /* spell check string */
+ if (!gossip_chat_get_is_command (str)) {
+ correct = gossip_spell_check (str);
+ } else {
+ correct = TRUE;
+ }
+
+ if (!correct) {
+ gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
+ } else {
+ gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
+ }
+
+ g_free (str);
+
+ /* set start iter to the end iters position */
+ start = end;
+ }
+}
+
+typedef struct {
+ GtkWidget *window;
+ gint width;
+ gint height;
+} ChangeSizeData;
+
+static gboolean
+chat_change_size_in_idle_cb (ChangeSizeData *data)
+{
+ gtk_window_resize (GTK_WINDOW (data->window),
+ data->width, data->height);
+
+ return FALSE;
+}
+
+static void
+chat_text_view_scroll_hide_cb (GtkWidget *widget,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkWidget *sw;
+
+ priv = GET_PRIV (chat);
+
+ priv->vscroll_visible = FALSE;
+ g_signal_handlers_disconnect_by_func (widget, chat_text_view_scroll_hide_cb, chat);
+
+ sw = gtk_widget_get_parent (chat->input_text_view);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_NEVER);
+ g_object_set (sw, "height-request", -1, NULL);
+}
+
+static void
+chat_text_view_size_allocate_cb (GtkWidget *widget,
+ GtkAllocation *allocation,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ gint width;
+ GtkWidget *dialog;
+ ChangeSizeData *data;
+ gint window_height;
+ gint new_height;
+ GtkAllocation *view_allocation;
+ gint current_height;
+ gint diff;
+ GtkWidget *sw;
+
+ priv = GET_PRIV (chat);
+
+ if (priv->default_window_height <= 0) {
+ return;
+ }
+
+ sw = gtk_widget_get_parent (widget);
+ if (sw->allocation.height >= MAX_INPUT_HEIGHT && !priv->vscroll_visible) {
+ GtkWidget *vscroll;
+
+ priv->vscroll_visible = TRUE;
+ gtk_widget_set_size_request (sw, sw->allocation.width, MAX_INPUT_HEIGHT);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ vscroll = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (sw));
+ g_signal_connect (vscroll, "hide",
+ G_CALLBACK (chat_text_view_scroll_hide_cb),
+ chat);
+ }
+
+ if (priv->last_input_height <= allocation->height) {
+ priv->last_input_height = allocation->height;
+ return;
+ }
+
+ diff = priv->last_input_height - allocation->height;
+ priv->last_input_height = allocation->height;
+
+ view_allocation = &GTK_WIDGET (chat->view)->allocation;
+
+ dialog = gossip_chat_window_get_dialog (priv->window);
+ gtk_window_get_size (GTK_WINDOW (dialog), NULL, &current_height);
+
+ new_height = view_allocation->height + priv->padding_height + allocation->height - diff;
+
+ if (new_height <= priv->default_window_height) {
+ window_height = priv->default_window_height;
+ } else {
+ window_height = new_height;
+ }
+
+ if (current_height <= window_height) {
+ return;
+ }
+
+ /* Restore the window's size */
+ gtk_window_get_size (GTK_WINDOW (dialog), &width, NULL);
+
+ data = g_new0 (ChangeSizeData, 1);
+ data->window = dialog;
+ data->width = width;
+ data->height = window_height;
+
+ g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+ (GSourceFunc) chat_change_size_in_idle_cb,
+ data, g_free);
+}
+
+static void
+chat_text_view_realize_cb (GtkWidget *widget,
+ GossipChat *chat)
+{
+ gossip_debug (DEBUG_DOMAIN, "Setting focus to the input text view");
+ gtk_widget_grab_focus (widget);
+}
+
+static void
+chat_insert_smiley_activate_cb (GtkWidget *menuitem,
+ GossipChat *chat)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ const gchar *smiley;
+
+ smiley = g_object_get_data (G_OBJECT (menuitem), "smiley_text");
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+ gtk_text_buffer_insert (buffer, &iter, smiley, -1);
+
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+ gtk_text_buffer_insert (buffer, &iter, " ", -1);
+}
+
+static void
+chat_text_populate_popup_cb (GtkTextView *view,
+ GtkMenu *menu,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+ gint x, y;
+ GtkTextIter iter, start, end;
+ GtkWidget *item;
+ gchar *str = NULL;
+ GossipChatSpell *chat_spell;
+ GtkWidget *smiley_menu;
+
+ priv = GET_PRIV (chat);
+
+ /* Add the emoticon menu. */
+ item = gtk_separator_menu_item_new ();
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("Insert Smiley"));
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ smiley_menu = gossip_chat_view_get_smiley_menu (
+ G_CALLBACK (chat_insert_smiley_activate_cb),
+ chat,
+ priv->tooltips);
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), smiley_menu);
+
+ /* Add the spell check menu item. */
+ buffer = gtk_text_view_get_buffer (view);
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ tag = gtk_text_tag_table_lookup (table, "misspelled");
+
+ gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
+
+ gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
+ GTK_TEXT_WINDOW_WIDGET,
+ x, y,
+ &x, &y);
+
+ gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
+
+ start = end = iter;
+
+ if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
+ gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
+
+ str = gtk_text_buffer_get_text (buffer,
+ &start, &end, FALSE);
+ }
+
+ if (G_STR_EMPTY (str)) {
+ return;
+ }
+
+ chat_spell = chat_spell_new (chat, str, start, end);
+
+ g_object_set_data_full (G_OBJECT (menu),
+ "chat_spell", chat_spell,
+ (GDestroyNotify) chat_spell_free);
+
+ item = gtk_separator_menu_item_new ();
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Check Word Spelling..."));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (chat_text_check_word_spelling_cb),
+ chat_spell);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+}
+
+static void
+chat_text_check_word_spelling_cb (GtkMenuItem *menuitem,
+ GossipChatSpell *chat_spell)
+{
+/*FIXME: gossip_spell_dialog_show (chat_spell->chat,
+ chat_spell->start,
+ chat_spell->end,
+ chat_spell->word);*/
+}
+
+static GossipChatSpell *
+chat_spell_new (GossipChat *chat,
+ const gchar *word,
+ GtkTextIter start,
+ GtkTextIter end)
+{
+ GossipChatSpell *chat_spell;
+
+ chat_spell = g_new0 (GossipChatSpell, 1);
+
+ chat_spell->chat = g_object_ref (chat);
+ chat_spell->word = g_strdup (word);
+ chat_spell->start = start;
+ chat_spell->end = end;
+
+ return chat_spell;
+}
+
+static void
+chat_spell_free (GossipChatSpell *chat_spell)
+{
+ g_object_unref (chat_spell->chat);
+ g_free (chat_spell->word);
+ g_free (chat_spell);
+}
+
+static void
+chat_composing_start (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ if (priv->composing_stop_timeout_id) {
+ /* Just restart the timeout */
+ chat_composing_remove_timeout (chat);
+ } else {
+ empathy_tp_chat_set_state (priv->tp_chat,
+ TP_CHANNEL_CHAT_STATE_COMPOSING);
+ }
+
+ priv->composing_stop_timeout_id = g_timeout_add (
+ 1000 * COMPOSING_STOP_TIMEOUT,
+ (GSourceFunc) chat_composing_stop_timeout_cb,
+ chat);
+}
+
+static void
+chat_composing_stop (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ chat_composing_remove_timeout (chat);
+ empathy_tp_chat_set_state (priv->tp_chat,
+ TP_CHANNEL_CHAT_STATE_ACTIVE);
+}
+
+static void
+chat_composing_remove_timeout (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ if (priv->composing_stop_timeout_id) {
+ g_source_remove (priv->composing_stop_timeout_id);
+ priv->composing_stop_timeout_id = 0;
+ }
+}
+
+static gboolean
+chat_composing_stop_timeout_cb (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ priv->composing_stop_timeout_id = 0;
+ empathy_tp_chat_set_state (priv->tp_chat,
+ TP_CHANNEL_CHAT_STATE_PAUSED);
+
+ return FALSE;
+}
+
+static void
+chat_state_changed_cb (EmpathyTpChat *tp_chat,
+ GossipContact *contact,
+ TelepathyChannelChatState state,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GossipContact *own_contact;
+ GList *l;
+ gboolean was_composing;
+
+ priv = GET_PRIV (chat);
+
+ own_contact = gossip_contact_get_user (contact);
+ if (gossip_contact_equal (own_contact, contact)) {
+ /* We don't care about our own chat state */
+ return;
+ }
+
+ was_composing = (priv->compositors != NULL);
+
+ /* Find the contact in the list. After that l is the list elem or NULL */
+ for (l = priv->compositors; l; l = l->next) {
+ if (gossip_contact_equal (contact, l->data)) {
+ break;
+ }
+ }
+
+ switch (state) {
+ case TP_CHANNEL_CHAT_STATE_GONE:
+ case TP_CHANNEL_CHAT_STATE_INACTIVE:
+ case TP_CHANNEL_CHAT_STATE_ACTIVE:
+ /* Contact is not composing */
+ if (l) {
+ priv->compositors = g_list_remove_link (priv->compositors, l);
+ g_object_unref (l->data);
+ g_list_free1 (l);
+ }
+ break;
+ case TP_CHANNEL_CHAT_STATE_PAUSED:
+ case TP_CHANNEL_CHAT_STATE_COMPOSING:
+ /* Contact is composing */
+ if (!l) {
+ priv->compositors = g_list_prepend (priv->compositors,
+ g_object_ref (contact));
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Was composing: %s now composing: %s",
+ was_composing ? "yes" : "no",
+ priv->compositors ? "yes" : "no");
+
+ if ((was_composing && !priv->compositors) ||
+ (!was_composing && priv->compositors)) {
+ /* Composing state changed */
+ g_signal_emit (chat, signals[COMPOSING], 0,
+ (gboolean) priv->compositors);
+ }
+}
+
+/* Scroll down after the back-log has been received. */
+static gboolean
+chat_scroll_down_idle_func (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ gossip_chat_scroll_down (chat);
+ g_object_unref (chat);
+
+ priv->scroll_idle_id = 0;
+
+ return FALSE;
+}
+
+gboolean
+gossip_chat_get_is_command (const gchar *str)
+{
+ g_return_val_if_fail (str != NULL, FALSE);
+
+ if (str[0] != '/') {
+ return FALSE;
+ }
+
+ if (g_str_has_prefix (str, "/me")) {
+ return TRUE;
+ }
+ else if (g_str_has_prefix (str, "/nick")) {
+ return TRUE;
+ }
+ else if (g_str_has_prefix (str, "/topic")) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gossip_chat_correct_word (GossipChat *chat,
+ GtkTextIter start,
+ GtkTextIter end,
+ const gchar *new_word)
+{
+ GtkTextBuffer *buffer;
+
+ g_return_if_fail (chat != NULL);
+ g_return_if_fail (new_word != NULL);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+
+ gtk_text_buffer_delete (buffer, &start, &end);
+ gtk_text_buffer_insert (buffer, &start,
+ new_word,
+ -1);
+}
+
+const gchar *
+gossip_chat_get_name (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_name) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_name (chat);
+ }
+
+ return NULL;
+}
+
+gchar *
+gossip_chat_get_tooltip (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_tooltip) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_tooltip (chat);
+ }
+
+ return NULL;
+}
+
+const gchar *
+gossip_chat_get_status_icon_name (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_status_icon_name) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_status_icon_name (chat);
+ }
+
+ return NULL;
+}
+
+GtkWidget *
+gossip_chat_get_widget (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_widget) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_widget (chat);
+ }
+
+ return NULL;
+}
+
+gboolean
+gossip_chat_is_group_chat (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->is_group_chat) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->is_group_chat (chat);
+ }
+
+ return FALSE;
+}
+
+gboolean
+gossip_chat_is_connected (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
+
+ priv = GET_PRIV (chat);
+
+ return (priv->tp_chat != NULL);
+}
+
+void
+gossip_chat_save_geometry (GossipChat *chat,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ gossip_geometry_save (gossip_chat_get_id (chat), x, y, w, h);
+}
+
+void
+gossip_chat_load_geometry (GossipChat *chat,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h)
+{
+ gossip_geometry_load (gossip_chat_get_id (chat), x, y, w, h);
+}
+
+void
+gossip_chat_set_tp_chat (GossipChat *chat,
+ EmpathyTpChat *tp_chat)
+{
+ GossipChatPriv *priv;
+ GtkWidget *widget;
+
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+ g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat));
+
+ priv = GET_PRIV (chat);
+
+ if (tp_chat == priv->tp_chat) {
+ return;
+ }
+
+ if (priv->tp_chat) {
+ g_signal_handlers_disconnect_by_func (priv->tp_chat,
+ chat_message_received_cb,
+ chat);
+ g_signal_handlers_disconnect_by_func (priv->tp_chat,
+ chat_destroy_cb,
+ chat);
+ g_object_unref (priv->tp_chat);
+ }
+
+ g_free (priv->id);
+ priv->tp_chat = g_object_ref (tp_chat);
+ priv->id = g_strdup (empathy_tp_chat_get_id (tp_chat));
+
+ g_signal_connect (tp_chat, "message-received",
+ G_CALLBACK (chat_message_received_cb),
+ chat);
+ g_signal_connect (tp_chat, "chat-state-changed",
+ G_CALLBACK (chat_state_changed_cb),
+ chat);
+ g_signal_connect (tp_chat, "destroy",
+ G_CALLBACK (chat_destroy_cb),
+ chat);
+
+ empathy_tp_chat_request_pending (tp_chat);
+
+ if (!priv->sensitive) {
+ widget = gossip_chat_get_widget (chat);
+ gtk_widget_set_sensitive (widget, TRUE);
+ gossip_chat_view_append_event (chat->view, _("Connected"));
+ priv->sensitive = TRUE;
+ }
+}
+
+const gchar *
+gossip_chat_get_id (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ return priv->id;
+}
+
+void
+gossip_chat_clear (GossipChat *chat)
+{
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ gossip_chat_view_clear (chat->view);
+}
+
+void
+gossip_chat_set_window (GossipChat *chat,
+ GossipChatWindow *window)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+ priv->window = window;
+}
+
+GossipChatWindow *
+gossip_chat_get_window (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ return priv->window;
+}
+
+void
+gossip_chat_scroll_down (GossipChat *chat)
+{
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ gossip_chat_view_scroll_down (chat->view);
+}
+
+void
+gossip_chat_cut (GossipChat *chat)
+{
+ GtkTextBuffer *buffer;
+
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+ if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+
+ gtk_text_buffer_cut_clipboard (buffer, clipboard, TRUE);
+ }
+}
+
+void
+gossip_chat_copy (GossipChat *chat)
+{
+ GtkTextBuffer *buffer;
+
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ if (gossip_chat_view_get_selection_bounds (chat->view, NULL, NULL)) {
+ gossip_chat_view_copy_clipboard (chat->view);
+ return;
+ }
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+ if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+
+ gtk_text_buffer_copy_clipboard (buffer, clipboard);
+ }
+}
+
+void
+gossip_chat_paste (GossipChat *chat)
+{
+ GtkTextBuffer *buffer;
+ GtkClipboard *clipboard;
+
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+
+ gtk_text_buffer_paste_clipboard (buffer, clipboard, NULL, TRUE);
+}
+
+void
+gossip_chat_present (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ priv = GET_PRIV (chat);
+
+ if (priv->window == NULL) {
+ GossipChatWindow *window;
+
+ window = gossip_chat_window_get_default ();
+ if (!window) {
+ window = gossip_chat_window_new ();
+ }
+
+ gossip_chat_window_add_chat (window, chat);
+ }
+
+ gossip_chat_window_switch_to_chat (priv->window, chat);
+ gossip_window_present (
+ GTK_WINDOW (gossip_chat_window_get_dialog (priv->window)),
+ TRUE);
+
+ gtk_widget_grab_focus (chat->input_text_view);
+}
+
+gboolean
+gossip_chat_should_play_sound (GossipChat *chat)
+{
+ GossipChatWindow *window;
+ gboolean play = TRUE;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
+
+ window = gossip_chat_get_window (chat);
+ if (!window) {
+ return TRUE;
+ }
+
+ play = !gossip_chat_window_has_focus (window);
+
+ return play;
+}
+
+gboolean
+gossip_chat_should_highlight_nick (GossipMessage *message)
+{
+ GossipContact *my_contact;
+ const gchar *msg, *to;
+ gchar *cf_msg, *cf_to;
+ gchar *ch;
+ gboolean ret_val;
+
+ g_return_val_if_fail (GOSSIP_IS_MESSAGE (message), FALSE);
+
+ gossip_debug (DEBUG_DOMAIN, "Highlighting nickname");
+
+ ret_val = FALSE;
+
+ msg = gossip_message_get_body (message);
+ if (!msg) {
+ return FALSE;
+ }
+
+ my_contact = gossip_contact_get_user (gossip_message_get_sender (message));
+ to = gossip_contact_get_name (my_contact);
+ if (!to) {
+ return FALSE;
+ }
+
+ cf_msg = g_utf8_casefold (msg, -1);
+ cf_to = g_utf8_casefold (to, -1);
+
+ ch = strstr (cf_msg, cf_to);
+ if (ch == NULL) {
+ goto finished;
+ }
+
+ if (ch != cf_msg) {
+ /* Not first in the message */
+ if ((*(ch - 1) != ' ') &&
+ (*(ch - 1) != ',') &&
+ (*(ch - 1) != '.')) {
+ goto finished;
+ }
+ }
+
+ ch = ch + strlen (cf_to);
+ if (ch >= cf_msg + strlen (cf_msg)) {
+ ret_val = TRUE;
+ goto finished;
+ }
+
+ if ((*ch == ' ') ||
+ (*ch == ',') ||
+ (*ch == '.') ||
+ (*ch == ':')) {
+ ret_val = TRUE;
+ goto finished;
+ }
+
+finished:
+ g_free (cf_msg);
+ g_free (cf_to);
+
+ return ret_val;
+}
+
diff --git a/trunk/libempathy-gtk/gossip-chat.glade b/trunk/libempathy-gtk/gossip-chat.glade
new file mode 100644
index 000000000..d07bf77e6
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-chat.glade
@@ -0,0 +1,699 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="chat_page_window">
+ <property name="title" translatable="yes">Chat</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">350</property>
+ <property name="default_height">250</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+
+ <child>
+ <widget class="GtkVBox" id="chat_widget">
+ <property name="border_width">4</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">3</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="chat_view_sw">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="input_text_view_sw">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+<widget class="GtkWindow" id="chat_window">
+ <property name="title" translatable="yes">Chat</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">350</property>
+ <property name="default_height">250</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+
+ <child>
+ <widget class="GtkVBox" id="chat_vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkMenuBar" id="chats_menubar">
+ <property name="visible">True</property>
+ <property name="pack_direction">GTK_PACK_DIRECTION_LTR</property>
+ <property name="child_pack_direction">GTK_PACK_DIRECTION_LTR</property>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_conv">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Conversation</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="menu_conv_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_conv_clear">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">C_lear</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image235">
+ <property name="visible">True</property>
+ <property name="stock">gtk-clear</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_conv_insert_smiley">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Insert _Smiley</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator11">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_conv_log">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_View Previous Conversations</property>
+ <property name="use_underline">True</property>
+ <accelerator key="F3" modifiers="0" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image236">
+ <property name="visible">True</property>
+ <property name="stock">gtk-justify-left</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="menu_conv_separator">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_conv_add_contact">
+ <property name="label" translatable="yes">_Add Contact...</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image237">
+ <property name="visible">True</property>
+ <property name="stock">gtk-add</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_conv_info">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Contact Infor_mation</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image238">
+ <property name="visible">True</property>
+ <property name="stock">gtk-info</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator7">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_conv_close">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Close</property>
+ <property name="use_underline">True</property>
+ <accelerator key="W" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image239">
+ <property name="visible">True</property>
+ <property name="stock">gtk-close</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_room">
+ <property name="label" translatable="yes">_Room</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="menu_room_menu">
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_room_set_topic">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Change _Topic...</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator12">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_room_join_new">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Join _New...</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image240">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_room_invite">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">In_vite...</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator7">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_room_add">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Add To Favorites</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image241">
+ <property name="visible">True</property>
+ <property name="stock">gtk-add</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator10">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkCheckMenuItem" id="menu_room_show_contacts">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Show Contacts</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <accelerator key="F11" modifiers="0" signal="activate"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_edit">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Edit</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="menu_edit_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_edit_cut">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Cu_t</property>
+ <property name="use_underline">True</property>
+ <accelerator key="X" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image242">
+ <property name="visible">True</property>
+ <property name="stock">gtk-cut</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_edit_copy">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Copy</property>
+ <property name="use_underline">True</property>
+ <accelerator key="C" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image243">
+ <property name="visible">True</property>
+ <property name="stock">gtk-copy</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_edit_paste">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Paste</property>
+ <property name="use_underline">True</property>
+ <accelerator key="V" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image244">
+ <property name="visible">True</property>
+ <property name="stock">gtk-paste</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_tabs">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Tabs</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="menu_tabs_menu">
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_tabs_prev">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Previous Tab</property>
+ <property name="use_underline">True</property>
+ <accelerator key="Page_Up" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_tabs_next">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Next Tab</property>
+ <property name="use_underline">True</property>
+ <accelerator key="Page_Down" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator4">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_tabs_left">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Move Tab _Left</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_tabs_right">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Move Tab _Right</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_tabs_detach">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Detach Tab</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+<widget class="GtkDialog" id="chat_invite_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Invite</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="modal">True</property>
+ <property name="default_width">275</property>
+ <property name="default_height">225</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="button_cancel">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="button_invite">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">In_vite</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox7">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkLabel" id="label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Select who would you like to invite:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox8">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Invitation _message:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">You have been invited to join a chat conference.</property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">True</property>
+ <property name="width_chars">40</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/trunk/libempathy-gtk/gossip-chat.h b/trunk/libempathy-gtk/gossip-chat.h
new file mode 100644
index 000000000..5fedfdd01
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-chat.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __GOSSIP_CHAT_H__
+#define __GOSSIP_CHAT_H__
+
+#include <glib-object.h>
+
+#include <libempathy/gossip-contact.h>
+#include <libempathy/gossip-message.h>
+#include <libempathy/empathy-tp-chat.h>
+
+#include "gossip-chat-view.h"
+#include "gossip-spell.h"
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CHAT (gossip_chat_get_type ())
+#define GOSSIP_CHAT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CHAT, GossipChat))
+#define GOSSIP_CHAT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_CHAT, GossipChatClass))
+#define GOSSIP_IS_CHAT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CHAT))
+#define GOSSIP_IS_CHAT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CHAT))
+#define GOSSIP_CHAT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CHAT, GossipChatClass))
+
+typedef struct _GossipChat GossipChat;
+typedef struct _GossipChatClass GossipChatClass;
+typedef struct _GossipChatPriv GossipChatPriv;
+
+#include "gossip-chat-window.h"
+
+struct _GossipChat {
+ GObject parent;
+
+ /* Protected */
+ GossipChatView *view;
+ GtkWidget *input_text_view;
+ gboolean is_first_char;
+ McAccount *account;
+};
+
+struct _GossipChatClass {
+ GObjectClass parent;
+
+ /* VTable */
+ const gchar * (*get_name) (GossipChat *chat);
+ gchar * (*get_tooltip) (GossipChat *chat);
+ const gchar * (*get_status_icon_name)(GossipChat *chat);
+ GtkWidget * (*get_widget) (GossipChat *chat);
+ gboolean (*is_group_chat) (GossipChat *chat);
+};
+
+GType gossip_chat_get_type (void);
+
+GossipChatView * gossip_chat_get_view (GossipChat *chat);
+GossipChatWindow *gossip_chat_get_window (GossipChat *chat);
+void gossip_chat_set_window (GossipChat *chat,
+ GossipChatWindow *window);
+void gossip_chat_present (GossipChat *chat);
+void gossip_chat_clear (GossipChat *chat);
+void gossip_chat_scroll_down (GossipChat *chat);
+void gossip_chat_cut (GossipChat *chat);
+void gossip_chat_copy (GossipChat *chat);
+void gossip_chat_paste (GossipChat *chat);
+const gchar * gossip_chat_get_name (GossipChat *chat);
+gchar * gossip_chat_get_tooltip (GossipChat *chat);
+const gchar * gossip_chat_get_status_icon_name (GossipChat *chat);
+GtkWidget * gossip_chat_get_widget (GossipChat *chat);
+gboolean gossip_chat_is_group_chat (GossipChat *chat);
+gboolean gossip_chat_is_connected (GossipChat *chat);
+
+void gossip_chat_save_geometry (GossipChat *chat,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+void gossip_chat_load_geometry (GossipChat *chat,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h);
+void gossip_chat_set_tp_chat (GossipChat *chat,
+ EmpathyTpChat *tp_chat);
+const gchar * gossip_chat_get_id (GossipChat *chat);
+
+/* For spell checker dialog to correct the misspelled word. */
+gboolean gossip_chat_get_is_command (const gchar *str);
+void gossip_chat_correct_word (GossipChat *chat,
+ GtkTextIter start,
+ GtkTextIter end,
+ const gchar *new_word);
+gboolean gossip_chat_should_play_sound (GossipChat *chat);
+gboolean gossip_chat_should_highlight_nick (GossipMessage *message);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CHAT_H__ */
diff --git a/trunk/libempathy-gtk/gossip-chatrooms-window.c b/trunk/libempathy-gtk/gossip-chatrooms-window.c
new file mode 100644
index 000000000..5ff5d8812
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-chatrooms-window.c
@@ -0,0 +1,530 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Mikael Hallendal <micke@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <libempathy/gossip-chatroom-manager.h>
+#include <libempathy/gossip-utils.h>
+
+#include "gossip-account-chooser.h"
+#include "gossip-chatrooms-window.h"
+//#include "gossip-edit-chatroom-dialog.h"
+#include "gossip-new-chatroom-dialog.h"
+#include "gossip-ui-utils.h"
+
+typedef struct {
+ GossipChatroomManager *manager;
+
+ GtkWidget *window;
+ GtkWidget *hbox_account;
+ GtkWidget *label_account;
+ GtkWidget *account_chooser;
+ GtkWidget *treeview;
+ GtkWidget *button_remove;
+ GtkWidget *button_edit;
+ GtkWidget *button_close;
+
+ gint room_column;
+} GossipChatroomsWindow;
+
+static void chatrooms_window_destroy_cb (GtkWidget *widget,
+ GossipChatroomsWindow *window);
+static void chatrooms_window_model_setup (GossipChatroomsWindow *window);
+static void chatrooms_window_model_add_columns (GossipChatroomsWindow *window);
+static void chatrooms_window_model_refresh_data (GossipChatroomsWindow *window,
+ gboolean first_time);
+static void chatrooms_window_model_add (GossipChatroomsWindow *window,
+ GossipChatroom *chatroom,
+ gboolean set_active);
+static void chatrooms_window_model_cell_auto_connect_toggled (GtkCellRendererToggle *cell,
+ gchar *path_string,
+ GossipChatroomsWindow *window);
+static GossipChatroom * chatrooms_window_model_get_selected (GossipChatroomsWindow *window);
+static void chatrooms_window_model_action_selected (GossipChatroomsWindow *window);
+static void chatrooms_window_row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GossipChatroomsWindow *window);
+static void chatrooms_window_button_remove_clicked_cb (GtkWidget *widget,
+ GossipChatroomsWindow *window);
+static void chatrooms_window_button_edit_clicked_cb (GtkWidget *widget,
+ GossipChatroomsWindow *window);
+static void chatrooms_window_button_close_clicked_cb (GtkWidget *widget,
+ GossipChatroomsWindow *window);
+static void chatrooms_window_chatroom_added_cb (GossipChatroomManager *manager,
+ GossipChatroom *chatroom,
+ GossipChatroomsWindow *window);
+static void chatrooms_window_account_changed_cb (GtkWidget *combo_box,
+ GossipChatroomsWindow *window);
+
+enum {
+ COL_IMAGE,
+ COL_NAME,
+ COL_ROOM,
+ COL_AUTO_CONNECT,
+ COL_POINTER,
+ COL_COUNT
+};
+
+void
+gossip_chatrooms_window_show (GtkWindow *parent)
+{
+ static GossipChatroomsWindow *window = NULL;
+ GladeXML *glade;
+
+ if (window) {
+ gtk_window_present (GTK_WINDOW (window->window));
+ return;
+ }
+
+ window = g_new0 (GossipChatroomsWindow, 1);
+
+ glade = gossip_glade_get_file ("gossip-chatrooms-window.glade",
+ "chatrooms_window",
+ NULL,
+ "chatrooms_window", &window->window,
+ "hbox_account", &window->hbox_account,
+ "label_account", &window->label_account,
+ "treeview", &window->treeview,
+ "button_edit", &window->button_edit,
+ "button_remove", &window->button_remove,
+ "button_close", &window->button_close,
+ NULL);
+
+ gossip_glade_connect (glade,
+ window,
+ "chatrooms_window", "destroy", chatrooms_window_destroy_cb,
+ "button_remove", "clicked", chatrooms_window_button_remove_clicked_cb,
+ "button_edit", "clicked", chatrooms_window_button_edit_clicked_cb,
+ "button_close", "clicked", chatrooms_window_button_close_clicked_cb,
+ NULL);
+
+ g_object_unref (glade);
+
+ g_object_add_weak_pointer (G_OBJECT (window->window), (gpointer) &window);
+
+ /* Get the session and chat room manager */
+ window->manager = gossip_chatroom_manager_new ();
+
+ g_signal_connect (window->manager, "chatroom-added",
+ G_CALLBACK (chatrooms_window_chatroom_added_cb),
+ window);
+
+ /* Account chooser for chat rooms */
+ window->account_chooser = gossip_account_chooser_new ();
+ gossip_account_chooser_set_account (GOSSIP_ACCOUNT_CHOOSER (window->account_chooser), NULL);
+ g_object_set (window->account_chooser,
+ "can-select-all", TRUE,
+ "has-all-option", TRUE,
+ NULL);
+
+ gtk_box_pack_start (GTK_BOX (window->hbox_account),
+ window->account_chooser,
+ TRUE, TRUE, 0);
+
+ g_signal_connect (window->account_chooser, "changed",
+ G_CALLBACK (chatrooms_window_account_changed_cb),
+ window);
+
+ gtk_widget_show (window->account_chooser);
+
+ /* Set up chatrooms */
+ chatrooms_window_model_setup (window);
+
+ /* Set focus */
+ gtk_widget_grab_focus (window->treeview);
+
+ /* Last touches */
+ if (parent) {
+ gtk_window_set_transient_for (GTK_WINDOW (window->window),
+ GTK_WINDOW (parent));
+ }
+
+ gtk_widget_show (window->window);
+}
+
+static void
+chatrooms_window_destroy_cb (GtkWidget *widget,
+ GossipChatroomsWindow *window)
+{
+ g_object_unref (window->manager);
+ g_free (window);
+}
+
+static void
+chatrooms_window_model_setup (GossipChatroomsWindow *window)
+{
+ GtkTreeView *view;
+ GtkListStore *store;
+ GtkTreeSelection *selection;
+
+ /* View */
+ view = GTK_TREE_VIEW (window->treeview);
+
+ g_signal_connect (view, "row-activated",
+ G_CALLBACK (chatrooms_window_row_activated_cb),
+ window);
+
+ /* Store */
+ store = gtk_list_store_new (COL_COUNT,
+ G_TYPE_STRING, /* Image */
+ G_TYPE_STRING, /* Name */
+ G_TYPE_STRING, /* Room */
+ G_TYPE_BOOLEAN, /* Auto start */
+ GOSSIP_TYPE_CHATROOM); /* Chatroom */
+
+ gtk_tree_view_set_model (view, GTK_TREE_MODEL (store));
+
+ /* Selection */
+ selection = gtk_tree_view_get_selection (view);
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+ /* Columns */
+ chatrooms_window_model_add_columns (window);
+
+ /* Add data */
+ chatrooms_window_model_refresh_data (window, TRUE);
+
+ /* Clean up */
+ g_object_unref (store);
+}
+
+static void
+chatrooms_window_model_add_columns (GossipChatroomsWindow *window)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ gint count;
+
+ view = GTK_TREE_VIEW (window->treeview);
+ model = gtk_tree_view_get_model (view);
+
+ gtk_tree_view_set_headers_visible (view, TRUE);
+ gtk_tree_view_set_headers_clickable (view, TRUE);
+
+ /* Name & Status */
+ column = gtk_tree_view_column_new ();
+ count = gtk_tree_view_append_column (view, column);
+
+ gtk_tree_view_column_set_title (column, _("Name"));
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_column_set_sort_column_id (column, count - 1);
+
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+ gtk_tree_view_column_add_attribute (column, cell, "icon-name", COL_IMAGE);
+
+ cell = gtk_cell_renderer_text_new ();
+ g_object_set (cell,
+ "xpad", 4,
+ "ypad", 1,
+ NULL);
+ gtk_tree_view_column_pack_start (column, cell, TRUE);
+ gtk_tree_view_column_add_attribute (column, cell, "text", COL_NAME);
+
+ /* Room */
+ cell = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("Room"), cell,
+ "text", COL_ROOM,
+ NULL);
+ count = gtk_tree_view_append_column (view, column);
+ gtk_tree_view_column_set_sort_column_id (column, count - 1);
+ window->room_column = count - 1;
+
+ /* Chatroom auto connect */
+ cell = gtk_cell_renderer_toggle_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("Auto Connect"), cell,
+ "active", COL_AUTO_CONNECT,
+ NULL);
+ count = gtk_tree_view_append_column (view, column);
+ gtk_tree_view_column_set_sort_column_id (column, count - 1);
+
+ g_signal_connect (cell, "toggled",
+ G_CALLBACK (chatrooms_window_model_cell_auto_connect_toggled),
+ window);
+
+ /* Sort model */
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), 0,
+ GTK_SORT_ASCENDING);
+}
+
+static void
+chatrooms_window_model_refresh_data (GossipChatroomsWindow *window,
+ gboolean first_time)
+{
+ GtkTreeView *view;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GtkTreeViewColumn *column;
+ GossipAccountChooser *account_chooser;
+ McAccount *account;
+ GList *chatrooms, *l;
+
+ view = GTK_TREE_VIEW (window->treeview);
+ selection = gtk_tree_view_get_selection (view);
+ model = gtk_tree_view_get_model (view);
+ store = GTK_LIST_STORE (model);
+
+ /* Look up chatrooms */
+ account_chooser = GOSSIP_ACCOUNT_CHOOSER (window->account_chooser);
+ account = gossip_account_chooser_get_account (account_chooser);
+
+ chatrooms = gossip_chatroom_manager_get_chatrooms (window->manager, account);
+
+ /* Sort out columns, we only show the server column for
+ * selected protocol types, such as Jabber.
+ */
+ if (account) {
+ column = gtk_tree_view_get_column (view, window->room_column);
+ gtk_tree_view_column_set_visible (column, TRUE);
+ } else {
+ column = gtk_tree_view_get_column (view, window->room_column);
+ gtk_tree_view_column_set_visible (column, FALSE);
+ }
+
+ /* Clean out the store */
+ gtk_list_store_clear (store);
+
+ /* Populate with chatroom list. */
+ for (l = chatrooms; l; l = l->next) {
+ chatrooms_window_model_add (window, l->data, FALSE);
+ }
+
+ if (gtk_tree_model_get_iter_first (model, &iter)) {
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+
+ if (account) {
+ g_object_unref (account);
+ }
+
+ g_list_free (chatrooms);
+}
+
+static void
+chatrooms_window_model_add (GossipChatroomsWindow *window,
+ GossipChatroom *chatroom,
+ gboolean set_active)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkListStore *store;
+ GtkTreeIter iter;
+
+ view = GTK_TREE_VIEW (window->treeview);
+ selection = gtk_tree_view_get_selection (view);
+ model = gtk_tree_view_get_model (view);
+ store = GTK_LIST_STORE (model);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COL_NAME, gossip_chatroom_get_name (chatroom),
+ COL_ROOM, gossip_chatroom_get_room (chatroom),
+ COL_AUTO_CONNECT, gossip_chatroom_get_auto_connect (chatroom),
+ COL_POINTER, chatroom,
+ -1);
+
+ if (set_active) {
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+}
+
+static void
+chatrooms_window_model_cell_auto_connect_toggled (GtkCellRendererToggle *cell,
+ gchar *path_string,
+ GossipChatroomsWindow *window)
+{
+ GossipChatroom *chatroom;
+ gboolean enabled;
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ view = GTK_TREE_VIEW (window->treeview);
+ model = gtk_tree_view_get_model (view);
+ store = GTK_LIST_STORE (model);
+
+ path = gtk_tree_path_new_from_string (path_string);
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter,
+ COL_AUTO_CONNECT, &enabled,
+ COL_POINTER, &chatroom,
+ -1);
+
+ enabled = !enabled;
+
+ gossip_chatroom_set_auto_connect (chatroom, enabled);
+ gossip_chatroom_manager_store (window->manager);
+
+ gtk_list_store_set (store, &iter, COL_AUTO_CONNECT, enabled, -1);
+ gtk_tree_path_free (path);
+ g_object_unref (chatroom);
+}
+
+static GossipChatroom *
+chatrooms_window_model_get_selected (GossipChatroomsWindow *window)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GossipChatroom *chatroom = NULL;
+
+ view = GTK_TREE_VIEW (window->treeview);
+ selection = gtk_tree_view_get_selection (view);
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gtk_tree_model_get (model, &iter, COL_POINTER, &chatroom, -1);
+ }
+
+ return chatroom;
+}
+
+static void
+chatrooms_window_model_action_selected (GossipChatroomsWindow *window)
+{
+ GossipChatroom *chatroom;
+ GtkTreeView *view;
+ GtkTreeModel *model;
+
+ view = GTK_TREE_VIEW (window->treeview);
+ model = gtk_tree_view_get_model (view);
+
+ chatroom = chatrooms_window_model_get_selected (window);
+ if (!chatroom) {
+ return;
+ }
+
+ //gossip_edit_chatroom_dialog_show (GTK_WINDOW (window->window), chatroom);
+
+ g_object_unref (chatroom);
+}
+
+static void
+chatrooms_window_row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GossipChatroomsWindow *window)
+{
+ if (GTK_WIDGET_IS_SENSITIVE (window->button_edit)) {
+ chatrooms_window_model_action_selected (window);
+ }
+}
+
+static void
+chatrooms_window_button_remove_clicked_cb (GtkWidget *widget,
+ GossipChatroomsWindow *window)
+{
+ GossipChatroom *chatroom;
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ /* Remove from treeview */
+ view = GTK_TREE_VIEW (window->treeview);
+ selection = gtk_tree_view_get_selection (view);
+
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ return;
+ }
+
+ gtk_tree_model_get (model, &iter, COL_POINTER, &chatroom, -1);
+ gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+
+ /* Remove from config */
+ gossip_chatroom_manager_remove (window->manager, chatroom);
+
+ g_object_unref (chatroom);
+}
+
+static void
+chatrooms_window_button_edit_clicked_cb (GtkWidget *widget,
+ GossipChatroomsWindow *window)
+{
+ GossipChatroom *chatroom;
+
+ chatroom = chatrooms_window_model_get_selected (window);
+ if (!chatroom) {
+ return;
+ }
+
+ //gossip_edit_chatroom_dialog_show (GTK_WINDOW (window->window), chatroom);
+
+ g_object_unref (chatroom);
+}
+
+static void
+chatrooms_window_button_close_clicked_cb (GtkWidget *widget,
+ GossipChatroomsWindow *window)
+{
+ gtk_widget_destroy (window->window);
+}
+
+static void
+chatrooms_window_chatroom_added_cb (GossipChatroomManager *manager,
+ GossipChatroom *chatroom,
+ GossipChatroomsWindow *window)
+{
+ GossipAccountChooser *account_chooser;
+ McAccount *account;
+
+ account_chooser = GOSSIP_ACCOUNT_CHOOSER (window->account_chooser);
+ account = gossip_account_chooser_get_account (account_chooser);
+
+ if (!account) {
+ chatrooms_window_model_add (window, chatroom, FALSE);
+ } else {
+ if (gossip_account_equal (account, gossip_chatroom_get_account (chatroom))) {
+ chatrooms_window_model_add (window, chatroom, FALSE);
+ }
+
+ g_object_unref (account);
+ }
+}
+
+static void
+chatrooms_window_account_changed_cb (GtkWidget *combo_box,
+ GossipChatroomsWindow *window)
+{
+ chatrooms_window_model_refresh_data (window, FALSE);
+}
+
diff --git a/trunk/libempathy-gtk/gossip-chatrooms-window.glade b/trunk/libempathy-gtk/gossip-chatrooms-window.glade
new file mode 100644
index 000000000..ad2971e00
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-chatrooms-window.glade
@@ -0,0 +1,477 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkDialog" id="edit_chatroom_dialog">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Edit Favorite Room</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="icon_name">gtk-edit</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area3">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="button_cancel">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="button_save">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-save</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkTable" id="table4">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="n_rows">5</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+
+ <child>
+ <widget class="GtkEntry" id="entry_room">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry_server">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry_nickname">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label_room">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Room:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_room</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label_server">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">S_erver:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_server</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label_nickname">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Nickname:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_nickname</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label_name">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">N_ame:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_name</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry_name">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <property name="width_chars">25</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_auto_connect">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Join this chat room when Gossip starts and you are connected</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Join room on start_up</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+<widget class="GtkWindow" id="chatrooms_window">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Manage Favorite Rooms</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox12">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox18">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox_account">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="label_account">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Account:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="height_request">150</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="headers_visible">True</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHButtonBox" id="hbuttonbox3">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkButton" id="button_close">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="button_remove">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-remove</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="button_edit">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-edit</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/trunk/libempathy-gtk/gossip-chatrooms-window.h b/trunk/libempathy-gtk/gossip-chatrooms-window.h
new file mode 100644
index 000000000..d1314ec29
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-chatrooms-window.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Mikael Hallendal <micke@imendio.com>
+ */
+
+#ifndef __GOSSIP_CHATROOMS_WINDOW_H__
+#define __GOSSIP_CHATROOMS_WINDOW_H__
+
+G_BEGIN_DECLS
+
+void gossip_chatrooms_window_show (GtkWindow *parent);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CHATROOMS_WINDOW_H__ */
diff --git a/trunk/libempathy-gtk/gossip-contact-groups.c b/trunk/libempathy-gtk/gossip-contact-groups.c
new file mode 100644
index 000000000..8a6afda11
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-contact-groups.c
@@ -0,0 +1,286 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-utils.h>
+
+#include "gossip-contact-groups.h"
+
+#define DEBUG_DOMAIN "ContactGroups"
+
+#define CONTACT_GROUPS_XML_FILENAME "contact-groups.xml"
+#define CONTACT_GROUPS_DTD_FILENAME "gossip-contact-groups.dtd"
+
+typedef struct {
+ gchar *name;
+ gboolean expanded;
+} ContactGroup;
+
+static void contact_groups_file_parse (const gchar *filename);
+static gboolean contact_groups_file_save (void);
+static ContactGroup *contact_group_new (const gchar *name,
+ gboolean expanded);
+static void contact_group_free (ContactGroup *group);
+
+static GList *groups = NULL;
+
+void
+gossip_contact_groups_get_all (void)
+{
+ gchar *dir;
+ gchar *file_with_path;
+
+ /* If already set up clean up first */
+ if (groups) {
+ g_list_foreach (groups, (GFunc)contact_group_free, NULL);
+ g_list_free (groups);
+ groups = NULL;
+ }
+
+ dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
+ file_with_path = g_build_filename (dir, CONTACT_GROUPS_XML_FILENAME, NULL);
+ g_free (dir);
+
+ if (g_file_test (file_with_path, G_FILE_TEST_EXISTS)) {
+ contact_groups_file_parse (file_with_path);
+ }
+
+ g_free (file_with_path);
+}
+
+static void
+contact_groups_file_parse (const gchar *filename)
+{
+ xmlParserCtxtPtr ctxt;
+ xmlDocPtr doc;
+ xmlNodePtr contacts;
+ xmlNodePtr account;
+ xmlNodePtr node;
+
+ gossip_debug (DEBUG_DOMAIN, "Attempting to parse file:'%s'...", filename);
+
+ ctxt = xmlNewParserCtxt ();
+
+ /* Parse and validate the file. */
+ doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
+ if (!doc) {
+ g_warning ("Failed to parse file:'%s'", filename);
+ xmlFreeParserCtxt (ctxt);
+ return;
+ }
+
+ if (!gossip_xml_validate (doc, CONTACT_GROUPS_DTD_FILENAME)) {
+ g_warning ("Failed to validate file:'%s'", filename);
+ xmlFreeDoc(doc);
+ xmlFreeParserCtxt (ctxt);
+ return;
+ }
+
+ /* The root node, contacts. */
+ contacts = xmlDocGetRootElement (doc);
+
+ account = NULL;
+ node = contacts->children;
+ while (node) {
+ if (strcmp ((gchar *) node->name, "account") == 0) {
+ account = node;
+ break;
+ }
+ node = node->next;
+ }
+
+ node = NULL;
+ if (account) {
+ node = account->children;
+ }
+
+ while (node) {
+ if (strcmp ((gchar *) node->name, "group") == 0) {
+ gchar *name;
+ gchar *expanded_str;
+ gboolean expanded;
+ ContactGroup *contact_group;
+
+ name = (gchar *) xmlGetProp (node, "name");
+ expanded_str = (gchar *) xmlGetProp (node, "expanded");
+
+ if (expanded_str && strcmp (expanded_str, "yes") == 0) {
+ expanded = TRUE;
+ } else {
+ expanded = FALSE;
+ }
+
+ contact_group = contact_group_new (name, expanded);
+ groups = g_list_append (groups, contact_group);
+
+ xmlFree (name);
+ xmlFree (expanded_str);
+ }
+
+ node = node->next;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Parsed %d contact groups", g_list_length (groups));
+
+ xmlFreeDoc(doc);
+ xmlFreeParserCtxt (ctxt);
+}
+
+static ContactGroup *
+contact_group_new (const gchar *name,
+ gboolean expanded)
+{
+ ContactGroup *group;
+
+ group = g_new0 (ContactGroup, 1);
+
+ group->name = g_strdup (name);
+ group->expanded = expanded;
+
+ return group;
+}
+
+static void
+contact_group_free (ContactGroup *group)
+{
+ g_return_if_fail (group != NULL);
+
+ g_free (group->name);
+
+ g_free (group);
+}
+
+static gboolean
+contact_groups_file_save (void)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root;
+ xmlNodePtr node;
+ GList *l;
+ gchar *dir;
+ gchar *file;
+
+ dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
+ g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
+ file = g_build_filename (dir, CONTACT_GROUPS_XML_FILENAME, NULL);
+ g_free (dir);
+
+ doc = xmlNewDoc ("1.0");
+ root = xmlNewNode (NULL, "contacts");
+ xmlDocSetRootElement (doc, root);
+
+ node = xmlNewChild (root, NULL, "account", NULL);
+ xmlNewProp (node, "name", "Default");
+
+ for (l = groups; l; l = l->next) {
+ ContactGroup *cg;
+ xmlNodePtr subnode;
+
+ cg = l->data;
+
+ subnode = xmlNewChild (node, NULL, "group", NULL);
+ xmlNewProp (subnode, "expanded", cg->expanded ? "yes" : "no");
+ xmlNewProp (subnode, "name", cg->name);
+ }
+
+ /* Make sure the XML is indented properly */
+ xmlIndentTreeOutput = 1;
+
+ gossip_debug (DEBUG_DOMAIN, "Saving file:'%s'", file);
+ xmlSaveFormatFileEnc (file, doc, "utf-8", 1);
+ xmlFreeDoc (doc);
+
+ xmlCleanupParser ();
+ xmlMemoryDump ();
+
+ g_free (file);
+
+ return TRUE;
+}
+
+gboolean
+gossip_contact_group_get_expanded (const gchar *group)
+{
+ GList *l;
+ gboolean default_val = TRUE;
+
+ g_return_val_if_fail (group != NULL, default_val);
+
+ for (l = groups; l; l = l->next) {
+ ContactGroup *cg = l->data;
+
+ if (!cg || !cg->name) {
+ continue;
+ }
+
+ if (strcmp (cg->name, group) == 0) {
+ return cg->expanded;
+ }
+ }
+
+ return default_val;
+}
+
+void
+gossip_contact_group_set_expanded (const gchar *group,
+ gboolean expanded)
+{
+ GList *l;
+ ContactGroup *cg;
+ gboolean changed = FALSE;
+
+ g_return_if_fail (group != NULL);
+
+ for (l = groups; l; l = l->next) {
+ ContactGroup *cg = l->data;
+
+ if (!cg || !cg->name) {
+ continue;
+ }
+
+ if (strcmp (cg->name, group) == 0) {
+ cg->expanded = expanded;
+ changed = TRUE;
+ break;
+ }
+ }
+
+ /* if here... we don't have a ContactGroup for the group. */
+ if (!changed) {
+ cg = contact_group_new (group, expanded);
+ groups = g_list_append (groups, cg);
+ }
+
+ contact_groups_file_save ();
+}
diff --git a/trunk/libempathy-gtk/gossip-contact-groups.dtd b/trunk/libempathy-gtk/gossip-contact-groups.dtd
new file mode 100644
index 000000000..689220f0e
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-contact-groups.dtd
@@ -0,0 +1,17 @@
+<!--
+ DTD for Gossips contact groups.
+ by Martyn Russell <mr@gnome.org>
+-->
+
+<!-- Root element. -->
+<!ELEMENT contacts (account)>
+
+<!ELEMENT account (group)+>
+<!ATTLIST account
+ name CDATA #REQUIRED>
+
+<!-- Groups in the roster. -->
+<!ELEMENT group EMPTY>
+<!ATTLIST group
+ name CDATA #REQUIRED
+ expanded CDATA #REQUIRED>
diff --git a/trunk/libempathy-gtk/gossip-contact-groups.h b/trunk/libempathy-gtk/gossip-contact-groups.h
new file mode 100644
index 000000000..88bbdc0cb
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-contact-groups.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_CONTACT_GROUPS_H__
+#define __GOSSIP_CONTACT_GROUPS_H__
+
+G_BEGIN_DECLS
+
+#include <glib.h>
+
+void gossip_contact_groups_get_all (void);
+
+gboolean gossip_contact_group_get_expanded (const gchar *group);
+void gossip_contact_group_set_expanded (const gchar *group,
+ gboolean expanded);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CONTACT_GROUPS_H__ */
diff --git a/trunk/libempathy-gtk/gossip-contact-list-store.c b/trunk/libempathy-gtk/gossip-contact-list-store.c
new file mode 100644
index 000000000..f0ff476dd
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-contact-list-store.c
@@ -0,0 +1,1458 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include <libempathy/gossip-debug.h>
+
+#include "gossip-contact-list-store.h"
+#include "gossip-contact-groups.h"
+#include "gossip-ui-utils.h"
+
+#define DEBUG_DOMAIN "ContactListStore"
+
+/* Active users are those which have recently changed state
+ * (e.g. online, offline or from normal to a busy state).
+ */
+
+/* Time user is shown as active */
+#define ACTIVE_USER_SHOW_TIME 7000
+
+/* Time after connecting which we wait before active users are enabled */
+#define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5000
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST_STORE, GossipContactListStorePriv))
+
+struct _GossipContactListStorePriv {
+ EmpathyContactList *list;
+ gboolean show_offline;
+ gboolean show_avatars;
+ gboolean is_compact;
+ gboolean show_active;
+ GossipContactListStoreSort sort_criterium;
+
+ GossipContactGroupsFunc get_contact_groups;
+ gpointer get_contact_groups_data;
+};
+
+typedef struct {
+ GtkTreeIter iter;
+ const gchar *name;
+ gboolean found;
+} FindGroup;
+
+typedef struct {
+ GossipContact *contact;
+ gboolean found;
+ GList *iters;
+} FindContact;
+
+typedef struct {
+ GossipContactListStore *store;
+ GossipContact *contact;
+ gboolean remove;
+} ShowActiveData;
+
+static void gossip_contact_list_store_class_init (GossipContactListStoreClass *klass);
+static void gossip_contact_list_store_init (GossipContactListStore *list);
+static void contact_list_store_finalize (GObject *object);
+static void contact_list_store_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void contact_list_store_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void contact_list_store_setup (GossipContactListStore *store);
+static void contact_list_store_contact_added_cb (EmpathyContactList *list_iface,
+ GossipContact *contact,
+ GossipContactListStore *store);
+static void contact_list_store_add_contact (GossipContactListStore *store,
+ GossipContact *contact);
+static void contact_list_store_contact_removed_cb (EmpathyContactList *list_iface,
+ GossipContact *contact,
+ GossipContactListStore *store);
+static void contact_list_store_remove_contact (GossipContactListStore *store,
+ GossipContact *contact);
+static void contact_list_store_contact_update (GossipContactListStore *store,
+ GossipContact *contact);
+static void contact_list_store_contact_groups_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipContactListStore *store);
+static void contact_list_store_contact_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipContactListStore *store);
+static void contact_list_store_contact_set_active (GossipContactListStore *store,
+ GossipContact *contact,
+ gboolean active,
+ gboolean set_changed);
+static ShowActiveData * contact_list_store_contact_active_new (GossipContactListStore *store,
+ GossipContact *contact,
+ gboolean remove);
+static void contact_list_store_contact_active_free (ShowActiveData *data);
+static gboolean contact_list_store_contact_active_cb (ShowActiveData *data);
+static gboolean contact_list_store_get_group_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ FindGroup *fg);
+static void contact_list_store_get_group (GossipContactListStore *store,
+ const gchar *name,
+ GtkTreeIter *iter_group_to_set,
+ GtkTreeIter *iter_separator_to_set,
+ gboolean *created);
+static gint contact_list_store_state_sort_func (GtkTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b,
+ gpointer user_data);
+static gint contact_list_store_name_sort_func (GtkTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b,
+ gpointer user_data);
+static gboolean contact_list_store_find_contact_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ FindContact *fc);
+static GList * contact_list_store_find_contact (GossipContactListStore *store,
+ GossipContact *contact);
+static gboolean contact_list_store_update_list_mode_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GossipContactListStore *store);
+
+enum {
+ PROP_0,
+ PROP_SHOW_OFFLINE,
+ PROP_SHOW_AVATARS,
+ PROP_IS_COMPACT,
+ PROP_SORT_CRITERIUM
+};
+
+GType
+gossip_contact_list_store_sort_get_type (void)
+{
+ static GType etype = 0;
+
+ if (etype == 0) {
+ static const GEnumValue values[] = {
+ { GOSSIP_CONTACT_LIST_STORE_SORT_NAME,
+ "GOSSIP_CONTACT_LIST_STORE_SORT_NAME",
+ "name" },
+ { GOSSIP_CONTACT_LIST_STORE_SORT_STATE,
+ "GOSSIP_CONTACT_LIST_STORE_SORT_STATE",
+ "state" },
+ { 0, NULL, NULL }
+ };
+
+ etype = g_enum_register_static ("GossipContactListStoreSort", values);
+ }
+
+ return etype;
+}
+
+G_DEFINE_TYPE (GossipContactListStore, gossip_contact_list_store, GTK_TYPE_TREE_STORE);
+
+static void
+gossip_contact_list_store_class_init (GossipContactListStoreClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = contact_list_store_finalize;
+ object_class->get_property = contact_list_store_get_property;
+ object_class->set_property = contact_list_store_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_SHOW_OFFLINE,
+ g_param_spec_boolean ("show-offline",
+ "Show Offline",
+ "Whether contact list should display "
+ "offline contacts",
+ FALSE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SHOW_AVATARS,
+ g_param_spec_boolean ("show-avatars",
+ "Show Avatars",
+ "Whether contact list should display "
+ "avatars for contacts",
+ TRUE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_IS_COMPACT,
+ g_param_spec_boolean ("is-compact",
+ "Is Compact",
+ "Whether the contact list is in compact mode or not",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_SORT_CRITERIUM,
+ g_param_spec_enum ("sort-criterium",
+ "Sort citerium",
+ "The sort criterium to use for sorting the contact list",
+ GOSSIP_TYPE_CONTACT_LIST_STORE_SORT,
+ GOSSIP_CONTACT_LIST_STORE_SORT_NAME,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (GossipContactListStorePriv));
+}
+
+static void
+gossip_contact_list_store_init (GossipContactListStore *store)
+{
+ GossipContactListStorePriv *priv;
+
+ priv = GET_PRIV (store);
+
+ priv->is_compact = FALSE;
+ priv->show_active = TRUE;
+ priv->show_avatars = TRUE;
+}
+
+static void
+contact_list_store_finalize (GObject *object)
+{
+ GossipContactListStorePriv *priv;
+
+ priv = GET_PRIV (object);
+
+ /* FIXME: disconnect all signals on the list and contacts */
+
+ if (priv->list) {
+ g_object_unref (priv->list);
+ }
+
+ G_OBJECT_CLASS (gossip_contact_list_store_parent_class)->finalize (object);
+}
+
+static void
+contact_list_store_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GossipContactListStorePriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_SHOW_OFFLINE:
+ g_value_set_boolean (value, priv->show_offline);
+ break;
+ case PROP_SHOW_AVATARS:
+ g_value_set_boolean (value, priv->show_avatars);
+ break;
+ case PROP_IS_COMPACT:
+ g_value_set_boolean (value, priv->is_compact);
+ break;
+ case PROP_SORT_CRITERIUM:
+ g_value_set_enum (value, priv->sort_criterium);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+contact_list_store_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GossipContactListStorePriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_SHOW_OFFLINE:
+ gossip_contact_list_store_set_show_offline (GOSSIP_CONTACT_LIST_STORE (object),
+ g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_AVATARS:
+ gossip_contact_list_store_set_show_avatars (GOSSIP_CONTACT_LIST_STORE (object),
+ g_value_get_boolean (value));
+ break;
+ case PROP_IS_COMPACT:
+ gossip_contact_list_store_set_is_compact (GOSSIP_CONTACT_LIST_STORE (object),
+ g_value_get_boolean (value));
+ break;
+ case PROP_SORT_CRITERIUM:
+ gossip_contact_list_store_set_sort_criterium (GOSSIP_CONTACT_LIST_STORE (object),
+ g_value_get_enum (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+GossipContactListStore *
+gossip_contact_list_store_new (EmpathyContactList *list_iface)
+{
+ GossipContactListStore *store;
+ GossipContactListStorePriv *priv;
+ GList *contacts, *l;
+ gboolean show_active;
+
+ g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list_iface), NULL);
+
+ store = g_object_new (GOSSIP_TYPE_CONTACT_LIST_STORE, NULL);
+ priv = GET_PRIV (store);
+
+ contact_list_store_setup (store);
+ priv->list = g_object_ref (list_iface);
+
+ /* Signal connection. */
+ g_signal_connect (priv->list,
+ "contact-added",
+ G_CALLBACK (contact_list_store_contact_added_cb),
+ store);
+ g_signal_connect (priv->list,
+ "contact-removed",
+ G_CALLBACK (contact_list_store_contact_removed_cb),
+ store);
+
+ /* Add contacts already created. Do not highlight them. */
+ show_active = priv->show_active;
+ priv->show_active = FALSE;
+ contacts = empathy_contact_list_get_contacts (priv->list);
+ for (l = contacts; l; l = l->next) {
+ GossipContact *contact;
+
+ contact = l->data;
+
+ contact_list_store_contact_added_cb (priv->list, contact, store);
+
+ g_object_unref (contact);
+ }
+ g_list_free (contacts);
+ priv->show_active = show_active;
+
+ return store;
+}
+
+EmpathyContactList *
+gossip_contact_list_store_get_list_iface (GossipContactListStore *store)
+{
+ GossipContactListStorePriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store), FALSE);
+
+ priv = GET_PRIV (store);
+
+ return priv->list;
+}
+
+gboolean
+gossip_contact_list_store_get_show_offline (GossipContactListStore *store)
+{
+ GossipContactListStorePriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store), FALSE);
+
+ priv = GET_PRIV (store);
+
+ return priv->show_offline;
+}
+
+void
+gossip_contact_list_store_set_show_offline (GossipContactListStore *store,
+ gboolean show_offline)
+{
+ GossipContactListStorePriv *priv;
+ GList *contacts, *l;
+ gboolean show_active;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store));
+
+ priv = GET_PRIV (store);
+
+ priv->show_offline = show_offline;
+ show_active = priv->show_active;
+
+ /* Disable temporarily. */
+ priv->show_active = FALSE;
+
+ contacts = empathy_contact_list_get_contacts (priv->list);
+ for (l = contacts; l; l = l->next) {
+ GossipContact *contact;
+
+ contact = GOSSIP_CONTACT (l->data);
+
+ contact_list_store_contact_update (store, contact);
+
+ g_object_unref (contact);
+ }
+ g_list_free (contacts);
+
+ /* Restore to original setting. */
+ priv->show_active = show_active;
+}
+
+gboolean
+gossip_contact_list_store_get_show_avatars (GossipContactListStore *store)
+{
+ GossipContactListStorePriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store), TRUE);
+
+ priv = GET_PRIV (store);
+
+ return priv->show_avatars;
+}
+
+void
+gossip_contact_list_store_set_show_avatars (GossipContactListStore *store,
+ gboolean show_avatars)
+{
+ GossipContactListStorePriv *priv;
+ GtkTreeModel *model;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store));
+
+ priv = GET_PRIV (store);
+
+ priv->show_avatars = show_avatars;
+
+ model = GTK_TREE_MODEL (store);
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc)
+ contact_list_store_update_list_mode_foreach,
+ store);
+}
+
+gboolean
+gossip_contact_list_store_get_is_compact (GossipContactListStore *store)
+{
+ GossipContactListStorePriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store), TRUE);
+
+ priv = GET_PRIV (store);
+
+ return priv->is_compact;
+}
+
+void
+gossip_contact_list_store_set_is_compact (GossipContactListStore *store,
+ gboolean is_compact)
+{
+ GossipContactListStorePriv *priv;
+ GtkTreeModel *model;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store));
+
+ priv = GET_PRIV (store);
+
+ priv->is_compact = is_compact;
+
+ model = GTK_TREE_MODEL (store);
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc)
+ contact_list_store_update_list_mode_foreach,
+ store);
+}
+
+GossipContactListStoreSort
+gossip_contact_list_store_get_sort_criterium (GossipContactListStore *store)
+{
+ GossipContactListStorePriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store), 0);
+
+ priv = GET_PRIV (store);
+
+ return priv->sort_criterium;
+}
+
+void
+gossip_contact_list_store_set_sort_criterium (GossipContactListStore *store,
+ GossipContactListStoreSort sort_criterium)
+{
+ GossipContactListStorePriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store));
+
+ priv = GET_PRIV (store);
+
+ priv->sort_criterium = sort_criterium;
+
+ switch (sort_criterium) {
+ case GOSSIP_CONTACT_LIST_STORE_SORT_STATE:
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+ COL_STATUS,
+ GTK_SORT_ASCENDING);
+ break;
+
+ case GOSSIP_CONTACT_LIST_STORE_SORT_NAME:
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+ COL_NAME,
+ GTK_SORT_ASCENDING);
+ break;
+ }
+}
+
+gboolean
+gossip_contact_list_store_row_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gboolean is_separator = FALSE;
+
+ g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
+
+ gtk_tree_model_get (model, iter,
+ COL_IS_SEPARATOR, &is_separator,
+ -1);
+
+ return is_separator;
+}
+
+gchar *
+gossip_contact_list_store_get_parent_group (GtkTreeModel *model,
+ GtkTreePath *path,
+ gboolean *path_is_group)
+{
+ GtkTreeIter parent_iter, iter;
+ gchar *name = NULL;
+ gboolean is_group;
+
+ g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
+
+ if (path_is_group) {
+ *path_is_group = FALSE;
+ }
+
+ if (!gtk_tree_model_get_iter (model, &iter, path)) {
+ return NULL;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ COL_IS_GROUP, &is_group,
+ COL_NAME, &name,
+ -1);
+
+ if (!is_group) {
+ g_free (name);
+ name = NULL;
+
+ if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
+ return NULL;
+ }
+
+ iter = parent_iter;
+
+ gtk_tree_model_get (model, &iter,
+ COL_IS_GROUP, &is_group,
+ COL_NAME, &name,
+ -1);
+ if (!is_group) {
+ g_free (name);
+ return NULL;
+ }
+ }
+
+ if (path_is_group) {
+ *path_is_group = TRUE;
+ }
+
+ return name;
+}
+
+gboolean
+gossip_contact_list_store_search_equal_func (GtkTreeModel *model,
+ gint column,
+ const gchar *key,
+ GtkTreeIter *iter,
+ gpointer search_data)
+{
+ gchar *name, *name_folded;
+ gchar *key_folded;
+ gboolean ret;
+
+ g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
+
+ if (!key) {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
+
+ if (!name) {
+ return FALSE;
+ }
+
+ name_folded = g_utf8_casefold (name, -1);
+ key_folded = g_utf8_casefold (key, -1);
+
+ if (name_folded && key_folded &&
+ strstr (name_folded, key_folded)) {
+ ret = FALSE;
+ } else {
+ ret = TRUE;
+ }
+
+ g_free (name);
+ g_free (name_folded);
+ g_free (key_folded);
+
+ return ret;
+}
+
+void
+gossip_contact_list_store_set_contact_groups_func (GossipContactListStore *store,
+ GossipContactGroupsFunc func,
+ gpointer user_data)
+{
+ GossipContactListStorePriv *priv;
+ GList *contacts, *l;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store));
+
+ priv = GET_PRIV (store);
+
+ if (func) {
+ priv->get_contact_groups = func;
+ priv->get_contact_groups_data = user_data;
+ } else {
+ priv->get_contact_groups = NULL;
+ priv->get_contact_groups_data = NULL;
+ }
+
+ /* If we set a custom function to get contacts groups we have to
+ * disconnect our default notify::groups signal and wait for the user
+ * to call himself gossip_contact_list_store_update_contact_groups ()
+ * when needed. If func is NULL we come back to default.
+ */
+ contacts = empathy_contact_list_get_contacts (priv->list);
+ for (l = contacts; l; l = l->next) {
+ GossipContact *contact;
+
+ contact = l->data;
+
+ if (func) {
+ g_signal_handlers_disconnect_by_func (contact,
+ G_CALLBACK (contact_list_store_contact_groups_updated_cb),
+ store);
+ } else {
+ g_signal_connect (contact, "notify::groups",
+ G_CALLBACK (contact_list_store_contact_groups_updated_cb),
+ store);
+ }
+
+ gossip_contact_list_store_update_contact_groups (store, contact);
+
+ g_object_unref (contact);
+ }
+ g_list_free (contacts);
+}
+
+void
+gossip_contact_list_store_update_contact_groups (GossipContactListStore *store,
+ GossipContact *contact)
+{
+ gossip_debug (DEBUG_DOMAIN, "Contact:'%s' updating groups",
+ gossip_contact_get_name (contact));
+
+ /* We do this to make sure the groups are correct, if not, we
+ * would have to check the groups already set up for each
+ * contact and then see what has been updated.
+ */
+ contact_list_store_remove_contact (store, contact);
+ contact_list_store_add_contact (store, contact);
+}
+
+static void
+contact_list_store_setup (GossipContactListStore *store)
+{
+ GossipContactListStorePriv *priv;
+ GType types[] = {G_TYPE_STRING, /* Status icon-name */
+ GDK_TYPE_PIXBUF, /* Avatar pixbuf */
+ G_TYPE_BOOLEAN, /* Avatar pixbuf visible */
+ G_TYPE_STRING, /* Name */
+ G_TYPE_STRING, /* Status string */
+ G_TYPE_BOOLEAN, /* Show status */
+ GOSSIP_TYPE_CONTACT, /* Contact type */
+ G_TYPE_BOOLEAN, /* Is group */
+ G_TYPE_BOOLEAN, /* Is active */
+ G_TYPE_BOOLEAN, /* Is online */
+ G_TYPE_BOOLEAN}; /* Is separator */
+
+ priv = GET_PRIV (store);
+
+ gtk_tree_store_set_column_types (GTK_TREE_STORE (store), COL_COUNT, types);
+
+ /* Set up sorting */
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
+ COL_NAME,
+ contact_list_store_name_sort_func,
+ store, NULL);
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
+ COL_STATUS,
+ contact_list_store_state_sort_func,
+ store, NULL);
+
+ priv->sort_criterium = GOSSIP_CONTACT_LIST_STORE_SORT_NAME;
+ gossip_contact_list_store_set_sort_criterium (store, priv->sort_criterium);
+}
+
+static void
+contact_list_store_contact_added_cb (EmpathyContactList *list_iface,
+ GossipContact *contact,
+ GossipContactListStore *store)
+{
+ GossipContactListStorePriv *priv;
+
+ priv = GET_PRIV (store);
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' added",
+ gossip_contact_get_name (contact));
+
+ if (!priv->get_contact_groups) {
+ g_signal_connect (contact, "notify::groups",
+ G_CALLBACK (contact_list_store_contact_groups_updated_cb),
+ store);
+ }
+ g_signal_connect (contact, "notify::presence",
+ G_CALLBACK (contact_list_store_contact_updated_cb),
+ store);
+ g_signal_connect (contact, "notify::name",
+ G_CALLBACK (contact_list_store_contact_updated_cb),
+ store);
+ g_signal_connect (contact, "notify::avatar",
+ G_CALLBACK (contact_list_store_contact_updated_cb),
+ store);
+ g_signal_connect (contact, "notify::type",
+ G_CALLBACK (contact_list_store_contact_updated_cb),
+ store);
+
+ contact_list_store_add_contact (store, contact);
+}
+
+static void
+contact_list_store_add_contact (GossipContactListStore *store,
+ GossipContact *contact)
+{
+ GossipContactListStorePriv *priv;
+ GtkTreeIter iter;
+ GList *groups, *l;
+
+ priv = GET_PRIV (store);
+
+ if (!priv->show_offline && !gossip_contact_is_online (contact)) {
+ return;
+ }
+
+ /* If no groups just add it at the top level. */
+ if (priv->get_contact_groups) {
+ groups = priv->get_contact_groups (contact,
+ priv->get_contact_groups_data);
+ } else {
+ groups = gossip_contact_get_groups (contact);
+ }
+
+ if (!groups) {
+ gtk_tree_store_append (GTK_TREE_STORE (store), &iter, NULL);
+ gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
+ COL_NAME, gossip_contact_get_name (contact),
+ COL_CONTACT, contact,
+ COL_IS_GROUP, FALSE,
+ COL_IS_SEPARATOR, FALSE,
+ -1);
+ }
+
+ /* Else add to each group. */
+ for (l = groups; l; l = l->next) {
+ GtkTreeIter iter_group;
+ const gchar *name;
+
+ name = l->data;
+ if (!name) {
+ continue;
+ }
+
+ contact_list_store_get_group (store, name, &iter_group, NULL, NULL);
+
+ gtk_tree_store_insert_after (GTK_TREE_STORE (store), &iter,
+ &iter_group, NULL);
+ gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
+ COL_NAME, gossip_contact_get_name (contact),
+ COL_CONTACT, contact,
+ COL_IS_GROUP, FALSE,
+ COL_IS_SEPARATOR, FALSE,
+ -1);
+ }
+
+ contact_list_store_contact_update (store, contact);
+}
+
+static void
+contact_list_store_contact_removed_cb (EmpathyContactList *list_iface,
+ GossipContact *contact,
+ GossipContactListStore *store)
+{
+ gossip_debug (DEBUG_DOMAIN, "Contact:'%s' removed",
+ gossip_contact_get_name (contact));
+
+ /* Disconnect signals */
+ g_signal_handlers_disconnect_by_func (contact,
+ G_CALLBACK (contact_list_store_contact_groups_updated_cb),
+ store);
+ g_signal_handlers_disconnect_by_func (contact,
+ G_CALLBACK (contact_list_store_contact_updated_cb),
+ store);
+
+ contact_list_store_remove_contact (store, contact);
+}
+
+static void
+contact_list_store_remove_contact (GossipContactListStore *store,
+ GossipContact *contact)
+{
+ GossipContactListStorePriv *priv;
+ GtkTreeModel *model;
+ GList *iters, *l;
+
+ priv = GET_PRIV (store);
+
+ iters = contact_list_store_find_contact (store, contact);
+ if (!iters) {
+ return;
+ }
+
+ /* Clean up model */
+ model = GTK_TREE_MODEL (store);
+
+ for (l = iters; l; l = l->next) {
+ GtkTreeIter parent;
+
+ /* NOTE: it is only <= 2 here because we have
+ * separators after the group name, otherwise it
+ * should be 1.
+ */
+ if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
+ gtk_tree_model_iter_n_children (model, &parent) <= 2) {
+ gtk_tree_store_remove (GTK_TREE_STORE (store), &parent);
+ } else {
+ gtk_tree_store_remove (GTK_TREE_STORE (store), l->data);
+ }
+ }
+
+ g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
+ g_list_free (iters);
+}
+
+static void
+contact_list_store_contact_update (GossipContactListStore *store,
+ GossipContact *contact)
+{
+ GossipContactListStorePriv *priv;
+ ShowActiveData *data;
+ GtkTreeModel *model;
+ GList *iters, *l;
+ gboolean in_list;
+ gboolean should_be_in_list;
+ gboolean was_online = TRUE;
+ gboolean now_online = FALSE;
+ gboolean set_model = FALSE;
+ gboolean do_remove = FALSE;
+ gboolean do_set_active = FALSE;
+ gboolean do_set_refresh = FALSE;
+ GdkPixbuf *pixbuf_avatar;
+
+ priv = GET_PRIV (store);
+
+ model = GTK_TREE_MODEL (store);
+
+ iters = contact_list_store_find_contact (store, contact);
+ if (!iters) {
+ in_list = FALSE;
+ } else {
+ in_list = TRUE;
+ }
+
+ /* Get online state now. */
+ now_online = gossip_contact_is_online (contact);
+
+ if (priv->show_offline || now_online) {
+ should_be_in_list = TRUE;
+ } else {
+ should_be_in_list = FALSE;
+ }
+
+ if (!in_list && !should_be_in_list) {
+ /* Nothing to do. */
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' in list:NO, should be:NO",
+ gossip_contact_get_name (contact));
+
+ g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
+ g_list_free (iters);
+ return;
+ }
+ else if (in_list && !should_be_in_list) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' in list:YES, should be:NO",
+ gossip_contact_get_name (contact));
+
+ if (priv->show_active) {
+ do_remove = TRUE;
+ do_set_active = TRUE;
+ do_set_refresh = TRUE;
+
+ set_model = TRUE;
+ gossip_debug (DEBUG_DOMAIN, "Remove item (after timeout)");
+ } else {
+ gossip_debug (DEBUG_DOMAIN, "Remove item (now)!");
+ contact_list_store_remove_contact (store, contact);
+ }
+ }
+ else if (!in_list && should_be_in_list) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' in list:NO, should be:YES",
+ gossip_contact_get_name (contact));
+
+ contact_list_store_add_contact (store, contact);
+
+ if (priv->show_active) {
+ do_set_active = TRUE;
+
+ gossip_debug (DEBUG_DOMAIN, "Set active (contact added)");
+ }
+ } else {
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' in list:YES, should be:YES",
+ gossip_contact_get_name (contact));
+
+ /* Get online state before. */
+ if (iters && g_list_length (iters) > 0) {
+ gtk_tree_model_get (model, iters->data,
+ COL_IS_ONLINE, &was_online,
+ -1);
+ }
+
+ /* Is this really an update or an online/offline. */
+ if (priv->show_active) {
+ if (was_online != now_online) {
+ do_set_active = TRUE;
+ do_set_refresh = TRUE;
+
+ gossip_debug (DEBUG_DOMAIN, "Set active (contact updated %s)",
+ was_online ? "online -> offline" :
+ "offline -> online");
+ } else {
+ /* Was TRUE for presence updates. */
+ /* do_set_active = FALSE; */
+ do_set_refresh = TRUE;
+
+ gossip_debug (DEBUG_DOMAIN, "Set active (contact updated)");
+ }
+ }
+
+ set_model = TRUE;
+ }
+
+ pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
+ for (l = iters; l && set_model; l = l->next) {
+ gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
+ COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
+ COL_PIXBUF_AVATAR, pixbuf_avatar,
+ COL_PIXBUF_AVATAR_VISIBLE, priv->show_avatars,
+ COL_NAME, gossip_contact_get_name (contact),
+ COL_STATUS, gossip_contact_get_status (contact),
+ COL_STATUS_VISIBLE, !priv->is_compact,
+ COL_IS_GROUP, FALSE,
+ COL_IS_ONLINE, now_online,
+ COL_IS_SEPARATOR, FALSE,
+ -1);
+ }
+
+ if (pixbuf_avatar) {
+ g_object_unref (pixbuf_avatar);
+ }
+
+ if (priv->show_active && do_set_active) {
+ contact_list_store_contact_set_active (store, contact, do_set_active, do_set_refresh);
+
+ if (do_set_active) {
+ data = contact_list_store_contact_active_new (store, contact, do_remove);
+ g_timeout_add (ACTIVE_USER_SHOW_TIME,
+ (GSourceFunc) contact_list_store_contact_active_cb,
+ data);
+ }
+ }
+
+ /* FIXME: when someone goes online then offline quickly, the
+ * first timeout sets the user to be inactive and the second
+ * timeout removes the user from the contact list, really we
+ * should remove the first timeout.
+ */
+ g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
+ g_list_free (iters);
+}
+
+static void
+contact_list_store_contact_groups_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipContactListStore *store)
+{
+ gossip_contact_list_store_update_contact_groups (store, contact);
+}
+
+static void
+contact_list_store_contact_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipContactListStore *store)
+{
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' updated, checking roster is in sync...",
+ gossip_contact_get_name (contact));
+
+ contact_list_store_contact_update (store, contact);
+}
+
+static void
+contact_list_store_contact_set_active (GossipContactListStore *store,
+ GossipContact *contact,
+ gboolean active,
+ gboolean set_changed)
+{
+ GossipContactListStorePriv *priv;
+ GtkTreeModel *model;
+ GList *iters, *l;
+
+ priv = GET_PRIV (store);
+ model = GTK_TREE_MODEL (store);
+
+ iters = contact_list_store_find_contact (store, contact);
+ for (l = iters; l; l = l->next) {
+ GtkTreePath *path;
+
+ gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
+ COL_IS_ACTIVE, active,
+ -1);
+
+ gossip_debug (DEBUG_DOMAIN, "Set item %s", active ? "active" : "inactive");
+
+ if (set_changed) {
+ path = gtk_tree_model_get_path (model, l->data);
+ gtk_tree_model_row_changed (model, path, l->data);
+ gtk_tree_path_free (path);
+ }
+ }
+
+ g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
+ g_list_free (iters);
+
+}
+
+static ShowActiveData *
+contact_list_store_contact_active_new (GossipContactListStore *store,
+ GossipContact *contact,
+ gboolean remove)
+{
+ ShowActiveData *data;
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' now active, and %s be removed",
+ gossip_contact_get_name (contact),
+ remove ? "WILL" : "WILL NOT");
+
+ data = g_slice_new0 (ShowActiveData);
+
+ data->store = g_object_ref (store);
+ data->contact = g_object_ref (contact);
+ data->remove = remove;
+
+ return data;
+}
+
+static void
+contact_list_store_contact_active_free (ShowActiveData *data)
+{
+ g_object_unref (data->contact);
+ g_object_unref (data->store);
+
+ g_slice_free (ShowActiveData, data);
+}
+
+static gboolean
+contact_list_store_contact_active_cb (ShowActiveData *data)
+{
+ GossipContactListStorePriv *priv;
+
+ priv = GET_PRIV (data->store);
+
+ if (data->remove &&
+ !priv->show_offline &&
+ !gossip_contact_is_online (data->contact)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' active timeout, removing item",
+ gossip_contact_get_name (data->contact));
+ contact_list_store_remove_contact (data->store, data->contact);
+ }
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' no longer active",
+ gossip_contact_get_name (data->contact));
+
+ contact_list_store_contact_set_active (data->store,
+ data->contact,
+ FALSE,
+ TRUE);
+
+ contact_list_store_contact_active_free (data);
+
+ return FALSE;
+}
+
+static gboolean
+contact_list_store_get_group_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ FindGroup *fg)
+{
+ gchar *str;
+ gboolean is_group;
+
+ /* Groups are only at the top level. */
+ if (gtk_tree_path_get_depth (path) != 1) {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (model, iter,
+ COL_NAME, &str,
+ COL_IS_GROUP, &is_group,
+ -1);
+
+ if (is_group && strcmp (str, fg->name) == 0) {
+ fg->found = TRUE;
+ fg->iter = *iter;
+ }
+
+ g_free (str);
+
+ return fg->found;
+}
+
+static void
+contact_list_store_get_group (GossipContactListStore *store,
+ const gchar *name,
+ GtkTreeIter *iter_group_to_set,
+ GtkTreeIter *iter_separator_to_set,
+ gboolean *created)
+{
+ GossipContactListStorePriv *priv;
+ GtkTreeModel *model;
+ GtkTreeIter iter_group;
+ GtkTreeIter iter_separator;
+ FindGroup fg;
+
+ priv = GET_PRIV (store);
+
+ memset (&fg, 0, sizeof (fg));
+
+ fg.name = name;
+
+ model = GTK_TREE_MODEL (store);
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc) contact_list_store_get_group_foreach,
+ &fg);
+
+ if (!fg.found) {
+ if (created) {
+ *created = TRUE;
+ }
+
+ gtk_tree_store_append (GTK_TREE_STORE (store), &iter_group, NULL);
+ gtk_tree_store_set (GTK_TREE_STORE (store), &iter_group,
+ COL_ICON_STATUS, NULL,
+ COL_NAME, name,
+ COL_IS_GROUP, TRUE,
+ COL_IS_ACTIVE, FALSE,
+ COL_IS_SEPARATOR, FALSE,
+ -1);
+
+ if (iter_group_to_set) {
+ *iter_group_to_set = iter_group;
+ }
+
+ gtk_tree_store_append (GTK_TREE_STORE (store),
+ &iter_separator,
+ &iter_group);
+ gtk_tree_store_set (GTK_TREE_STORE (store), &iter_separator,
+ COL_IS_SEPARATOR, TRUE,
+ -1);
+
+ if (iter_separator_to_set) {
+ *iter_separator_to_set = iter_separator;
+ }
+ } else {
+ if (created) {
+ *created = FALSE;
+ }
+
+ if (iter_group_to_set) {
+ *iter_group_to_set = fg.iter;
+ }
+
+ iter_separator = fg.iter;
+
+ if (gtk_tree_model_iter_next (model, &iter_separator)) {
+ gboolean is_separator;
+
+ gtk_tree_model_get (model, &iter_separator,
+ COL_IS_SEPARATOR, &is_separator,
+ -1);
+
+ if (is_separator && iter_separator_to_set) {
+ *iter_separator_to_set = iter_separator;
+ }
+ }
+ }
+}
+
+static gint
+contact_list_store_state_sort_func (GtkTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b,
+ gpointer user_data)
+{
+ gint ret_val = 0;
+ gchar *name_a, *name_b;
+ gboolean is_separator_a, is_separator_b;
+ GossipContact *contact_a, *contact_b;
+ GossipPresence *presence_a, *presence_b;
+ McPresence state_a, state_b;
+
+ gtk_tree_model_get (model, iter_a,
+ COL_NAME, &name_a,
+ COL_CONTACT, &contact_a,
+ COL_IS_SEPARATOR, &is_separator_a,
+ -1);
+ gtk_tree_model_get (model, iter_b,
+ COL_NAME, &name_b,
+ COL_CONTACT, &contact_b,
+ COL_IS_SEPARATOR, &is_separator_b,
+ -1);
+
+ /* Separator or group? */
+ if (is_separator_a || is_separator_b) {
+ if (is_separator_a) {
+ ret_val = -1;
+ } else if (is_separator_b) {
+ ret_val = 1;
+ }
+ } else if (!contact_a && contact_b) {
+ ret_val = 1;
+ } else if (contact_a && !contact_b) {
+ ret_val = -1;
+ } else if (!contact_a && !contact_b) {
+ /* Handle groups */
+ ret_val = g_utf8_collate (name_a, name_b);
+ }
+
+ if (ret_val) {
+ goto free_and_out;
+ }
+
+ /* If we managed to get this far, we can start looking at
+ * the presences.
+ */
+ presence_a = gossip_contact_get_presence (GOSSIP_CONTACT (contact_a));
+ presence_b = gossip_contact_get_presence (GOSSIP_CONTACT (contact_b));
+
+ if (!presence_a && presence_b) {
+ ret_val = 1;
+ } else if (presence_a && !presence_b) {
+ ret_val = -1;
+ } else if (!presence_a && !presence_b) {
+ /* Both offline, sort by name */
+ ret_val = g_utf8_collate (name_a, name_b);
+ } else {
+ state_a = gossip_presence_get_state (presence_a);
+ state_b = gossip_presence_get_state (presence_b);
+
+ if (state_a < state_b) {
+ ret_val = -1;
+ } else if (state_a > state_b) {
+ ret_val = 1;
+ } else {
+ /* Fallback: compare by name */
+ ret_val = g_utf8_collate (name_a, name_b);
+ }
+ }
+
+free_and_out:
+ g_free (name_a);
+ g_free (name_b);
+
+ if (contact_a) {
+ g_object_unref (contact_a);
+ }
+
+ if (contact_b) {
+ g_object_unref (contact_b);
+ }
+
+ return ret_val;
+}
+
+static gint
+contact_list_store_name_sort_func (GtkTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b,
+ gpointer user_data)
+{
+ gchar *name_a, *name_b;
+ GossipContact *contact_a, *contact_b;
+ gboolean is_separator_a, is_separator_b;
+ gint ret_val;
+
+ gtk_tree_model_get (model, iter_a,
+ COL_NAME, &name_a,
+ COL_CONTACT, &contact_a,
+ COL_IS_SEPARATOR, &is_separator_a,
+ -1);
+ gtk_tree_model_get (model, iter_b,
+ COL_NAME, &name_b,
+ COL_CONTACT, &contact_b,
+ COL_IS_SEPARATOR, &is_separator_b,
+ -1);
+
+ /* If contact is NULL it means it's a group. */
+
+ if (is_separator_a || is_separator_b) {
+ if (is_separator_a) {
+ ret_val = -1;
+ } else if (is_separator_b) {
+ ret_val = 1;
+ }
+ } else if (!contact_a && contact_b) {
+ ret_val = 1;
+ } else if (contact_a && !contact_b) {
+ ret_val = -1;
+ } else {
+ ret_val = g_utf8_collate (name_a, name_b);
+ }
+
+ g_free (name_a);
+ g_free (name_b);
+
+ if (contact_a) {
+ g_object_unref (contact_a);
+ }
+
+ if (contact_b) {
+ g_object_unref (contact_b);
+ }
+
+ return ret_val;
+}
+
+static gboolean
+contact_list_store_find_contact_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ FindContact *fc)
+{
+ GossipContact *contact;
+
+ gtk_tree_model_get (model, iter,
+ COL_CONTACT, &contact,
+ -1);
+
+ if (!contact) {
+ return FALSE;
+ }
+
+ if (gossip_contact_equal (contact, fc->contact)) {
+ fc->found = TRUE;
+ fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
+ }
+ g_object_unref (contact);
+
+ return FALSE;
+}
+
+static GList *
+contact_list_store_find_contact (GossipContactListStore *store,
+ GossipContact *contact)
+{
+ GossipContactListStorePriv *priv;
+ GtkTreeModel *model;
+ GList *l = NULL;
+ FindContact fc;
+
+ priv = GET_PRIV (store);
+
+ memset (&fc, 0, sizeof (fc));
+
+ fc.contact = contact;
+
+ model = GTK_TREE_MODEL (store);
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc) contact_list_store_find_contact_foreach,
+ &fc);
+
+ if (fc.found) {
+ l = fc.iters;
+ }
+
+ return l;
+}
+
+static gboolean
+contact_list_store_update_list_mode_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GossipContactListStore *store)
+{
+ GossipContactListStorePriv *priv;
+ gboolean show_avatar = FALSE;
+
+ priv = GET_PRIV (store);
+
+ if (priv->show_avatars && !priv->is_compact) {
+ show_avatar = TRUE;
+ }
+
+ gtk_tree_store_set (GTK_TREE_STORE (store), iter,
+ COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
+ COL_STATUS_VISIBLE, !priv->is_compact,
+ -1);
+
+ return FALSE;
+}
+
diff --git a/trunk/libempathy-gtk/gossip-contact-list-store.h b/trunk/libempathy-gtk/gossip-contact-list-store.h
new file mode 100644
index 000000000..b2131bcab
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-contact-list-store.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __GOSSIP_CONTACT_LIST_STORE_H__
+#define __GOSSIP_CONTACT_LIST_STORE_H__
+
+#include <gtk/gtktreestore.h>
+
+#include <libempathy/empathy-contact-list.h>
+#include <libempathy/gossip-contact.h>
+
+G_BEGIN_DECLS
+
+/*
+ * GossipContactListStoreSort
+ */
+#define GOSSIP_TYPE_CONTACT_LIST_STORE_SORT (gossip_contact_list_store_sort_get_type ())
+
+typedef enum {
+ GOSSIP_CONTACT_LIST_STORE_SORT_STATE,
+ GOSSIP_CONTACT_LIST_STORE_SORT_NAME
+} GossipContactListStoreSort;
+
+GType gossip_contact_list_store_sort_get_type (void) G_GNUC_CONST;
+
+/*
+ * GossipContactListStore
+ */
+#define GOSSIP_TYPE_CONTACT_LIST_STORE (gossip_contact_list_store_get_type ())
+#define GOSSIP_CONTACT_LIST_STORE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CONTACT_LIST_STORE, GossipContactListStore))
+#define GOSSIP_CONTACT_LIST_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GOSSIP_TYPE_CONTACT_LIST_STORE, GossipContactListStoreClass))
+#define GOSSIP_IS_CONTACT_LIST_STORE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CONTACT_LIST_STORE))
+#define GOSSIP_IS_CONTACT_LIST_STORE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CONTACT_LIST_STORE))
+#define GOSSIP_CONTACT_LIST_STORE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CONTACT_LIST_STORE, GossipContactListStoreClass))
+
+typedef struct _GossipContactListStore GossipContactListStore;
+typedef struct _GossipContactListStoreClass GossipContactListStoreClass;
+typedef struct _GossipContactListStorePriv GossipContactListStorePriv;
+
+enum {
+ COL_ICON_STATUS,
+ COL_PIXBUF_AVATAR,
+ COL_PIXBUF_AVATAR_VISIBLE,
+ COL_NAME,
+ COL_STATUS,
+ COL_STATUS_VISIBLE,
+ COL_CONTACT,
+ COL_IS_GROUP,
+ COL_IS_ACTIVE,
+ COL_IS_ONLINE,
+ COL_IS_SEPARATOR,
+ COL_COUNT
+} GossipContactListStoreCol;
+
+struct _GossipContactListStore {
+ GtkTreeStore parent;
+};
+
+struct _GossipContactListStoreClass {
+ GtkTreeStoreClass parent_class;
+};
+typedef GList * (*GossipContactGroupsFunc) (GossipContact *contact,
+ gpointer user_data);
+
+GType gossip_contact_list_store_get_type (void) G_GNUC_CONST;
+GossipContactListStore * gossip_contact_list_store_new (EmpathyContactList *list_iface);
+EmpathyContactList * gossip_contact_list_store_get_list_iface (GossipContactListStore *store);
+gboolean gossip_contact_list_store_get_show_offline (GossipContactListStore *store);
+void gossip_contact_list_store_set_show_offline (GossipContactListStore *store,
+ gboolean show_offline);
+gboolean gossip_contact_list_store_get_show_avatars (GossipContactListStore *store);
+void gossip_contact_list_store_set_show_avatars (GossipContactListStore *store,
+ gboolean show_avatars);
+gboolean gossip_contact_list_store_get_is_compact (GossipContactListStore *store);
+void gossip_contact_list_store_set_is_compact (GossipContactListStore *store,
+ gboolean is_compact);
+GossipContactListStoreSort gossip_contact_list_store_get_sort_criterium (GossipContactListStore *store);
+void gossip_contact_list_store_set_sort_criterium (GossipContactListStore *store,
+ GossipContactListStoreSort sort_criterium);
+gboolean gossip_contact_list_store_row_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data);
+gchar * gossip_contact_list_store_get_parent_group (GtkTreeModel *model,
+ GtkTreePath *path,
+ gboolean *path_is_group);
+gboolean gossip_contact_list_store_search_equal_func (GtkTreeModel *model,
+ gint column,
+ const gchar *key,
+ GtkTreeIter *iter,
+ gpointer search_data);
+void gossip_contact_list_store_set_contact_groups_func (GossipContactListStore*store,
+ GossipContactGroupsFunc func,
+ gpointer user_data);
+void gossip_contact_list_store_update_contact_groups (GossipContactListStore *store,
+ GossipContact *contact);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CONTACT_LIST_STORE_H__ */
+
diff --git a/trunk/libempathy-gtk/gossip-contact-list-view.c b/trunk/libempathy-gtk/gossip-contact-list-view.c
new file mode 100644
index 000000000..3cad7821d
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-contact-list-view.c
@@ -0,0 +1,1505 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <libmissioncontrol/mc-account.h>
+#include <libmissioncontrol/mission-control.h>
+
+#include <libempathy/empathy-contact-manager.h>
+#include <libempathy/empathy-contact-list.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-utils.h>
+#include <libempathy/empathy-marshal.h>
+
+#include "gossip-contact-list-view.h"
+#include "gossip-contact-list-store.h"
+#include "empathy-images.h"
+#include "gossip-contact-groups.h"
+#include "gossip-cell-renderer-expander.h"
+#include "gossip-cell-renderer-text.h"
+#include "gossip-ui-utils.h"
+//#include "gossip-chat-invite.h"
+//#include "gossip-contact-info-dialog.h"
+//#include "gossip-edit-contact-dialog.h"
+//#include "gossip-ft-window.h"
+//#include "gossip-log-window.h"
+
+#define DEBUG_DOMAIN "ContactListView"
+
+/* Flashing delay for icons (milliseconds). */
+#define FLASH_TIMEOUT 500
+
+/* Active users are those which have recently changed state
+ * (e.g. online, offline or from normal to a busy state).
+ */
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST_VIEW, GossipContactListViewPriv))
+
+struct _GossipContactListViewPriv {
+ GossipContactListStore *store;
+ GtkUIManager *ui;
+ GtkTreeRowReference *drag_row;
+ GtkTreeModel *filter;
+ gchar *filter_text;
+
+ GossipContactListViewDragReceivedFunc drag_received;
+ gpointer drag_received_data;
+};
+
+typedef struct {
+ GossipContactListView *view;
+ GtkTreePath *path;
+ guint timeout_id;
+} DragMotionData;
+
+typedef struct {
+ GossipContactListView *view;
+ GossipContact *contact;
+ gboolean remove;
+} ShowActiveData;
+
+static void gossip_contact_list_view_class_init (GossipContactListViewClass *klass);
+static void gossip_contact_list_view_init (GossipContactListView *list);
+static void contact_list_view_finalize (GObject *object);
+static void contact_list_view_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void contact_list_view_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void contact_list_view_setup (GossipContactListView *view);
+static void contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GossipContactListView *view);
+static void contact_list_view_contact_received (GossipContactListView *view,
+ GossipContact *contact,
+ GdkDragAction action,
+ const gchar *old_group,
+ const gchar *new_group);
+static void contact_list_view_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ gpointer user_data);
+static gboolean contact_list_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time,
+ gpointer data);
+static gboolean contact_list_view_drag_motion_cb (DragMotionData *data);
+static void contact_list_view_drag_begin (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data);
+static void contact_list_view_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ gpointer user_data);
+static void contact_list_view_drag_end (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data);
+static void contact_list_view_cell_set_background (GossipContactListView *view,
+ GtkCellRenderer *cell,
+ gboolean is_group,
+ gboolean is_active);
+static void contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactListView *view);
+static void contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactListView *view);
+static void contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactListView *view);
+static void contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactListView *view);
+static GtkWidget * contact_list_view_get_contact_menu (GossipContactListView *view,
+ gboolean can_send_file,
+ gboolean can_show_log);
+static gboolean contact_list_view_button_press_event_cb (GossipContactListView *view,
+ GdkEventButton *event,
+ gpointer user_data);
+static void contact_list_view_row_activated_cb (GossipContactListView *view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *col,
+ gpointer user_data);
+static void contact_list_view_row_expand_or_collapse_cb (GossipContactListView *view,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gpointer user_data);
+static gboolean contact_list_view_filter_show_contact (GossipContact *contact,
+ const gchar *filter);
+static gboolean contact_list_view_filter_show_group (GossipContactListView *view,
+ const gchar *group,
+ const gchar *filter);
+static gboolean contact_list_view_filter_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactListView *view);
+static void contact_list_view_action_cb (GtkAction *action,
+ GossipContactListView *view);
+static void contact_list_view_action_activated (GossipContactListView *view,
+ GossipContact *contact);
+
+enum {
+ PROP_0,
+ PROP_FILTER,
+};
+
+static const GtkActionEntry entries[] = {
+ { "ContactMenu", NULL,
+ N_("_Contact"), NULL, NULL,
+ NULL
+ },
+ { "GroupMenu", NULL,
+ N_("_Group"),NULL, NULL,
+ NULL
+ },
+ { "Chat", EMPATHY_IMAGE_MESSAGE,
+ N_("_Chat"), NULL, N_("Chat with contact"),
+ G_CALLBACK (contact_list_view_action_cb)
+ },
+ { "Information", EMPATHY_IMAGE_CONTACT_INFORMATION,
+ N_("Infor_mation"), "<control>I", N_("View contact information"),
+ G_CALLBACK (contact_list_view_action_cb)
+ },
+ { "Rename", NULL,
+ N_("Re_name"), NULL, N_("Rename"),
+ G_CALLBACK (contact_list_view_action_cb)
+ },
+ { "Edit", GTK_STOCK_EDIT,
+ N_("_Edit"), NULL, N_("Edit the groups and name for this contact"),
+ G_CALLBACK (contact_list_view_action_cb)
+ },
+ { "Remove", GTK_STOCK_REMOVE,
+ N_("_Remove"), NULL, N_("Remove contact"),
+ G_CALLBACK (contact_list_view_action_cb)
+ },
+ { "Invite", EMPATHY_IMAGE_GROUP_MESSAGE,
+ N_("_Invite to Chat Room"), NULL, N_("Invite to a currently open chat room"),
+ G_CALLBACK (contact_list_view_action_cb)
+ },
+ { "SendFile", NULL,
+ N_("_Send File..."), NULL, N_("Send a file"),
+ G_CALLBACK (contact_list_view_action_cb)
+ },
+ { "Log", GTK_STOCK_JUSTIFY_LEFT,
+ N_("_View Previous Conversations"), NULL, N_("View previous conversations with this contact"),
+ G_CALLBACK (contact_list_view_action_cb)
+ },
+};
+
+static guint n_entries = G_N_ELEMENTS (entries);
+
+static const gchar *ui_info =
+ "<ui>"
+ " <popup name='Contact'>"
+ " <menuitem action='Chat'/>"
+ " <menuitem action='Log'/>"
+ " <menuitem action='SendFile'/>"
+ " <separator/>"
+ " <menuitem action='Invite'/>"
+ " <separator/>"
+ " <menuitem action='Edit'/>"
+ " <menuitem action='Remove'/>"
+ " <separator/>"
+ " <menuitem action='Information'/>"
+ " </popup>"
+ " <popup name='Group'>"
+ " <menuitem action='Rename'/>"
+ " </popup>"
+ "</ui>";
+
+enum DndDragType {
+ DND_DRAG_TYPE_CONTACT_ID,
+ DND_DRAG_TYPE_URL,
+ DND_DRAG_TYPE_STRING,
+};
+
+static const GtkTargetEntry drag_types_dest[] = {
+ { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
+ { "text/uri-list", 0, DND_DRAG_TYPE_URL },
+ { "text/plain", 0, DND_DRAG_TYPE_STRING },
+ { "STRING", 0, DND_DRAG_TYPE_STRING },
+};
+
+static const GtkTargetEntry drag_types_source[] = {
+ { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
+};
+
+static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
+static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
+
+enum {
+ DRAG_CONTACT_RECEIVED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (GossipContactListView, gossip_contact_list_view, GTK_TYPE_TREE_VIEW);
+
+static void
+gossip_contact_list_view_class_init (GossipContactListViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = contact_list_view_finalize;
+ object_class->get_property = contact_list_view_get_property;
+ object_class->set_property = contact_list_view_set_property;
+
+ signals[DRAG_CONTACT_RECEIVED] =
+ g_signal_new ("drag-contact-received",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ empathy_marshal_VOID__OBJECT_STRING_STRING,
+ G_TYPE_NONE,
+ 3, GOSSIP_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
+
+ g_object_class_install_property (object_class,
+ PROP_FILTER,
+ g_param_spec_string ("filter",
+ "Filter",
+ "The text to use to filter the contact list",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (GossipContactListViewPriv));
+}
+
+static void
+gossip_contact_list_view_init (GossipContactListView *view)
+{
+ GossipContactListViewPriv *priv;
+ GtkActionGroup *action_group;
+ GError *error = NULL;
+
+ priv = GET_PRIV (view);
+
+ /* Get saved group states. */
+ gossip_contact_groups_get_all ();
+
+ /* Set up UI Manager */
+ priv->ui = gtk_ui_manager_new ();
+
+ action_group = gtk_action_group_new ("Actions");
+ gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
+ gtk_action_group_add_actions (action_group, entries, n_entries, view);
+ gtk_ui_manager_insert_action_group (priv->ui, action_group, 0);
+
+ if (!gtk_ui_manager_add_ui_from_string (priv->ui, ui_info, -1, &error)) {
+ g_warning ("Could not build contact menus from string:'%s'", error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (action_group);
+
+ gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
+ gossip_contact_list_store_row_separator_func,
+ NULL, NULL);
+
+ /* Connect to tree view signals rather than override. */
+ g_signal_connect (view,
+ "button-press-event",
+ G_CALLBACK (contact_list_view_button_press_event_cb),
+ NULL);
+ g_signal_connect (view,
+ "row-activated",
+ G_CALLBACK (contact_list_view_row_activated_cb),
+ NULL);
+ g_signal_connect (view,
+ "row-expanded",
+ G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
+ GINT_TO_POINTER (TRUE));
+ g_signal_connect (view,
+ "row-collapsed",
+ G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
+ GINT_TO_POINTER (FALSE));
+}
+
+static void
+contact_list_view_finalize (GObject *object)
+{
+ GossipContactListViewPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ if (priv->ui) {
+ g_object_unref (priv->ui);
+ }
+ if (priv->store) {
+ g_object_unref (priv->store);
+ }
+ if (priv->filter) {
+ g_object_unref (priv->filter);
+ }
+ g_free (priv->filter_text);
+
+ G_OBJECT_CLASS (gossip_contact_list_view_parent_class)->finalize (object);
+}
+
+static void
+contact_list_view_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GossipContactListViewPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_FILTER:
+ g_value_set_string (value, priv->filter_text);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+contact_list_view_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GossipContactListViewPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_FILTER:
+ gossip_contact_list_view_set_filter (GOSSIP_CONTACT_LIST_VIEW (object),
+ g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+GossipContactListView *
+gossip_contact_list_view_new (GossipContactListStore *store)
+{
+ GossipContactListViewPriv *priv;
+ GossipContactListView *view;
+
+ view = g_object_new (GOSSIP_TYPE_CONTACT_LIST_VIEW, NULL);
+ priv = GET_PRIV (view);
+
+ priv->store = g_object_ref (store);
+ contact_list_view_setup (view);
+
+ return view;
+}
+
+GossipContact *
+gossip_contact_list_view_get_selected (GossipContactListView *view)
+{
+ GossipContactListViewPriv *priv;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GossipContact *contact;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view), NULL);
+
+ priv = GET_PRIV (view);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ return NULL;
+ }
+
+ gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
+
+ return contact;
+}
+
+gchar *
+gossip_contact_list_view_get_selected_group (GossipContactListView *view)
+{
+ GossipContactListViewPriv *priv;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gboolean is_group;
+ gchar *name;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view), NULL);
+
+ priv = GET_PRIV (view);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ return NULL;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ COL_IS_GROUP, &is_group,
+ COL_NAME, &name,
+ -1);
+
+ if (!is_group) {
+ g_free (name);
+ return NULL;
+ }
+
+ return name;
+}
+
+GtkWidget *
+gossip_contact_list_view_get_group_menu (GossipContactListView *view)
+{
+ GossipContactListViewPriv *priv;
+ GtkWidget *widget;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view), NULL);
+
+ priv = GET_PRIV (view);
+
+ widget = gtk_ui_manager_get_widget (priv->ui, "/Group");
+
+ return widget;
+}
+
+GtkWidget *
+gossip_contact_list_view_get_contact_menu (GossipContactListView *view,
+ GossipContact *contact)
+{
+ gboolean can_show_log;
+ gboolean can_send_file;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view), NULL);
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ can_show_log = FALSE; /* FIXME: gossip_log_exists_for_contact (contact); */
+ can_send_file = FALSE;
+
+ return contact_list_view_get_contact_menu (view,
+ can_send_file,
+ can_show_log);
+}
+
+void
+gossip_contact_list_view_set_filter (GossipContactListView *view,
+ const gchar *filter)
+{
+ GossipContactListViewPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view));
+
+ priv = GET_PRIV (view);
+
+ g_free (priv->filter_text);
+ if (filter) {
+ priv->filter_text = g_utf8_casefold (filter, -1);
+ } else {
+ priv->filter_text = NULL;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Refiltering with filter:'%s' (case folded)", filter);
+ gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
+}
+
+void
+gossip_contact_list_view_set_drag_received_func (GossipContactListView *view,
+ GossipContactListViewDragReceivedFunc func,
+ gpointer user_data)
+{
+ GossipContactListViewPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view));
+
+ priv = GET_PRIV (view);
+
+ if (func) {
+ priv->drag_received = func;
+ priv->drag_received_data = user_data;
+ } else {
+ priv->drag_received = NULL;
+ priv->drag_received_data = NULL;
+ }
+}
+
+static void
+contact_list_view_setup (GossipContactListView *view)
+{
+ GossipContactListViewPriv *priv;
+ GtkCellRenderer *cell;
+ GtkTreeViewColumn *col;
+ gint i;
+
+ priv = GET_PRIV (view);
+
+ /* Create filter */
+ priv->filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (priv->store), NULL);
+
+ gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter),
+ (GtkTreeModelFilterVisibleFunc)
+ contact_list_view_filter_func,
+ view, NULL);
+ gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
+ gossip_contact_list_store_search_equal_func,
+ view, NULL);
+ g_signal_connect (priv->filter, "row-has-child-toggled",
+ G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
+ view);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (view), priv->filter);
+
+
+ /* Setup view */
+ g_object_set (view,
+ "headers-visible", FALSE,
+ "reorderable", TRUE,
+ "show-expanders", FALSE,
+ NULL);
+
+ col = gtk_tree_view_column_new ();
+
+ /* State */
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (col, cell, FALSE);
+ gtk_tree_view_column_set_cell_data_func (
+ col, cell,
+ (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
+ view, NULL);
+
+ g_object_set (cell,
+ "xpad", 5,
+ "ypad", 1,
+ "visible", FALSE,
+ NULL);
+
+ /* Name */
+ cell = gossip_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (col, cell, TRUE);
+ gtk_tree_view_column_set_cell_data_func (
+ col, cell,
+ (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
+ view, NULL);
+
+ gtk_tree_view_column_add_attribute (col, cell,
+ "name", COL_NAME);
+ gtk_tree_view_column_add_attribute (col, cell,
+ "status", COL_STATUS);
+ gtk_tree_view_column_add_attribute (col, cell,
+ "is_group", COL_IS_GROUP);
+
+ /* Avatar */
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (col, cell, FALSE);
+ gtk_tree_view_column_set_cell_data_func (
+ col, cell,
+ (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
+ view, NULL);
+
+ g_object_set (cell,
+ "xpad", 0,
+ "ypad", 0,
+ "visible", FALSE,
+ "width", 32,
+ "height", 32,
+ NULL);
+
+ /* Expander */
+ cell = gossip_cell_renderer_expander_new ();
+ gtk_tree_view_column_pack_end (col, cell, FALSE);
+ gtk_tree_view_column_set_cell_data_func (
+ col, cell,
+ (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
+ view, NULL);
+
+ /* Actually add the column now we have added all cell renderers */
+ gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
+
+ /* Drag & Drop. */
+ for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
+ drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
+ FALSE);
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
+ drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
+ FALSE);
+ }
+
+ /* Note: We support the COPY action too, but need to make the
+ * MOVE action the default.
+ */
+ gtk_drag_source_set (GTK_WIDGET (view),
+ GDK_BUTTON1_MASK,
+ drag_types_source,
+ G_N_ELEMENTS (drag_types_source),
+ GDK_ACTION_MOVE);
+
+ gtk_drag_dest_set (GTK_WIDGET (view),
+ GTK_DEST_DEFAULT_ALL,
+ drag_types_dest,
+ G_N_ELEMENTS (drag_types_dest),
+ GDK_ACTION_MOVE | GDK_ACTION_LINK);
+
+ g_signal_connect (GTK_WIDGET (view),
+ "drag-data-received",
+ G_CALLBACK (contact_list_view_drag_data_received),
+ NULL);
+
+ /* FIXME: noticed but when you drag the row over the treeview
+ * fast, it seems to stop redrawing itself, if we don't
+ * connect this signal, all is fine.
+ */
+ g_signal_connect (view,
+ "drag-motion",
+ G_CALLBACK (contact_list_view_drag_motion),
+ NULL);
+
+ g_signal_connect (view,
+ "drag-begin",
+ G_CALLBACK (contact_list_view_drag_begin),
+ NULL);
+ g_signal_connect (view,
+ "drag-data-get",
+ G_CALLBACK (contact_list_view_drag_data_get),
+ NULL);
+ g_signal_connect (view,
+ "drag-end",
+ G_CALLBACK (contact_list_view_drag_end),
+ NULL);
+}
+
+static void
+contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GossipContactListView *view)
+{
+ gboolean is_group = FALSE;
+ gchar *name = NULL;
+
+ gtk_tree_model_get (model, iter,
+ COL_IS_GROUP, &is_group,
+ COL_NAME, &name,
+ -1);
+
+ if (!is_group || G_STR_EMPTY (name)) {
+ g_free (name);
+ return;
+ }
+
+ if (gossip_contact_group_get_expanded (name)) {
+ g_signal_handlers_block_by_func (view,
+ contact_list_view_row_expand_or_collapse_cb,
+ GINT_TO_POINTER (TRUE));
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
+ g_signal_handlers_unblock_by_func (view,
+ contact_list_view_row_expand_or_collapse_cb,
+ GINT_TO_POINTER (TRUE));
+ } else {
+ g_signal_handlers_block_by_func (view,
+ contact_list_view_row_expand_or_collapse_cb,
+ GINT_TO_POINTER (FALSE));
+ gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
+ g_signal_handlers_unblock_by_func (view,
+ contact_list_view_row_expand_or_collapse_cb,
+ GINT_TO_POINTER (FALSE));
+ }
+
+ g_free (name);
+}
+
+static void
+contact_list_view_contact_received (GossipContactListView *view,
+ GossipContact *contact,
+ GdkDragAction action,
+ const gchar *old_group,
+ const gchar *new_group)
+{
+ GossipContactListViewPriv *priv;
+ GList *groups, *l;
+ GList *new_groups_list = NULL;
+
+ priv = GET_PRIV (view);
+
+ groups = gossip_contact_get_groups (contact);
+ for (l = groups; l; l = l->next) {
+ gchar *str;
+
+ str = l->data;
+
+ if (action == GDK_ACTION_MOVE &&
+ old_group != NULL &&
+ strcmp (str, old_group) == 0) {
+ continue;
+ }
+
+ if (new_group && strcmp (str, new_group) == 0) {
+ /* Otherwise we set it twice */
+ continue;
+ }
+
+ new_groups_list = g_list_prepend (new_groups_list, g_strdup (str));
+ }
+
+ if (new_group) {
+ new_groups_list = g_list_prepend (new_groups_list, g_strdup (new_group));
+ }
+
+ gossip_contact_set_groups (contact, new_groups_list);
+}
+
+static void
+contact_list_view_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ gpointer user_data)
+{
+ GossipContactListViewPriv *priv;
+ EmpathyContactManager *manager;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeViewDropPosition position;
+ GossipContact *contact;
+ const gchar *id;
+ gchar *new_group = NULL;
+ gchar *old_group = NULL;
+ gboolean is_row;
+
+ priv = GET_PRIV (widget);
+
+ id = (const gchar*) selection->data;
+ gossip_debug (DEBUG_DOMAIN, "Received %s%s drag & drop contact from roster with id:'%s'",
+ context->action == GDK_ACTION_MOVE ? "move" : "",
+ context->action == GDK_ACTION_COPY ? "copy" : "",
+ id);
+
+ /* FIXME: This is ambigous, an id can come from multiple accounts */
+ manager = empathy_contact_manager_new ();
+ contact = empathy_contact_list_find (EMPATHY_CONTACT_LIST (manager), id);
+ g_object_unref (manager);
+
+ if (!contact) {
+ gossip_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
+ return;
+ }
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+
+ /* Get source group information. */
+ if (priv->drag_row) {
+ path = gtk_tree_row_reference_get_path (priv->drag_row);
+ if (path) {
+ old_group = gossip_contact_list_store_get_parent_group (model, path, NULL);
+ gtk_tree_path_free (path);
+ }
+ }
+
+ /* Get destination group information. */
+ is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
+ x,
+ y,
+ &path,
+ &position);
+
+ if (is_row) {
+ new_group = gossip_contact_list_store_get_parent_group (model, path, NULL);
+ gtk_tree_path_free (path);
+ }
+
+ gossip_debug (DEBUG_DOMAIN,
+ "contact '%s' dragged from '%s' to '%s'",
+ gossip_contact_get_name (contact),
+ old_group, new_group);
+
+ if (priv->drag_received) {
+ priv->drag_received (contact,
+ context->action,
+ old_group,
+ new_group,
+ priv->drag_received_data);
+ } else {
+ contact_list_view_contact_received (GOSSIP_CONTACT_LIST_VIEW (widget),
+ contact,
+ context->action,
+ old_group,
+ new_group);
+ }
+
+ g_free (old_group);
+ g_free (new_group);
+
+ gtk_drag_finish (context, TRUE, FALSE, GDK_CURRENT_TIME);
+}
+
+static gboolean
+contact_list_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time,
+ gpointer data)
+{
+ static DragMotionData *dm = NULL;
+ GtkTreePath *path;
+ gboolean is_row;
+ gboolean is_different = FALSE;
+ gboolean cleanup = TRUE;
+
+ is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+ x,
+ y,
+ &path,
+ NULL,
+ NULL,
+ NULL);
+
+ cleanup &= (!dm);
+
+ if (is_row) {
+ cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
+ is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
+ } else {
+ cleanup &= FALSE;
+ }
+
+ if (!is_different && !cleanup) {
+ return TRUE;
+ }
+
+ if (dm) {
+ gtk_tree_path_free (dm->path);
+ if (dm->timeout_id) {
+ g_source_remove (dm->timeout_id);
+ }
+
+ g_free (dm);
+
+ dm = NULL;
+ }
+
+ if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
+ dm = g_new0 (DragMotionData, 1);
+
+ dm->view = GOSSIP_CONTACT_LIST_VIEW (widget);
+ dm->path = gtk_tree_path_copy (path);
+
+ dm->timeout_id = g_timeout_add (
+ 1500,
+ (GSourceFunc) contact_list_view_drag_motion_cb,
+ dm);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+contact_list_view_drag_motion_cb (DragMotionData *data)
+{
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
+ data->path,
+ FALSE);
+
+ data->timeout_id = 0;
+
+ return FALSE;
+}
+
+static void
+contact_list_view_drag_begin (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data)
+{
+ GossipContactListViewPriv *priv;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ priv = GET_PRIV (widget);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ return;
+ }
+
+ path = gtk_tree_model_get_path (model, &iter);
+ priv->drag_row = gtk_tree_row_reference_new (model, path);
+ gtk_tree_path_free (path);
+}
+
+static void
+contact_list_view_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ gpointer user_data)
+{
+ GossipContactListViewPriv *priv;
+ GtkTreePath *src_path;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GossipContact *contact;
+ const gchar *id;
+
+ priv = GET_PRIV (widget);
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+ if (!priv->drag_row) {
+ return;
+ }
+
+ src_path = gtk_tree_row_reference_get_path (priv->drag_row);
+ if (!src_path) {
+ return;
+ }
+
+ if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
+ gtk_tree_path_free (src_path);
+ return;
+ }
+
+ gtk_tree_path_free (src_path);
+
+ contact = gossip_contact_list_view_get_selected (GOSSIP_CONTACT_LIST_VIEW (widget));
+ if (!contact) {
+ return;
+ }
+
+ id = gossip_contact_get_id (contact);
+ g_object_unref (contact);
+
+ switch (info) {
+ case DND_DRAG_TYPE_CONTACT_ID:
+ gtk_selection_data_set (selection, drag_atoms_source[info], 8,
+ (guchar*)id, strlen (id) + 1);
+ break;
+
+ default:
+ return;
+ }
+}
+
+static void
+contact_list_view_drag_end (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data)
+{
+ GossipContactListViewPriv *priv;
+
+ priv = GET_PRIV (widget);
+
+ if (priv->drag_row) {
+ gtk_tree_row_reference_free (priv->drag_row);
+ priv->drag_row = NULL;
+ }
+}
+
+static void
+contact_list_view_cell_set_background (GossipContactListView *view,
+ GtkCellRenderer *cell,
+ gboolean is_group,
+ gboolean is_active)
+{
+ GdkColor color;
+ GtkStyle *style;
+
+ style = gtk_widget_get_style (GTK_WIDGET (view));
+
+ if (!is_group) {
+ if (is_active) {
+ color = style->bg[GTK_STATE_SELECTED];
+
+ /* Here we take the current theme colour and add it to
+ * the colour for white and average the two. This
+ * gives a colour which is inline with the theme but
+ * slightly whiter.
+ */
+ color.red = (color.red + (style->white).red) / 2;
+ color.green = (color.green + (style->white).green) / 2;
+ color.blue = (color.blue + (style->white).blue) / 2;
+
+ g_object_set (cell,
+ "cell-background-gdk", &color,
+ NULL);
+ } else {
+ g_object_set (cell,
+ "cell-background-gdk", NULL,
+ NULL);
+ }
+ } else {
+ g_object_set (cell,
+ "cell-background-gdk", NULL,
+ NULL);
+ }
+}
+
+static void
+contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactListView *view)
+{
+ gchar *icon_name;
+ gboolean is_group;
+ gboolean is_active;
+
+ gtk_tree_model_get (model, iter,
+ COL_IS_GROUP, &is_group,
+ COL_IS_ACTIVE, &is_active,
+ COL_ICON_STATUS, &icon_name,
+ -1);
+
+ g_object_set (cell,
+ "visible", !is_group,
+ "icon-name", icon_name,
+ NULL);
+
+ g_free (icon_name);
+
+ contact_list_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static void
+contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactListView *view)
+{
+ GdkPixbuf *pixbuf;
+ gboolean show_avatar;
+ gboolean is_group;
+ gboolean is_active;
+
+ gtk_tree_model_get (model, iter,
+ COL_PIXBUF_AVATAR, &pixbuf,
+ COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
+ COL_IS_GROUP, &is_group,
+ COL_IS_ACTIVE, &is_active,
+ -1);
+
+ g_object_set (cell,
+ "visible", !is_group && show_avatar,
+ "pixbuf", pixbuf,
+ NULL);
+
+ if (pixbuf) {
+ g_object_unref (pixbuf);
+ }
+
+ contact_list_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static void
+contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactListView *view)
+{
+ gboolean is_group;
+ gboolean is_active;
+ gboolean show_status;
+
+ gtk_tree_model_get (model, iter,
+ COL_IS_GROUP, &is_group,
+ COL_IS_ACTIVE, &is_active,
+ COL_STATUS_VISIBLE, &show_status,
+ -1);
+
+ g_object_set (cell,
+ "show-status", show_status,
+ NULL);
+
+ contact_list_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static void
+contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactListView *view)
+{
+ gboolean is_group;
+ gboolean is_active;
+
+ gtk_tree_model_get (model, iter,
+ COL_IS_GROUP, &is_group,
+ COL_IS_ACTIVE, &is_active,
+ -1);
+
+ if (gtk_tree_model_iter_has_child (model, iter)) {
+ GtkTreePath *path;
+ gboolean row_expanded;
+
+ path = gtk_tree_model_get_path (model, iter);
+ row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
+ gtk_tree_path_free (path);
+
+ g_object_set (cell,
+ "visible", TRUE,
+ "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
+ NULL);
+ } else {
+ g_object_set (cell, "visible", FALSE, NULL);
+ }
+
+ contact_list_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static GtkWidget *
+contact_list_view_get_contact_menu (GossipContactListView *view,
+ gboolean can_send_file,
+ gboolean can_show_log)
+{
+ GossipContactListViewPriv *priv;
+ GtkAction *action;
+ GtkWidget *widget;
+
+ priv = GET_PRIV (view);
+
+ /* Sort out sensitive items */
+ action = gtk_ui_manager_get_action (priv->ui, "/Contact/Log");
+ gtk_action_set_sensitive (action, can_show_log);
+
+ action = gtk_ui_manager_get_action (priv->ui, "/Contact/SendFile");
+ gtk_action_set_visible (action, can_send_file);
+
+ widget = gtk_ui_manager_get_widget (priv->ui, "/Contact");
+
+ return widget;
+}
+
+static gboolean
+contact_list_view_button_press_event_cb (GossipContactListView *view,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ GossipContactListViewPriv *priv;
+ GossipContact *contact;
+ GtkTreePath *path;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean row_exists;
+ GtkWidget *menu;
+
+ if (event->button != 3) {
+ return FALSE;
+ }
+
+ priv = GET_PRIV (view);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+
+ row_exists = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view),
+ event->x, event->y,
+ &path,
+ NULL, NULL, NULL);
+ if (!row_exists) {
+ return FALSE;
+ }
+
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, path);
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_path_free (path);
+
+ gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
+
+ if (contact) {
+ menu = gossip_contact_list_view_get_contact_menu (view, contact);
+ g_object_unref (contact);
+ } else {
+ menu = gossip_contact_list_view_get_group_menu (view);
+ }
+
+ if (!menu) {
+ return FALSE;
+ }
+
+ gtk_widget_show (menu);
+
+ gtk_menu_popup (GTK_MENU (menu),
+ NULL, NULL, NULL, NULL,
+ event->button, event->time);
+
+ return TRUE;
+}
+
+static void
+contact_list_view_row_activated_cb (GossipContactListView *view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *col,
+ gpointer user_data)
+{
+ GossipContact *contact;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
+
+ if (contact) {
+ contact_list_view_action_activated (view, contact);
+ g_object_unref (contact);
+ }
+}
+
+static void
+contact_list_view_row_expand_or_collapse_cb (GossipContactListView *view,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ GtkTreeModel *model;
+ gchar *name;
+ gboolean expanded;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+
+ gtk_tree_model_get (model, iter,
+ COL_NAME, &name,
+ -1);
+
+ expanded = GPOINTER_TO_INT (user_data);
+ gossip_contact_group_set_expanded (name, expanded);
+
+ g_free (name);
+}
+
+static gboolean
+contact_list_view_filter_show_contact (GossipContact *contact,
+ const gchar *filter)
+{
+ gchar *str;
+ gboolean visible;
+
+ /* Check contact id */
+ str = g_utf8_casefold (gossip_contact_get_id (contact), -1);
+ visible = G_STR_EMPTY (str) || strstr (str, filter);
+ g_free (str);
+
+ if (visible) {
+ return TRUE;
+ }
+
+ /* Check contact name */
+ str = g_utf8_casefold (gossip_contact_get_name (contact), -1);
+ visible = G_STR_EMPTY (str) || strstr (str, filter);
+ g_free (str);
+
+ return visible;
+}
+
+static gboolean
+contact_list_view_filter_show_group (GossipContactListView *view,
+ const gchar *group,
+ const gchar *filter)
+{
+ GossipContactListViewPriv *priv;
+ EmpathyContactList *list;
+ GList *contacts, *l;
+ gchar *str;
+ gboolean show_group = FALSE;
+
+ priv = GET_PRIV (view);
+
+ str = g_utf8_casefold (group, -1);
+ if (!str) {
+ return FALSE;
+ }
+
+ /* If the filter is the partially the group name, we show the
+ * whole group.
+ */
+ if (strstr (str, filter)) {
+ g_free (str);
+ return TRUE;
+ }
+
+ /* At this point, we need to check in advance if this
+ * group should be shown because a contact we want to
+ * show exists in it.
+ */
+ list = gossip_contact_list_store_get_list_iface (priv->store);
+ contacts = empathy_contact_list_get_contacts (list);
+ for (l = contacts; l && !show_group; l = l->next) {
+ if (!gossip_contact_is_in_group (l->data, group)) {
+ continue;
+ }
+
+ if (contact_list_view_filter_show_contact (l->data, filter)) {
+ show_group = TRUE;
+ }
+ }
+ g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
+ g_list_free (contacts);
+ g_free (str);
+
+ return show_group;
+}
+
+static gboolean
+contact_list_view_filter_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactListView *view)
+{
+ GossipContactListViewPriv *priv;
+ gboolean is_group;
+ gboolean is_separator;
+ gboolean visible = TRUE;
+
+ priv = GET_PRIV (view);
+
+ if (G_STR_EMPTY (priv->filter_text)) {
+ return TRUE;
+ }
+
+ /* Check to see if iter matches any group names */
+ gtk_tree_model_get (model, iter,
+ COL_IS_GROUP, &is_group,
+ COL_IS_SEPARATOR, &is_separator,
+ -1);
+
+ if (is_group) {
+ gchar *name;
+
+ gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
+ visible &= contact_list_view_filter_show_group (view,
+ name,
+ priv->filter_text);
+ g_free (name);
+ } else if (is_separator) {
+ /* Do nothing here */
+ } else {
+ GossipContact *contact;
+
+ /* Check contact id */
+ gtk_tree_model_get (model, iter, COL_CONTACT, &contact, -1);
+ visible &= contact_list_view_filter_show_contact (contact,
+ priv->filter_text);
+ g_object_unref (contact);
+ }
+
+ return visible;
+}
+
+static void
+contact_list_view_action_cb (GtkAction *action,
+ GossipContactListView *view)
+{
+ GossipContact *contact;
+ const gchar *name;
+ gchar *group;
+
+ name = gtk_action_get_name (action);
+ if (!name) {
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Action:'%s' activated", name);
+
+ contact = gossip_contact_list_view_get_selected (view);
+ group = gossip_contact_list_view_get_selected_group (view);
+
+ if (contact && strcmp (name, "Chat") == 0) {
+ contact_list_view_action_activated (view, contact);
+ }
+ else if (contact && strcmp (name, "Information") == 0) {
+ }
+ else if (contact && strcmp (name, "Edit") == 0) {
+ }
+ else if (contact && strcmp (name, "Remove") == 0) {
+ }
+ else if (contact && strcmp (name, "Invite") == 0) {
+ }
+ else if (contact && strcmp (name, "SendFile") == 0) {
+ }
+ else if (contact && strcmp (name, "Log") == 0) {
+ }
+ else if (group && strcmp (name, "Rename") == 0) {
+ }
+
+ g_free (group);
+ if (contact) {
+ g_object_unref (contact);
+ }
+}
+
+static void
+contact_list_view_action_activated (GossipContactListView *view,
+ GossipContact *contact)
+{
+ MissionControl *mc;
+
+ mc = gossip_mission_control_new ();
+ mission_control_request_channel (mc,
+ gossip_contact_get_account (contact),
+ TP_IFACE_CHANNEL_TYPE_TEXT,
+ gossip_contact_get_handle (contact),
+ TP_HANDLE_TYPE_CONTACT,
+ NULL, NULL);
+ g_object_unref (mc);
+}
+
diff --git a/trunk/libempathy-gtk/gossip-contact-list-view.h b/trunk/libempathy-gtk/gossip-contact-list-view.h
new file mode 100644
index 000000000..20262f8ad
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-contact-list-view.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __GOSSIP_CONTACT_LIST_VIEW_H__
+#define __GOSSIP_CONTACT_LIST_VIEW_H__
+
+#include <gtk/gtktreeview.h>
+
+#include <libempathy/gossip-contact.h>
+
+#include "gossip-contact-list-store.h"
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CONTACT_LIST_VIEW (gossip_contact_list_view_get_type ())
+#define GOSSIP_CONTACT_LIST_VIEW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CONTACT_LIST_VIEW, GossipContactListView))
+#define GOSSIP_CONTACT_LIST_VIEW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GOSSIP_TYPE_CONTACT_LIST_VIEW, GossipContactListViewClass))
+#define GOSSIP_IS_CONTACT_LIST_VIEW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CONTACT_LIST_VIEW))
+#define GOSSIP_IS_CONTACT_LIST_VIEW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CONTACT_LIST_VIEW))
+#define GOSSIP_CONTACT_LIST_VIEW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CONTACT_LIST_VIEW, GossipContactListViewClass))
+
+typedef struct _GossipContactListView GossipContactListView;
+typedef struct _GossipContactListViewClass GossipContactListViewClass;
+typedef struct _GossipContactListViewPriv GossipContactListViewPriv;
+
+struct _GossipContactListView {
+ GtkTreeView parent;
+};
+
+struct _GossipContactListViewClass {
+ GtkTreeViewClass parent_class;
+};
+
+typedef void (*GossipContactListViewDragReceivedFunc) (GossipContact *contact,
+ GdkDragAction action,
+ const gchar *old_group,
+ const gchar *new_group,
+ gpointer user_data);
+
+GType gossip_contact_list_view_get_type (void) G_GNUC_CONST;
+GossipContactListView *gossip_contact_list_view_new (GossipContactListStore *store);
+GossipContact * gossip_contact_list_view_get_selected (GossipContactListView *view);
+gchar * gossip_contact_list_view_get_selected_group (GossipContactListView *view);
+GtkWidget * gossip_contact_list_view_get_contact_menu (GossipContactListView *view,
+ GossipContact *contact);
+GtkWidget * gossip_contact_list_view_get_group_menu (GossipContactListView *view);
+void gossip_contact_list_view_set_filter (GossipContactListView *view,
+ const gchar *filter);
+void gossip_contact_list_view_set_drag_received_func (GossipContactListView *view,
+ GossipContactListViewDragReceivedFunc func,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CONTACT_LIST_VIEW_H__ */
+
diff --git a/trunk/libempathy-gtk/gossip-geometry.c b/trunk/libempathy-gtk/gossip-geometry.c
new file mode 100644
index 000000000..ad0bd8dd1
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-geometry.c
@@ -0,0 +1,186 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include "config.h"
+
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <gdk/gdk.h>
+
+#include <libempathy/gossip-debug.h>
+
+#include "gossip-geometry.h"
+
+#define DEBUG_DOMAIN "Geometry"
+
+#define GEOMETRY_DIR_CREATE_MODE (S_IRUSR | S_IWUSR | S_IXUSR)
+#define GEOMETRY_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
+
+#define GEOMETRY_KEY_FILENAME "geometry.ini"
+#define GEOMETRY_FORMAT "%d,%d,%d,%d"
+#define GEOMETRY_GROUP_NAME "geometry"
+
+static gchar *geometry_get_filename (void);
+
+static gchar *
+geometry_get_filename (void)
+{
+ gchar *dir;
+ gchar *filename;
+
+ dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
+ if (!g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
+ gossip_debug (DEBUG_DOMAIN, "Creating directory:'%s'", dir);
+ g_mkdir_with_parents (dir, GEOMETRY_DIR_CREATE_MODE);
+ }
+
+ filename = g_build_filename (dir, GEOMETRY_KEY_FILENAME, NULL);
+ g_free (dir);
+
+ return filename;
+}
+
+void
+gossip_geometry_save (const gchar *name,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ GError *error = NULL;
+ GKeyFile *key_file;
+ gchar *filename;
+ GdkScreen *screen;
+ gint max_width;
+ gint max_height;
+ gchar *content;
+ gsize length;
+ gchar *str;
+
+ gossip_debug (DEBUG_DOMAIN, "Saving window geometry: x:%d, y:%d, w:%d, h:%d\n",
+ x, y, w, h);
+
+ screen = gdk_screen_get_default ();
+ max_width = gdk_screen_get_width (screen);
+ max_height = gdk_screen_get_height (screen);
+
+ w = CLAMP (w, 100, max_width);
+ h = CLAMP (h, 100, max_height);
+
+ x = CLAMP (x, 0, max_width - w);
+ y = CLAMP (y, 0, max_height - h);
+
+ str = g_strdup_printf (GEOMETRY_FORMAT, x, y, w, h);
+
+ key_file = g_key_file_new ();
+
+ filename = geometry_get_filename ();
+
+ g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL);
+ g_key_file_set_string (key_file, GEOMETRY_GROUP_NAME, name, str);
+
+ g_free (str);
+
+ content = g_key_file_to_data (key_file, &length, NULL);
+ if (!g_file_set_contents (filename, content, length, &error)) {
+ g_warning ("Couldn't save window geometry, error:%d->'%s'",
+ error->code, error->message);
+ g_error_free (error);
+ }
+
+ g_free (content);
+ g_free (filename);
+ g_key_file_free (key_file);
+}
+
+void
+gossip_geometry_load (const gchar *name,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h)
+{
+ GKeyFile *key_file;
+ gchar *filename;
+ gchar *str = NULL;
+
+ if (x) {
+ *x = -1;
+ }
+
+ if (y) {
+ *y = -1;
+ }
+
+ if (w) {
+ *w = -1;
+ }
+
+ if (h) {
+ *h = -1;
+ }
+
+ key_file = g_key_file_new ();
+
+ filename = geometry_get_filename ();
+
+ if (g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL)) {
+ str = g_key_file_get_string (key_file, GEOMETRY_GROUP_NAME, name, NULL);
+ }
+
+ if (str) {
+ gint tmp_x, tmp_y, tmp_w, tmp_h;
+
+ sscanf (str, GEOMETRY_FORMAT, &tmp_x, &tmp_y, &tmp_w, &tmp_h);
+
+ if (x) {
+ *x = tmp_x;
+ }
+
+ if (y) {
+ *y = tmp_y;
+ }
+
+ if (w) {
+ *w = tmp_w;
+ }
+
+ if (h) {
+ *h = tmp_h;
+ }
+
+ g_free (str);
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Loading window geometry: x:%d, y:%d, w:%d, h:%d\n",
+ x ? *x : -1,
+ y ? *y : -1,
+ w ? *w : -1,
+ h ? *h : -1);
+
+ g_free (filename);
+ g_key_file_free (key_file);
+}
+
diff --git a/trunk/libempathy-gtk/gossip-geometry.h b/trunk/libempathy-gtk/gossip-geometry.h
new file mode 100644
index 000000000..24534fb38
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-geometry.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __GOSSIP_GEOMETRY_H__
+#define __GOSSIP_GEOMETRY_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void gossip_geometry_save (const gchar *name,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+void gossip_geometry_load (const gchar *name,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_GEOMETRY_H__ */
diff --git a/trunk/libempathy-gtk/gossip-group-chat.c b/trunk/libempathy-gtk/gossip-group-chat.c
new file mode 100644
index 000000000..3dc4022bf
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-group-chat.c
@@ -0,0 +1,632 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+
+#include <libempathy/empathy-tp-chat.h>
+#include <libempathy/empathy-tp-chatroom.h>
+#include <libempathy/gossip-contact.h>
+#include <libempathy/gossip-utils.h>
+#include <libempathy/gossip-debug.h>
+
+#include "gossip-group-chat.h"
+#include "gossip-chat.h"
+#include "gossip-chat-view.h"
+#include "gossip-contact-list-store.h"
+#include "gossip-contact-list-view.h"
+//#include "gossip-chat-invite.h"
+//#include "gossip-sound.h"
+#include "empathy-images.h"
+#include "gossip-ui-utils.h"
+
+#define DEBUG_DOMAIN "GroupChat"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_GROUP_CHAT, GossipGroupChatPriv))
+
+struct _GossipGroupChatPriv {
+ GossipContactListStore *store;
+ GossipContactListView *view;
+ EmpathyTpChatroom *tp_chat;
+
+ GtkWidget *widget;
+ GtkWidget *hpaned;
+ GtkWidget *vbox_left;
+ GtkWidget *scrolled_window_chat;
+ GtkWidget *scrolled_window_input;
+ GtkWidget *scrolled_window_contacts;
+ GtkWidget *hbox_topic;
+ GtkWidget *label_topic;
+
+ gchar *topic;
+ gchar *name;
+ GCompletion *completion;
+
+ gint contacts_width;
+ gboolean contacts_visible;
+};
+
+static void group_chat_finalize (GObject *object);
+static void group_chat_create_ui (GossipGroupChat *chat);
+static void group_chat_widget_destroy_cb (GtkWidget *widget,
+ GossipGroupChat *chat);
+static void group_chat_contact_added_cb (EmpathyTpChatroom *tp_chat,
+ GossipContact *contact,
+ GossipGroupChat *chat);
+static void group_chat_contact_removed_cb (EmpathyTpChatroom *tp_chat,
+ GossipContact *contact,
+ GossipGroupChat *chat);
+/*static void group_chat_topic_changed_cb (EmpathyTpChatroom *tp_chat,
+ const gchar *new_topic,
+ GossipGroupChat *chat);*/
+static void group_chat_topic_entry_activate_cb (GtkWidget *entry,
+ GtkDialog *dialog);
+static void group_chat_topic_response_cb (GtkWidget *dialog,
+ gint response,
+ GossipGroupChat *chat);
+static const gchar * group_chat_get_name (GossipChat *chat);
+static gchar * group_chat_get_tooltip (GossipChat *chat);
+static const gchar * group_chat_get_status_icon_name (GossipChat *chat);
+static GtkWidget * group_chat_get_widget (GossipChat *chat);
+static gboolean group_chat_is_group_chat (GossipChat *chat);
+/*static gboolean group_chat_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ GossipGroupChat *chat);*/
+static gint group_chat_contacts_completion_func (const gchar *s1,
+ const gchar *s2,
+ gsize n);
+
+G_DEFINE_TYPE (GossipGroupChat, gossip_group_chat, GOSSIP_TYPE_CHAT)
+
+static void
+gossip_group_chat_class_init (GossipGroupChatClass *klass)
+{
+ GObjectClass *object_class;
+ GossipChatClass *chat_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ chat_class = GOSSIP_CHAT_CLASS (klass);
+
+ object_class->finalize = group_chat_finalize;
+
+ chat_class->get_name = group_chat_get_name;
+ chat_class->get_tooltip = group_chat_get_tooltip;
+ chat_class->get_status_icon_name = group_chat_get_status_icon_name;
+ chat_class->get_widget = group_chat_get_widget;
+ chat_class->is_group_chat = group_chat_is_group_chat;
+
+ g_type_class_add_private (object_class, sizeof (GossipGroupChatPriv));
+}
+
+static void
+gossip_group_chat_init (GossipGroupChat *chat)
+{
+ GossipGroupChatPriv *priv;
+ GossipChatView *chatview;
+
+ priv = GET_PRIV (chat);
+
+ priv->contacts_visible = TRUE;
+
+ chatview = GOSSIP_CHAT_VIEW (GOSSIP_CHAT (chat)->view);
+ gossip_chat_view_set_is_group_chat (chatview, TRUE);
+
+ group_chat_create_ui (chat);
+}
+
+static void
+group_chat_finalize (GObject *object)
+{
+ GossipGroupChat *chat;
+ GossipGroupChatPriv *priv;
+
+ gossip_debug (DEBUG_DOMAIN, "Finalized:%p", object);
+
+ chat = GOSSIP_GROUP_CHAT (object);
+ priv = GET_PRIV (chat);
+
+ g_free (priv->name);
+ g_free (priv->topic);
+ g_object_unref (priv->store);
+ g_object_unref (priv->tp_chat);
+ g_completion_free (priv->completion);
+
+ G_OBJECT_CLASS (gossip_group_chat_parent_class)->finalize (object);
+}
+
+GossipGroupChat *
+gossip_group_chat_new (McAccount *account,
+ TpChan *tp_chan)
+{
+ GossipGroupChat *chat;
+ GossipGroupChatPriv *priv;
+
+ g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
+
+ chat = g_object_new (GOSSIP_TYPE_GROUP_CHAT, NULL);
+
+ priv = GET_PRIV (chat);
+
+ priv->tp_chat = empathy_tp_chatroom_new (account, tp_chan);
+ gossip_chat_set_tp_chat (GOSSIP_CHAT (chat), EMPATHY_TP_CHAT (priv->tp_chat));
+ GOSSIP_CHAT (chat)->account = g_object_ref (account);
+
+ /* FIXME: Ask the user before accepting */
+ empathy_tp_chatroom_accept_invitation (priv->tp_chat);
+
+ /* Create contact list */
+ priv->store = gossip_contact_list_store_new (EMPATHY_CONTACT_LIST (priv->tp_chat));
+ priv->view = gossip_contact_list_view_new (priv->store);
+ gossip_contact_list_store_set_show_offline (priv->store, TRUE);
+ gtk_container_add (GTK_CONTAINER (priv->scrolled_window_contacts),
+ GTK_WIDGET (priv->view));
+ gtk_widget_show (GTK_WIDGET (priv->view));
+
+ g_signal_connect (priv->tp_chat, "contact-added",
+ G_CALLBACK (group_chat_contact_added_cb),
+ chat);
+ g_signal_connect (priv->tp_chat, "contact-removed",
+ G_CALLBACK (group_chat_contact_removed_cb),
+ chat);
+/* g_signal_connect (priv->tp_chat, "chatroom-topic-changed",
+ G_CALLBACK (group_chat_topic_changed_cb),
+ chat);
+ g_signal_connect (priv->tp_chat, "contact-info-changed",
+ G_CALLBACK (group_chat_contact_info_changed_cb),
+ chat);*/
+
+ return chat;
+}
+
+gboolean
+gossip_group_chat_get_show_contacts (GossipGroupChat *chat)
+{
+ GossipGroupChat *group_chat;
+ GossipGroupChatPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_GROUP_CHAT (chat), FALSE);
+
+ group_chat = GOSSIP_GROUP_CHAT (chat);
+ priv = GET_PRIV (group_chat);
+
+ return priv->contacts_visible;
+}
+
+void
+gossip_group_chat_set_show_contacts (GossipGroupChat *chat,
+ gboolean show)
+{
+ GossipGroupChat *group_chat;
+ GossipGroupChatPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_GROUP_CHAT (chat));
+
+ group_chat = GOSSIP_GROUP_CHAT (chat);
+ priv = GET_PRIV (group_chat);
+
+ priv->contacts_visible = show;
+
+ if (show) {
+ gtk_widget_show (priv->scrolled_window_contacts);
+ gtk_paned_set_position (GTK_PANED (priv->hpaned),
+ priv->contacts_width);
+ } else {
+ priv->contacts_width = gtk_paned_get_position (GTK_PANED (priv->hpaned));
+ gtk_widget_hide (priv->scrolled_window_contacts);
+ }
+}
+
+void
+gossip_group_chat_set_topic (GossipGroupChat *chat)
+{
+ GossipGroupChatPriv *priv;
+ GossipChatWindow *chat_window;
+ GtkWidget *chat_dialog;
+ GtkWidget *dialog;
+ GtkWidget *entry;
+ GtkWidget *hbox;
+ const gchar *topic;
+
+ g_return_if_fail (GOSSIP_IS_GROUP_CHAT (chat));
+
+ priv = GET_PRIV (chat);
+
+ chat_window = gossip_chat_get_window (GOSSIP_CHAT (chat));
+ chat_dialog = gossip_chat_window_get_dialog (chat_window);
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (chat_dialog),
+ 0,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_OK_CANCEL,
+ _("Enter the new topic you want to set for this room:"));
+
+ topic = gtk_label_get_text (GTK_LABEL (priv->label_topic));
+
+ hbox = gtk_hbox_new (FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
+ hbox, FALSE, TRUE, 4);
+
+ entry = gtk_entry_new ();
+ gtk_entry_set_text (GTK_ENTRY (entry), topic);
+ gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1);
+
+ gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 4);
+
+ g_object_set (GTK_MESSAGE_DIALOG (dialog)->label, "use-markup", TRUE, NULL);
+ g_object_set_data (G_OBJECT (dialog), "entry", entry);
+
+ g_signal_connect (entry, "activate",
+ G_CALLBACK (group_chat_topic_entry_activate_cb),
+ dialog);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (group_chat_topic_response_cb),
+ chat);
+
+ gtk_widget_show_all (dialog);
+}
+
+static void
+group_chat_create_ui (GossipGroupChat *chat)
+{
+ GossipGroupChatPriv *priv;
+ GladeXML *glade;
+ GList *list = NULL;
+
+ priv = GET_PRIV (chat);
+
+ glade = gossip_glade_get_file ("gossip-group-chat.glade",
+ "group_chat_widget",
+ NULL,
+ "group_chat_widget", &priv->widget,
+ "hpaned", &priv->hpaned,
+ "vbox_left", &priv->vbox_left,
+ "scrolled_window_chat", &priv->scrolled_window_chat,
+ "scrolled_window_input", &priv->scrolled_window_input,
+ "hbox_topic", &priv->hbox_topic,
+ "label_topic", &priv->label_topic,
+ "scrolled_window_contacts", &priv->scrolled_window_contacts,
+ NULL);
+
+ gossip_glade_connect (glade,
+ chat,
+ "group_chat_widget", "destroy", group_chat_widget_destroy_cb,
+ NULL);
+
+ g_object_unref (glade);
+
+ g_object_set_data (G_OBJECT (priv->widget), "chat", g_object_ref (chat));
+
+ /* Add room GtkTextView. */
+ gtk_container_add (GTK_CONTAINER (priv->scrolled_window_chat),
+ GTK_WIDGET (GOSSIP_CHAT (chat)->view));
+ gtk_widget_show (GTK_WIDGET (GOSSIP_CHAT (chat)->view));
+
+ /* Add input GtkTextView */
+ gtk_container_add (GTK_CONTAINER (priv->scrolled_window_input),
+ GOSSIP_CHAT (chat)->input_text_view);
+ gtk_widget_show (GOSSIP_CHAT (chat)->input_text_view);
+
+ /* Add nick name completion */
+ priv->completion = g_completion_new (NULL);
+ g_completion_set_compare (priv->completion,
+ group_chat_contacts_completion_func);
+
+ /* Set widget focus order */
+ list = g_list_append (NULL, priv->scrolled_window_input);
+ gtk_container_set_focus_chain (GTK_CONTAINER (priv->vbox_left), list);
+ g_list_free (list);
+
+ list = g_list_append (NULL, priv->vbox_left);
+ list = g_list_append (list, priv->scrolled_window_contacts);
+ gtk_container_set_focus_chain (GTK_CONTAINER (priv->hpaned), list);
+ g_list_free (list);
+
+ list = g_list_append (NULL, priv->hpaned);
+ list = g_list_append (list, priv->hbox_topic);
+ gtk_container_set_focus_chain (GTK_CONTAINER (priv->widget), list);
+ g_list_free (list);
+}
+
+static void
+group_chat_widget_destroy_cb (GtkWidget *widget,
+ GossipGroupChat *chat)
+{
+ gossip_debug (DEBUG_DOMAIN, "Destroyed");
+
+ g_object_unref (chat);
+}
+
+static void
+group_chat_contact_added_cb (EmpathyTpChatroom *tp_chat,
+ GossipContact *contact,
+ GossipGroupChat *chat)
+{
+ GossipGroupChatPriv *priv;
+ gchar *str;
+
+ priv = GET_PRIV (chat);
+
+ str = g_strdup_printf (_("%s has joined the room"),
+ gossip_contact_get_name (contact));
+ gossip_chat_view_append_event (GOSSIP_CHAT (chat)->view, str);
+ g_free (str);
+}
+
+static void
+group_chat_contact_removed_cb (EmpathyTpChatroom *tp_chat,
+ GossipContact *contact,
+ GossipGroupChat *chat)
+{
+ GossipGroupChatPriv *priv;
+ gchar *str;
+
+ priv = GET_PRIV (chat);
+
+ str = g_strdup_printf (_("%s has left the room"),
+ gossip_contact_get_name (contact));
+ gossip_chat_view_append_event (GOSSIP_CHAT (chat)->view, str);
+ g_free (str);
+}
+/*
+static void
+group_chat_topic_changed_cb (EmpathyTpChatroom *tp_chat,
+ const gchar *new_topic,
+ GossipGroupChat *chat)
+{
+ GossipGroupChatPriv *priv;
+ gchar *str;
+
+ priv = GET_PRIV (chat);
+
+ gossip_debug (DEBUG_DOMAIN, "Topic changed by to:'%s'", new_topic);
+
+ g_free (priv->topic);
+ priv->topic = g_strdup (new_topic);
+
+ gtk_label_set_text (GTK_LABEL (priv->label_topic), new_topic);
+
+ str = g_strdup_printf (_("Topic set to: %s"), new_topic);
+ gossip_chat_view_append_event (GOSSIP_CHAT (chat)->view, str);
+ g_free (str);
+}
+*/
+static void
+group_chat_topic_entry_activate_cb (GtkWidget *entry,
+ GtkDialog *dialog)
+{
+ gtk_dialog_response (dialog, GTK_RESPONSE_OK);
+}
+
+static void
+group_chat_topic_response_cb (GtkWidget *dialog,
+ gint response,
+ GossipGroupChat *chat)
+{
+ if (response == GTK_RESPONSE_OK) {
+ GtkWidget *entry;
+ const gchar *topic;
+
+ entry = g_object_get_data (G_OBJECT (dialog), "entry");
+ topic = gtk_entry_get_text (GTK_ENTRY (entry));
+
+ if (!G_STR_EMPTY (topic)) {
+ GossipGroupChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ empathy_tp_chatroom_set_topic (priv->tp_chat, topic);
+ }
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+static const gchar *
+group_chat_get_name (GossipChat *chat)
+{
+ GossipGroupChat *group_chat;
+ GossipGroupChatPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_GROUP_CHAT (chat), NULL);
+
+ group_chat = GOSSIP_GROUP_CHAT (chat);
+ priv = GET_PRIV (group_chat);
+
+ return priv->name;
+}
+
+static gchar *
+group_chat_get_tooltip (GossipChat *chat)
+{
+ GossipGroupChat *group_chat;
+ GossipGroupChatPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_GROUP_CHAT (chat), NULL);
+
+ group_chat = GOSSIP_GROUP_CHAT (chat);
+ priv = GET_PRIV (group_chat);
+
+ if (priv->topic) {
+ gchar *topic, *tmp;
+
+ topic = g_strdup_printf (_("Topic: %s"), priv->topic);
+ tmp = g_strdup_printf ("%s\n%s", priv->name, topic);
+ g_free (topic);
+
+ return tmp;
+ }
+
+ return g_strdup (priv->name);
+}
+
+static const gchar *
+group_chat_get_status_icon_name (GossipChat *chat)
+{
+ return EMPATHY_IMAGE_GROUP_MESSAGE;
+}
+
+static GtkWidget *
+group_chat_get_widget (GossipChat *chat)
+{
+ GossipGroupChat *group_chat;
+ GossipGroupChatPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_GROUP_CHAT (chat), NULL);
+
+ group_chat = GOSSIP_GROUP_CHAT (chat);
+ priv = GET_PRIV (group_chat);
+
+ return priv->widget;
+}
+
+static gboolean
+group_chat_is_group_chat (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_GROUP_CHAT (chat), FALSE);
+
+ return TRUE;
+}
+#if 0
+static gboolean
+group_chat_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ GossipGroupChat *chat)
+{
+ GossipGroupChatPriv *priv;
+ GtkAdjustment *adj;
+ gdouble val;
+ GtkTextBuffer *buffer;
+ GtkTextIter start, current;
+ gchar *nick, *completed;
+ gint len;
+ GList *list, *l, *completed_list;
+ gboolean is_start_of_buffer;
+
+ priv = GET_PRIV (chat);
+
+ if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
+ (event->state & GDK_SHIFT_MASK) != GDK_SHIFT_MASK &&
+ event->keyval == GDK_Tab) {
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (GOSSIP_CHAT (chat)->input_text_view));
+ gtk_text_buffer_get_iter_at_mark (buffer, &current, gtk_text_buffer_get_insert (buffer));
+
+ /* Get the start of the nick to complete. */
+ gtk_text_buffer_get_iter_at_mark (buffer, &start, gtk_text_buffer_get_insert (buffer));
+ gtk_text_iter_backward_word_start (&start);
+ is_start_of_buffer = gtk_text_iter_is_start (&start);
+
+ nick = gtk_text_buffer_get_text (buffer, &start, &current, FALSE);
+
+ g_completion_clear_items (priv->completion);
+
+ len = strlen (nick);
+
+ list = group_chat_get_nick_list (chat);
+
+ g_completion_add_items (priv->completion, list);
+
+ completed_list = g_completion_complete (priv->completion,
+ nick,
+ &completed);
+
+ g_free (nick);
+
+ if (completed) {
+ int len;
+ gchar *text;
+
+ gtk_text_buffer_delete (buffer, &start, &current);
+
+ len = g_list_length (completed_list);
+
+ if (len == 1) {
+ /* If we only have one hit, use that text
+ * instead of the text in completed since the
+ * completed text will use the typed string
+ * which might be cased all wrong.
+ * Fixes #120876
+ * */
+ text = (gchar *) completed_list->data;
+ } else {
+ text = completed;
+ }
+
+ gtk_text_buffer_insert_at_cursor (buffer, text, strlen (text));
+
+ if (len == 1) {
+ if (is_start_of_buffer) {
+ gtk_text_buffer_insert_at_cursor (buffer, ", ", 2);
+ }
+ }
+
+ g_free (completed);
+ }
+
+ g_completion_clear_items (priv->completion);
+
+ for (l = list; l; l = l->next) {
+ g_free (l->data);
+ }
+
+ g_list_free (list);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+#endif
+
+static gint
+group_chat_contacts_completion_func (const gchar *s1,
+ const gchar *s2,
+ gsize n)
+{
+ gchar *tmp, *nick1, *nick2;
+ gint ret;
+
+ tmp = g_utf8_normalize (s1, -1, G_NORMALIZE_DEFAULT);
+ nick1 = g_utf8_casefold (tmp, -1);
+ g_free (tmp);
+
+ tmp = g_utf8_normalize (s2, -1, G_NORMALIZE_DEFAULT);
+ nick2 = g_utf8_casefold (tmp, -1);
+ g_free (tmp);
+
+ ret = strncmp (nick1, nick2, n);
+
+ g_free (nick1);
+ g_free (nick2);
+
+ return ret;
+}
+
diff --git a/trunk/libempathy-gtk/gossip-group-chat.glade b/trunk/libempathy-gtk/gossip-group-chat.glade
new file mode 100644
index 000000000..7b6f2c00f
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-group-chat.glade
@@ -0,0 +1,183 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="group_chat_window">
+ <property name="border_width">6</property>
+ <property name="title" translatable="yes">Group Chat</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">600</property>
+ <property name="default_height">400</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="icon">gossip-group-message.png</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+
+ <child>
+ <widget class="GtkVBox" id="group_chat_widget">
+ <property name="border_width">4</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox_topic">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkLabel" id="label80">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Topic:&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label_topic">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_END</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">True</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">2</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHPaned" id="hpaned">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox_left">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolled_window_chat">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolled_window_input">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">True</property>
+ <property name="resize">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolled_window_contacts">
+ <property name="width_request">0</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="shrink">False</property>
+ <property name="resize">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/trunk/libempathy-gtk/gossip-group-chat.h b/trunk/libempathy-gtk/gossip-group-chat.h
new file mode 100644
index 000000000..37a7772bb
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-group-chat.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __GOSSIP_GROUP_CHAT_H__
+#define __GOSSIP_GROUP_CHAT_H__
+
+G_BEGIN_DECLS
+
+#include <libtelepathy/tp-chan.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+#define GOSSIP_TYPE_GROUP_CHAT (gossip_group_chat_get_type ())
+#define GOSSIP_GROUP_CHAT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_GROUP_CHAT, GossipGroupChat))
+#define GOSSIP_GROUP_CHAT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_GROUP_CHAT, GossipGroupChatClass))
+#define GOSSIP_IS_GROUP_CHAT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_GROUP_CHAT))
+#define GOSSIP_IS_GROUP_CHAT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_GROUP_CHAT))
+#define GOSSIP_GROUP_CHAT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_GROUP_CHAT, GossipGroupChatClass))
+
+typedef struct _GossipGroupChat GossipGroupChat;
+typedef struct _GossipGroupChatClass GossipGroupChatClass;
+typedef struct _GossipGroupChatPriv GossipGroupChatPriv;
+
+#include "gossip-chat.h"
+
+struct _GossipGroupChat {
+ GossipChat parent;
+
+ GossipGroupChatPriv *priv;
+};
+
+struct _GossipGroupChatClass {
+ GossipChatClass parent_class;
+};
+
+GType gossip_group_chat_get_type (void) G_GNUC_CONST;
+GossipGroupChat *gossip_group_chat_new (McAccount *account,
+ TpChan *tp_chan);
+gboolean gossip_group_chat_get_show_contacts (GossipGroupChat *chat);
+void gossip_group_chat_set_show_contacts (GossipGroupChat *chat,
+ gboolean show);
+void gossip_group_chat_set_topic (GossipGroupChat *chat);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_GROUP_CHAT_H__ */
diff --git a/trunk/libempathy-gtk/gossip-new-chatroom-dialog.c b/trunk/libempathy-gtk/gossip-new-chatroom-dialog.c
new file mode 100644
index 000000000..81594f1d1
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-new-chatroom-dialog.c
@@ -0,0 +1,690 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <libmissioncontrol/mission-control.h>
+#include <libmissioncontrol/mc-account.h>
+#include <libmissioncontrol/mc-profile.h>
+
+#include <libempathy/gossip-utils.h>
+#include <libempathy/gossip-debug.h>
+
+#include "gossip-new-chatroom-dialog.h"
+#include "gossip-account-chooser.h"
+//#include "gossip-chatrooms-window.h"
+#include "gossip-ui-utils.h"
+#include "ephy-spinner.h"
+
+#define DEBUG_DOMAIN "NewChatroomDialog"
+
+typedef struct {
+ GtkWidget *window;
+
+ GtkWidget *vbox_widgets;
+
+ GtkWidget *hbox_account;
+ GtkWidget *label_account;
+ GtkWidget *account_chooser;
+
+ GtkWidget *hbox_server;
+ GtkWidget *label_server;
+ GtkWidget *entry_server;
+ GtkWidget *togglebutton_refresh;
+
+ GtkWidget *hbox_room;
+ GtkWidget *label_room;
+ GtkWidget *entry_room;
+
+ GtkWidget *hbox_nick;
+ GtkWidget *label_nick;
+ GtkWidget *entry_nick;
+
+ GtkWidget *vbox_browse;
+ GtkWidget *image_status;
+ GtkWidget *label_status;
+ GtkWidget *hbox_status;
+ GtkWidget *throbber;
+ GtkWidget *treeview;
+ GtkTreeModel *model;
+ GtkTreeModel *filter;
+
+ GtkWidget *button_join;
+ GtkWidget *button_close;
+} GossipNewChatroomDialog;
+
+typedef struct {
+ guint handle;
+ gchar *channel_type;
+ gchar *name;
+ gchar *id;
+} EmpathyRoomListItem;
+
+enum {
+ COL_IMAGE,
+ COL_NAME,
+ COL_POINTER,
+ COL_COUNT
+};
+
+static void new_chatroom_dialog_response_cb (GtkWidget *widget,
+ gint response,
+ GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_destroy_cb (GtkWidget *widget,
+ GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_model_setup (GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_model_add_columns (GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_update_buttons (GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_update_widgets (GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_account_changed_cb (GtkComboBox *combobox,
+ GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_model_add (GossipNewChatroomDialog *dialog,
+ EmpathyRoomListItem *item);
+static void new_chatroom_dialog_model_clear (GossipNewChatroomDialog *dialog);
+static GList * new_chatroom_dialog_model_get_selected (GossipNewChatroomDialog *dialog);
+static gboolean new_chatroom_dialog_model_filter_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_model_row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_model_row_inserted_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_model_row_deleted_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_model_selection_changed (GtkTreeSelection *selection,
+ GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_join (GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_entry_changed_cb (GtkWidget *entry,
+ GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_browse_start (GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_browse_stop (GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_entry_server_activate_cb (GtkWidget *widget,
+ GossipNewChatroomDialog *dialog);
+static void new_chatroom_dialog_togglebutton_refresh_toggled_cb (GtkWidget *widget,
+ GossipNewChatroomDialog *dialog);
+
+static GossipNewChatroomDialog *dialog_p = NULL;
+
+void
+gossip_new_chatroom_dialog_show (GtkWindow *parent)
+{
+ GossipNewChatroomDialog *dialog;
+ GladeXML *glade;
+ GList *accounts;
+ gint account_num;
+ GtkSizeGroup *size_group;
+
+ if (dialog_p) {
+ gtk_window_present (GTK_WINDOW (dialog_p->window));
+ return;
+ }
+
+ dialog_p = dialog = g_new0 (GossipNewChatroomDialog, 1);
+
+ glade = gossip_glade_get_file ("gossip-new-chatroom-dialog.glade",
+ "new_chatroom_dialog",
+ NULL,
+ "new_chatroom_dialog", &dialog->window,
+ "hbox_account", &dialog->hbox_account,
+ "label_account", &dialog->label_account,
+ "vbox_widgets", &dialog->vbox_widgets,
+ "label_server", &dialog->label_server,
+ "label_room", &dialog->label_room,
+ "label_nick", &dialog->label_nick,
+ "hbox_server", &dialog->hbox_server,
+ "hbox_room", &dialog->hbox_room,
+ "hbox_nick", &dialog->hbox_nick,
+ "entry_server", &dialog->entry_server,
+ "entry_room", &dialog->entry_room,
+ "entry_nick", &dialog->entry_nick,
+ "togglebutton_refresh", &dialog->togglebutton_refresh,
+ "vbox_browse", &dialog->vbox_browse,
+ "image_status", &dialog->image_status,
+ "label_status", &dialog->label_status,
+ "hbox_status", &dialog->hbox_status,
+ "treeview", &dialog->treeview,
+ "button_join", &dialog->button_join,
+ NULL);
+
+ gossip_glade_connect (glade,
+ dialog,
+ "new_chatroom_dialog", "response", new_chatroom_dialog_response_cb,
+ "new_chatroom_dialog", "destroy", new_chatroom_dialog_destroy_cb,
+ "entry_nick", "changed", new_chatroom_dialog_entry_changed_cb,
+ "entry_server", "changed", new_chatroom_dialog_entry_changed_cb,
+ "entry_server", "activate", new_chatroom_dialog_entry_server_activate_cb,
+ "entry_room", "changed", new_chatroom_dialog_entry_changed_cb,
+ "togglebutton_refresh", "toggled", new_chatroom_dialog_togglebutton_refresh_toggled_cb,
+ NULL);
+
+ g_object_unref (glade);
+
+ g_object_add_weak_pointer (G_OBJECT (dialog->window), (gpointer) &dialog_p);
+
+ /* Label alignment */
+ size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ gtk_size_group_add_widget (size_group, dialog->label_account);
+ gtk_size_group_add_widget (size_group, dialog->label_server);
+ gtk_size_group_add_widget (size_group, dialog->label_room);
+ gtk_size_group_add_widget (size_group, dialog->label_nick);
+
+ g_object_unref (size_group);
+
+ /* Account chooser for custom */
+ dialog->account_chooser = gossip_account_chooser_new ();
+ gtk_box_pack_start (GTK_BOX (dialog->hbox_account),
+ dialog->account_chooser,
+ TRUE, TRUE, 0);
+ gtk_widget_show (dialog->account_chooser);
+
+ g_signal_connect (GTK_COMBO_BOX (dialog->account_chooser), "changed",
+ G_CALLBACK (new_chatroom_dialog_account_changed_cb),
+ dialog);
+
+ /* Populate */
+ accounts = mc_accounts_list ();
+ account_num = g_list_length (accounts);
+
+ g_list_foreach (accounts, (GFunc) g_object_unref, NULL);
+ g_list_free (accounts);
+
+ if (account_num > 1) {
+ gtk_widget_show (dialog->hbox_account);
+ } else {
+ /* Show no accounts combo box */
+ gtk_widget_hide (dialog->hbox_account);
+ }
+
+ /* Add throbber */
+ dialog->throbber = ephy_spinner_new ();
+ ephy_spinner_set_size (EPHY_SPINNER (dialog->throbber), GTK_ICON_SIZE_LARGE_TOOLBAR);
+ gtk_widget_show (dialog->throbber);
+
+ gtk_box_pack_start (GTK_BOX (dialog->hbox_status), dialog->throbber,
+ FALSE, FALSE, 0);
+
+ /* Set up chatrooms treeview */
+ new_chatroom_dialog_model_setup (dialog);
+
+ /* Set things up according to the account type */
+ new_chatroom_dialog_update_widgets (dialog);
+
+ if (parent) {
+ gtk_window_set_transient_for (GTK_WINDOW (dialog->window),
+ GTK_WINDOW (parent));
+ }
+
+ gtk_widget_show (dialog->window);
+}
+
+static void
+new_chatroom_dialog_response_cb (GtkWidget *widget,
+ gint response,
+ GossipNewChatroomDialog *dialog)
+{
+ if (response == GTK_RESPONSE_OK) {
+ new_chatroom_dialog_join (dialog);
+ }
+
+ gtk_widget_destroy (widget);
+}
+
+static void
+new_chatroom_dialog_destroy_cb (GtkWidget *widget,
+ GossipNewChatroomDialog *dialog)
+{
+ g_object_unref (dialog->model);
+ g_object_unref (dialog->filter);
+
+ g_free (dialog);
+}
+
+static void
+new_chatroom_dialog_model_setup (GossipNewChatroomDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkListStore *store;
+ GtkTreeSelection *selection;
+
+ /* View */
+ view = GTK_TREE_VIEW (dialog->treeview);
+
+ g_signal_connect (view, "row-activated",
+ G_CALLBACK (new_chatroom_dialog_model_row_activated_cb),
+ dialog);
+
+ /* Store/Model */
+ store = gtk_list_store_new (COL_COUNT,
+ G_TYPE_STRING, /* Image */
+ G_TYPE_STRING, /* Text */
+ G_TYPE_POINTER); /* infos */
+
+ dialog->model = GTK_TREE_MODEL (store);
+
+ /* Filter */
+ dialog->filter = gtk_tree_model_filter_new (dialog->model, NULL);
+
+ gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (dialog->filter),
+ (GtkTreeModelFilterVisibleFunc)
+ new_chatroom_dialog_model_filter_func,
+ dialog,
+ NULL);
+
+ gtk_tree_view_set_model (view, dialog->filter);
+
+ g_signal_connect (dialog->filter, "row-inserted",
+ G_CALLBACK (new_chatroom_dialog_model_row_inserted_cb),
+ dialog);
+ g_signal_connect (dialog->filter, "row-deleted",
+ G_CALLBACK (new_chatroom_dialog_model_row_deleted_cb),
+ dialog);
+
+ /* Selection */
+ selection = gtk_tree_view_get_selection (view);
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+ COL_NAME, GTK_SORT_ASCENDING);
+
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (new_chatroom_dialog_model_selection_changed), dialog);
+
+ /* Columns */
+ new_chatroom_dialog_model_add_columns (dialog);
+}
+
+static void
+new_chatroom_dialog_model_add_columns (GossipNewChatroomDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ gtk_tree_view_set_headers_visible (view, FALSE);
+
+ /* Chatroom pointer */
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_title (column, _("Chat Rooms"));
+
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+
+ cell = gtk_cell_renderer_text_new ();
+ g_object_set (cell,
+ "xpad", (guint) 4,
+ "ypad", (guint) 1,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ NULL);
+
+ gtk_tree_view_column_pack_start (column, cell, TRUE);
+
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_append_column (view, column);
+}
+
+static void
+new_chatroom_dialog_update_buttons (GossipNewChatroomDialog *dialog)
+{
+ GtkButton *button;
+ GtkWidget *image;
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ guint items;
+ const gchar *room;
+
+ /* Sort out Join button. */
+ button = GTK_BUTTON (dialog->button_join);
+
+ image = gtk_button_get_image (button);
+ if (!image) {
+ image = gtk_image_new ();
+ gtk_button_set_image (button, image);
+ }
+ //gtk_button_set_use_stock (button, FALSE);
+
+ room = gtk_entry_get_text (GTK_ENTRY (dialog->entry_room));
+
+ /* Collect necessary information first. */
+ view = GTK_TREE_VIEW (dialog->treeview);
+ model = gtk_tree_view_get_model (view);
+ items = gtk_tree_model_iter_n_children (model, NULL);
+
+ if (items < 1 && !G_STR_EMPTY (room)) {
+ gtk_button_set_label (button, _("Create"));
+ gtk_image_set_from_stock (GTK_IMAGE (image),
+ GTK_STOCK_NEW,
+ GTK_ICON_SIZE_BUTTON);
+ } else {
+ gtk_button_set_label (button, _("Join"));
+ gtk_image_set_from_stock (GTK_IMAGE (image),
+ GTK_STOCK_EXECUTE,
+ GTK_ICON_SIZE_BUTTON);
+ }
+
+ gtk_widget_set_sensitive (dialog->button_join, !G_STR_EMPTY (room));
+}
+
+static void
+new_chatroom_dialog_update_widgets (GossipNewChatroomDialog *dialog)
+{
+ GossipAccountChooser *account_chooser;
+ McAccount *account;
+ McProfile *profile;
+ const gchar *protocol;
+
+ account_chooser = GOSSIP_ACCOUNT_CHOOSER (dialog->account_chooser);
+ account = gossip_account_chooser_get_account (account_chooser);
+ profile = mc_account_get_profile (account);
+ protocol = mc_profile_get_protocol_name (profile);
+
+ /* hardcode here known protocols */
+ if (strcmp (protocol, "jabber") == 0) {
+ const gchar *server;
+
+ server = mc_profile_get_default_account_domain (profile);
+ if (server) {
+ gchar *conference_server;
+
+ conference_server = g_strconcat ("conference.",
+ server, NULL);
+ gtk_entry_set_text (GTK_ENTRY (dialog->entry_server),
+ conference_server);
+ g_free (conference_server);
+ }
+
+ gtk_widget_show (dialog->hbox_server);
+ gtk_widget_show (dialog->hbox_nick);
+ gtk_widget_show (dialog->vbox_browse);
+
+ }
+ else if (strcmp (protocol, "salut") == 0) {
+ gtk_widget_hide (dialog->hbox_server);
+ gtk_widget_show (dialog->hbox_nick);
+ gtk_widget_show (dialog->vbox_browse);
+ }
+ else if (strcmp (protocol, "irc") == 0) {
+ gtk_widget_hide (dialog->hbox_server);
+ gtk_widget_hide (dialog->hbox_nick);
+ gtk_widget_show (dialog->vbox_browse);
+ } else {
+ gtk_widget_hide (dialog->hbox_server);
+ gtk_widget_hide (dialog->hbox_nick);
+ gtk_widget_hide (dialog->vbox_browse);
+ }
+
+ new_chatroom_dialog_update_buttons (dialog);
+
+ /* Final set up of the dialog */
+ gtk_widget_grab_focus (dialog->entry_room);
+
+ g_object_unref (account);
+ g_object_unref (profile);
+}
+
+static void
+new_chatroom_dialog_account_changed_cb (GtkComboBox *combobox,
+ GossipNewChatroomDialog *dialog)
+{
+ new_chatroom_dialog_update_widgets (dialog);
+}
+
+static void
+new_chatroom_dialog_model_add (GossipNewChatroomDialog *dialog,
+ EmpathyRoomListItem *item)
+{
+ GtkTreeView *view;
+ GtkTreeSelection *selection;
+ GtkListStore *store;
+ GtkTreeIter iter;
+
+ /* Add to model */
+ view = GTK_TREE_VIEW (dialog->treeview);
+ selection = gtk_tree_view_get_selection (view);
+ store = GTK_LIST_STORE (dialog->model);
+
+ gtk_list_store_append (store, &iter);
+
+ gtk_list_store_set (store, &iter,
+ COL_NAME, item->name,
+ COL_POINTER, item,
+ -1);
+}
+
+static void
+new_chatroom_dialog_model_clear (GossipNewChatroomDialog *dialog)
+{
+ GtkListStore *store;
+
+ store = GTK_LIST_STORE (dialog->model);
+ gtk_list_store_clear (store);
+}
+
+static GList *
+new_chatroom_dialog_model_get_selected (GossipNewChatroomDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GList *rows, *l;
+ GList *chatrooms = NULL;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ selection = gtk_tree_view_get_selection (view);
+ model = gtk_tree_view_get_model (view);
+
+ rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+ for (l = rows; l; l = l->next) {
+ GtkTreeIter iter;
+ EmpathyRoomListItem *chatroom;
+
+ if (!gtk_tree_model_get_iter (model, &iter, l->data)) {
+ continue;
+ }
+
+ gtk_tree_model_get (model, &iter, COL_POINTER, &chatroom, -1);
+ chatrooms = g_list_append (chatrooms, chatroom);
+ }
+
+ g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free (rows);
+
+ return chatrooms;
+}
+
+static gboolean
+new_chatroom_dialog_model_filter_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipNewChatroomDialog *dialog)
+{
+ EmpathyRoomListItem *chatroom;
+ const gchar *text;
+ gchar *room_nocase;
+ gchar *text_nocase;
+ gboolean found = FALSE;
+
+ gtk_tree_model_get (model, iter, COL_POINTER, &chatroom, -1);
+
+ if (!chatroom) {
+ return TRUE;
+ }
+
+ text = gtk_entry_get_text (GTK_ENTRY (dialog->entry_room));
+
+ /* Casefold */
+ room_nocase = g_utf8_casefold (chatroom->id, -1);
+ text_nocase = g_utf8_casefold (text, -1);
+
+ /* Compare */
+ if (g_utf8_strlen (text_nocase, -1) < 1 ||
+ strstr (room_nocase, text_nocase)) {
+ found = TRUE;
+ }
+
+ g_free (room_nocase);
+ g_free (text_nocase);
+
+ return found;
+}
+
+static void
+new_chatroom_dialog_model_row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GossipNewChatroomDialog *dialog)
+{
+ gtk_widget_activate (dialog->button_join);
+}
+
+static void
+new_chatroom_dialog_model_row_inserted_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GossipNewChatroomDialog *dialog)
+{
+ new_chatroom_dialog_update_buttons (dialog);
+}
+
+static void
+new_chatroom_dialog_model_row_deleted_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ GossipNewChatroomDialog *dialog)
+{
+ new_chatroom_dialog_update_buttons (dialog);
+}
+
+static void
+new_chatroom_dialog_model_selection_changed (GtkTreeSelection *selection,
+ GossipNewChatroomDialog *dialog)
+{
+ new_chatroom_dialog_update_buttons (dialog);
+}
+
+static void
+new_chatroom_dialog_join (GossipNewChatroomDialog *dialog)
+{
+ McAccount *account;
+ GossipAccountChooser *account_chooser;
+ MissionControl *mc;
+ GList *chatrooms, *l;
+ const gchar *room;
+ const gchar *server = NULL;
+ gchar *room_name = NULL;
+
+ chatrooms = new_chatroom_dialog_model_get_selected (dialog);
+ if (chatrooms) {
+ for (l = chatrooms; l; l = l->next) {
+ /* Join it */
+ }
+ g_list_free (chatrooms);
+ return;
+ }
+
+ room = gtk_entry_get_text (GTK_ENTRY (dialog->entry_room));
+ if (GTK_WIDGET_VISIBLE (dialog->hbox_server)) {
+ server = gtk_entry_get_text (GTK_ENTRY (dialog->entry_server));
+ }
+ account_chooser = GOSSIP_ACCOUNT_CHOOSER (dialog->account_chooser);
+ account = gossip_account_chooser_get_account (account_chooser);
+
+ if (!G_STR_EMPTY (server)) {
+ room_name = g_strconcat (room, "@", server, NULL);
+ } else {
+ room_name = g_strdup (room);
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Requesting channel for '%s'", room_name);
+
+ mc = gossip_mission_control_new ();
+ mission_control_request_channel_with_string_handle (mc,
+ account,
+ TP_IFACE_CHANNEL_TYPE_TEXT,
+ room_name,
+ TP_HANDLE_TYPE_ROOM,
+ NULL, NULL);
+ g_free (room_name);
+ g_object_unref (mc);
+}
+
+static void
+new_chatroom_dialog_entry_changed_cb (GtkWidget *entry,
+ GossipNewChatroomDialog *dialog)
+{
+ if (entry == dialog->entry_room) {
+ gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (dialog->filter));
+ }
+
+ new_chatroom_dialog_update_buttons (dialog);
+}
+
+static void
+new_chatroom_dialog_browse_start (GossipNewChatroomDialog *dialog)
+{
+ if (0) {
+ new_chatroom_dialog_model_clear (dialog);
+ new_chatroom_dialog_model_add (dialog, NULL);
+ }
+}
+
+static void
+new_chatroom_dialog_browse_stop (GossipNewChatroomDialog *dialog)
+{
+}
+
+static void
+new_chatroom_dialog_entry_server_activate_cb (GtkWidget *widget,
+ GossipNewChatroomDialog *dialog)
+{
+ new_chatroom_dialog_togglebutton_refresh_toggled_cb (dialog->togglebutton_refresh,
+ dialog);
+}
+
+static void
+new_chatroom_dialog_togglebutton_refresh_toggled_cb (GtkWidget *widget,
+ GossipNewChatroomDialog *dialog)
+{
+ gboolean toggled;
+
+ toggled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
+
+ if (toggled) {
+ new_chatroom_dialog_browse_start (dialog);
+ } else {
+ new_chatroom_dialog_browse_stop (dialog);
+ }
+}
+
diff --git a/trunk/libempathy-gtk/gossip-new-chatroom-dialog.glade b/trunk/libempathy-gtk/gossip-new-chatroom-dialog.glade
new file mode 100644
index 000000000..49bdadb64
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-new-chatroom-dialog.glade
@@ -0,0 +1,519 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkDialog" id="new_chatroom_dialog">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Join New</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">350</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="icon_name">gtk-new</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox4">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area4">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="button_cancel">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-7</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="button_join">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-5</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <property name="top_padding">0</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">0</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox29">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child>
+ <widget class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="stock">gtk-execute</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label79">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Join</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox_widgets">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox_account">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="label_account">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Account:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox_browse">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox_server">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="label_server">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Server:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_server</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry_server">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <property name="width_chars">25</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkToggleButton" id="togglebutton_refresh">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Re_fresh</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox_room">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="label_room">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Room:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_room</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry_room">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Enter the room name to join here or click on one or more rooms in the list.</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">True</property>
+ <property name="width_chars">32</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox_nick">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="label_nick">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Nickname:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_nick</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry_nick">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox_browse">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox_status">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox35">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">3</property>
+
+ <child>
+ <widget class="GtkImage" id="image_status">
+ <property name="visible">True</property>
+ <property name="icon_size">2</property>
+ <property name="icon_name">gtk-find</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label_status">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Browse:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="height_request">150</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="treeview">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">This list represents all chat rooms hosted on the server you have entered.</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/trunk/libempathy-gtk/gossip-new-chatroom-dialog.h b/trunk/libempathy-gtk/gossip-new-chatroom-dialog.h
new file mode 100644
index 000000000..44b7ce67f
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-new-chatroom-dialog.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __GOSSIP_NEW_CHATROOMS_WINDOW_H__
+#define __GOSSIP_NEW_CHATROOMS_WINDOW_H__
+
+G_BEGIN_DECLS
+
+void gossip_new_chatroom_dialog_show (GtkWindow *parent);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_NEW_CHATROOMS_WINDOW_H__ */
diff --git a/trunk/libempathy-gtk/gossip-preferences.c b/trunk/libempathy-gtk/gossip-preferences.c
new file mode 100644
index 000000000..fd93353df
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-preferences.c
@@ -0,0 +1,985 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+
+#include <libempathy/gossip-conf.h>
+
+#include "gossip-preferences.h"
+#include "gossip-ui-utils.h"
+#include "gossip-theme-manager.h"
+#include "gossip-spell.h"
+#include "gossip-contact-list-store.h"
+
+typedef struct {
+ GtkWidget *dialog;
+
+ GtkWidget *notebook;
+
+ GtkWidget *checkbutton_show_avatars;
+ GtkWidget *checkbutton_compact_contact_list;
+ GtkWidget *checkbutton_show_smileys;
+ GtkWidget *combobox_chat_theme;
+ GtkWidget *checkbutton_theme_chat_room;
+ GtkWidget *checkbutton_separate_chat_windows;
+ GtkWidget *radiobutton_contact_list_sort_by_name;
+ GtkWidget *radiobutton_contact_list_sort_by_state;
+
+ GtkWidget *checkbutton_sounds_for_messages;
+ GtkWidget *checkbutton_sounds_when_busy;
+ GtkWidget *checkbutton_sounds_when_away;
+ GtkWidget *checkbutton_popups_when_available;
+
+ GtkWidget *treeview_spell_checker;
+ GtkWidget *checkbutton_spell_checker;
+
+ GList *notify_ids;
+} GossipPreferences;
+
+static void preferences_setup_widgets (GossipPreferences *preferences);
+static void preferences_languages_setup (GossipPreferences *preferences);
+static void preferences_languages_add (GossipPreferences *preferences);
+static void preferences_languages_save (GossipPreferences *preferences);
+static gboolean preferences_languages_save_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gchar **languages);
+static void preferences_languages_load (GossipPreferences *preferences);
+static gboolean preferences_languages_load_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gchar **languages);
+static void preferences_languages_cell_toggled_cb (GtkCellRendererToggle *cell,
+ gchar *path_string,
+ GossipPreferences *preferences);
+static void preferences_themes_setup (GossipPreferences *preferences);
+static void preferences_widget_sync_bool (const gchar *key,
+ GtkWidget *widget);
+static void preferences_widget_sync_int (const gchar *key,
+ GtkWidget *widget);
+static void preferences_widget_sync_string (const gchar *key,
+ GtkWidget *widget);
+static void preferences_widget_sync_string_combo (const gchar *key,
+ GtkWidget *widget);
+static void preferences_notify_int_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void preferences_notify_string_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void preferences_notify_string_combo_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void preferences_notify_bool_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void preferences_notify_sensitivity_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void preferences_hookup_spin_button (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget);
+static void preferences_hookup_entry (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget);
+static void preferences_hookup_toggle_button (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget);
+static void preferences_hookup_radio_button (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget);
+static void preferences_hookup_string_combo (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget);
+static void preferences_hookup_sensitivity (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget);
+static void preferences_spin_button_value_changed_cb (GtkWidget *button,
+ gpointer user_data);
+static void preferences_entry_value_changed_cb (GtkWidget *entry,
+ gpointer user_data);
+static void preferences_toggle_button_toggled_cb (GtkWidget *button,
+ gpointer user_data);
+static void preferences_radio_button_toggled_cb (GtkWidget *button,
+ gpointer user_data);
+static void preferences_string_combo_changed_cb (GtkWidget *button,
+ gpointer user_data);
+static void preferences_destroy_cb (GtkWidget *widget,
+ GossipPreferences *preferences);
+static void preferences_response_cb (GtkWidget *widget,
+ gint response,
+ GossipPreferences *preferences);
+
+enum {
+ COL_LANG_ENABLED,
+ COL_LANG_CODE,
+ COL_LANG_NAME,
+ COL_LANG_COUNT
+};
+
+enum {
+ COL_COMBO_VISIBLE_NAME,
+ COL_COMBO_NAME,
+ COL_COMBO_COUNT
+};
+
+static void
+preferences_setup_widgets (GossipPreferences *preferences)
+{
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_SOUNDS_FOR_MESSAGES,
+ preferences->checkbutton_sounds_for_messages);
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_SOUNDS_WHEN_AWAY,
+ preferences->checkbutton_sounds_when_away);
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_SOUNDS_WHEN_BUSY,
+ preferences->checkbutton_sounds_when_busy);
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_POPUPS_WHEN_AVAILABLE,
+ preferences->checkbutton_popups_when_available);
+
+ preferences_hookup_sensitivity (preferences,
+ GOSSIP_PREFS_SOUNDS_FOR_MESSAGES,
+ preferences->checkbutton_sounds_when_away);
+ preferences_hookup_sensitivity (preferences,
+ GOSSIP_PREFS_SOUNDS_FOR_MESSAGES,
+ preferences->checkbutton_sounds_when_busy);
+
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_UI_SEPARATE_CHAT_WINDOWS,
+ preferences->checkbutton_separate_chat_windows);
+
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ preferences->checkbutton_show_avatars);
+
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_UI_COMPACT_CONTACT_LIST,
+ preferences->checkbutton_compact_contact_list);
+
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_CHAT_SHOW_SMILEYS,
+ preferences->checkbutton_show_smileys);
+
+ preferences_hookup_string_combo (preferences,
+ GOSSIP_PREFS_CHAT_THEME,
+ preferences->combobox_chat_theme);
+
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
+ preferences->checkbutton_theme_chat_room);
+
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_ENABLED,
+ preferences->checkbutton_spell_checker);
+ preferences_hookup_sensitivity (preferences,
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_ENABLED,
+ preferences->treeview_spell_checker);
+
+ preferences_hookup_radio_button (preferences,
+ GOSSIP_PREFS_CONTACTS_SORT_CRITERIUM,
+ preferences->radiobutton_contact_list_sort_by_name);
+}
+
+static void
+preferences_languages_setup (GossipPreferences *preferences)
+{
+ GtkTreeView *view;
+ GtkListStore *store;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ guint col_offset;
+
+ view = GTK_TREE_VIEW (preferences->treeview_spell_checker);
+
+ store = gtk_list_store_new (COL_LANG_COUNT,
+ G_TYPE_BOOLEAN, /* enabled */
+ G_TYPE_STRING, /* code */
+ G_TYPE_STRING); /* name */
+
+ gtk_tree_view_set_model (view, GTK_TREE_MODEL (store));
+
+ selection = gtk_tree_view_get_selection (view);
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+ model = GTK_TREE_MODEL (store);
+
+ renderer = gtk_cell_renderer_toggle_new ();
+ g_signal_connect (renderer, "toggled",
+ G_CALLBACK (preferences_languages_cell_toggled_cb),
+ preferences);
+
+ column = gtk_tree_view_column_new_with_attributes (NULL, renderer,
+ "active", COL_LANG_ENABLED,
+ NULL);
+
+ gtk_tree_view_append_column (view, column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ col_offset = gtk_tree_view_insert_column_with_attributes (view,
+ -1, _("Language"),
+ renderer,
+ "text", COL_LANG_NAME,
+ NULL);
+
+ g_object_set_data (G_OBJECT (renderer),
+ "column", GINT_TO_POINTER (COL_LANG_NAME));
+
+ column = gtk_tree_view_get_column (view, col_offset - 1);
+ gtk_tree_view_column_set_sort_column_id (column, COL_LANG_NAME);
+ gtk_tree_view_column_set_resizable (column, FALSE);
+ gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
+
+ g_object_unref (store);
+}
+
+static void
+preferences_languages_add (GossipPreferences *preferences)
+{
+ GtkTreeView *view;
+ GtkListStore *store;
+ GList *codes, *l;
+
+ view = GTK_TREE_VIEW (preferences->treeview_spell_checker);
+ store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
+
+ codes = gossip_spell_get_language_codes ();
+ for (l = codes; l; l = l->next) {
+ GtkTreeIter iter;
+ const gchar *code;
+ const gchar *name;
+
+ code = l->data;
+ name = gossip_spell_get_language_name (code);
+ if (!name) {
+ continue;
+ }
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COL_LANG_CODE, code,
+ COL_LANG_NAME, name,
+ -1);
+ }
+
+ gossip_spell_free_language_codes (codes);
+}
+
+static void
+preferences_languages_save (GossipPreferences *preferences)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+
+ gchar *languages = NULL;
+
+ view = GTK_TREE_VIEW (preferences->treeview_spell_checker);
+ model = gtk_tree_view_get_model (view);
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc) preferences_languages_save_foreach,
+ &languages);
+
+ if (!languages) {
+ /* Default to english */
+ languages = g_strdup ("en");
+ }
+
+ gossip_conf_set_string (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_LANGUAGES,
+ languages);
+
+ g_free (languages);
+}
+
+static gboolean
+preferences_languages_save_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gchar **languages)
+{
+ gboolean enabled;
+ gchar *code;
+
+ if (!languages) {
+ return TRUE;
+ }
+
+ gtk_tree_model_get (model, iter, COL_LANG_ENABLED, &enabled, -1);
+ if (!enabled) {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (model, iter, COL_LANG_CODE, &code, -1);
+ if (!code) {
+ return FALSE;
+ }
+
+ if (!(*languages)) {
+ *languages = g_strdup (code);
+ } else {
+ gchar *str = *languages;
+ *languages = g_strdup_printf ("%s,%s", str, code);
+ g_free (str);
+ }
+
+ g_free (code);
+
+ return FALSE;
+}
+
+static void
+preferences_languages_load (GossipPreferences *preferences)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ gchar *value;
+ gchar **vlanguages;
+
+ if (!gossip_conf_get_string (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_LANGUAGES,
+ &value) || !value) {
+ return;
+ }
+
+ vlanguages = g_strsplit (value, ",", -1);
+ g_free (value);
+
+ view = GTK_TREE_VIEW (preferences->treeview_spell_checker);
+ model = gtk_tree_view_get_model (view);
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc) preferences_languages_load_foreach,
+ vlanguages);
+
+ g_strfreev (vlanguages);
+}
+
+static gboolean
+preferences_languages_load_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gchar **languages)
+{
+ gchar *code;
+ gchar *lang;
+ gint i;
+ gboolean found = FALSE;
+
+ if (!languages) {
+ return TRUE;
+ }
+
+ gtk_tree_model_get (model, iter, COL_LANG_CODE, &code, -1);
+ if (!code) {
+ return FALSE;
+ }
+
+ for (i = 0, lang = languages[i]; lang; lang = languages[++i]) {
+ if (strcmp (lang, code) == 0) {
+ found = TRUE;
+ }
+ }
+
+ gtk_list_store_set (GTK_LIST_STORE (model), iter, COL_LANG_ENABLED, found, -1);
+ return FALSE;
+}
+
+static void
+preferences_languages_cell_toggled_cb (GtkCellRendererToggle *cell,
+ gchar *path_string,
+ GossipPreferences *preferences)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gboolean enabled;
+
+ view = GTK_TREE_VIEW (preferences->treeview_spell_checker);
+ model = gtk_tree_view_get_model (view);
+ store = GTK_LIST_STORE (model);
+
+ path = gtk_tree_path_new_from_string (path_string);
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter, COL_LANG_ENABLED, &enabled, -1);
+
+ enabled ^= 1;
+
+ gtk_list_store_set (store, &iter, COL_LANG_ENABLED, enabled, -1);
+ gtk_tree_path_free (path);
+
+ preferences_languages_save (preferences);
+}
+
+static void
+preferences_themes_setup (GossipPreferences *preferences)
+{
+ GtkComboBox *combo;
+ GtkListStore *model;
+ GtkTreeIter iter;
+ const gchar **themes;
+ gint i;
+
+ combo = GTK_COMBO_BOX (preferences->combobox_chat_theme);
+
+ model = gtk_list_store_new (COL_COMBO_COUNT,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ themes = gossip_theme_manager_get_themes ();
+ for (i = 0; themes[i]; i += 2) {
+ gtk_list_store_append (model, &iter);
+ gtk_list_store_set (model, &iter,
+ COL_COMBO_VISIBLE_NAME, _(themes[i + 1]),
+ COL_COMBO_NAME, themes[i],
+ -1);
+ }
+
+ gtk_combo_box_set_model (combo, GTK_TREE_MODEL (model));
+ g_object_unref (model);
+}
+
+static void
+preferences_widget_sync_bool (const gchar *key, GtkWidget *widget)
+{
+ gboolean value;
+
+ if (gossip_conf_get_bool (gossip_conf_get (), key, &value)) {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), value);
+ }
+}
+
+static void
+preferences_widget_sync_int (const gchar *key, GtkWidget *widget)
+{
+ gint value;
+
+ if (gossip_conf_get_int (gossip_conf_get (), key, &value)) {
+ if (GTK_IS_SPIN_BUTTON (widget)) {
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), value);
+ }
+ }
+}
+
+static void
+preferences_widget_sync_string (const gchar *key, GtkWidget *widget)
+{
+ gchar *value;
+
+ if (gossip_conf_get_string (gossip_conf_get (), key, &value) && value) {
+ if (GTK_IS_ENTRY (widget)) {
+ gtk_entry_set_text (GTK_ENTRY (widget), value);
+ } else if (GTK_IS_RADIO_BUTTON (widget)) {
+ if (strcmp (key, GOSSIP_PREFS_CONTACTS_SORT_CRITERIUM) == 0) {
+ GType type;
+ GEnumClass *enum_class;
+ GEnumValue *enum_value;
+ GSList *list;
+ GtkWidget *toggle_widget;
+
+ /* Get index from new string */
+ type = gossip_contact_list_store_sort_get_type ();
+ enum_class = G_ENUM_CLASS (g_type_class_peek (type));
+ enum_value = g_enum_get_value_by_nick (enum_class, value);
+
+ if (enum_value) {
+ list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (widget));
+ toggle_widget = g_slist_nth_data (list, enum_value->value);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle_widget), TRUE);
+ }
+ } else {
+ g_warning ("Unhandled key:'%s' just had string change", key);
+ }
+ }
+
+ g_free (value);
+ }
+}
+
+static void
+preferences_widget_sync_string_combo (const gchar *key, GtkWidget *widget)
+{
+ gchar *value;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean found;
+
+ if (!gossip_conf_get_string (gossip_conf_get (), key, &value)) {
+ return;
+ }
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (widget));
+
+ found = FALSE;
+ if (value && gtk_tree_model_get_iter_first (model, &iter)) {
+ gchar *name;
+
+ do {
+ gtk_tree_model_get (model, &iter,
+ COL_COMBO_NAME, &name,
+ -1);
+
+ if (strcmp (name, value) == 0) {
+ found = TRUE;
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (widget), &iter);
+ break;
+ } else {
+ found = FALSE;
+ }
+
+ g_free (name);
+ } while (gtk_tree_model_iter_next (model, &iter));
+ }
+
+ /* Fallback to the first one. */
+ if (!found) {
+ if (gtk_tree_model_get_iter_first (model, &iter)) {
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (widget), &iter);
+ }
+ }
+
+ g_free (value);
+}
+
+static void
+preferences_notify_int_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ preferences_widget_sync_int (key, user_data);
+}
+
+static void
+preferences_notify_string_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ preferences_widget_sync_string (key, user_data);
+}
+
+static void
+preferences_notify_string_combo_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ preferences_widget_sync_string_combo (key, user_data);
+}
+
+static void
+preferences_notify_bool_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ preferences_widget_sync_bool (key, user_data);
+}
+
+static void
+preferences_notify_sensitivity_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ gboolean value;
+
+ if (gossip_conf_get_bool (conf, key, &value)) {
+ gtk_widget_set_sensitive (GTK_WIDGET (user_data), value);
+ }
+}
+
+static void
+preferences_add_id (GossipPreferences *preferences, guint id)
+{
+ preferences->notify_ids = g_list_prepend (preferences->notify_ids,
+ GUINT_TO_POINTER (id));
+}
+
+static void
+preferences_hookup_spin_button (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget)
+{
+ guint id;
+
+ /* Silence warning. */
+ if (0) {
+ preferences_hookup_spin_button (preferences, key, widget);
+ }
+
+ preferences_widget_sync_int (key, widget);
+
+ g_object_set_data_full (G_OBJECT (widget), "key",
+ g_strdup (key), g_free);
+
+ g_signal_connect (widget,
+ "value_changed",
+ G_CALLBACK (preferences_spin_button_value_changed_cb),
+ NULL);
+
+ id = gossip_conf_notify_add (gossip_conf_get (),
+ key,
+ preferences_notify_int_cb,
+ widget);
+ if (id) {
+ preferences_add_id (preferences, id);
+ }
+}
+
+static void
+preferences_hookup_entry (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget)
+{
+ guint id;
+
+ if (0) { /* Silent warning before we use this function. */
+ preferences_hookup_entry (preferences, key, widget);
+ }
+
+ preferences_widget_sync_string (key, widget);
+
+ g_object_set_data_full (G_OBJECT (widget), "key",
+ g_strdup (key), g_free);
+
+ g_signal_connect (widget,
+ "changed",
+ G_CALLBACK (preferences_entry_value_changed_cb),
+ NULL);
+
+ id = gossip_conf_notify_add (gossip_conf_get (),
+ key,
+ preferences_notify_string_cb,
+ widget);
+ if (id) {
+ preferences_add_id (preferences, id);
+ }
+}
+
+static void
+preferences_hookup_toggle_button (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget)
+{
+ guint id;
+
+ preferences_widget_sync_bool (key, widget);
+
+ g_object_set_data_full (G_OBJECT (widget), "key",
+ g_strdup (key), g_free);
+
+ g_signal_connect (widget,
+ "toggled",
+ G_CALLBACK (preferences_toggle_button_toggled_cb),
+ NULL);
+
+ id = gossip_conf_notify_add (gossip_conf_get (),
+ key,
+ preferences_notify_bool_cb,
+ widget);
+ if (id) {
+ preferences_add_id (preferences, id);
+ }
+}
+
+static void
+preferences_hookup_radio_button (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget)
+{
+ GSList *group, *l;
+ guint id;
+
+ preferences_widget_sync_string (key, widget);
+
+ group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (widget));
+ for (l = group; l; l = l->next) {
+ g_signal_connect (l->data,
+ "toggled",
+ G_CALLBACK (preferences_radio_button_toggled_cb),
+ NULL);
+
+ g_object_set_data_full (G_OBJECT (l->data), "key",
+ g_strdup (key), g_free);
+ }
+
+ id = gossip_conf_notify_add (gossip_conf_get (),
+ key,
+ preferences_notify_string_cb,
+ widget);
+ if (id) {
+ preferences_add_id (preferences, id);
+ }
+}
+
+static void
+preferences_hookup_string_combo (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget)
+{
+ guint id;
+
+ preferences_widget_sync_string_combo (key, widget);
+
+ g_object_set_data_full (G_OBJECT (widget), "key",
+ g_strdup (key), g_free);
+
+ g_signal_connect (widget,
+ "changed",
+ G_CALLBACK (preferences_string_combo_changed_cb),
+ NULL);
+
+ id = gossip_conf_notify_add (gossip_conf_get (),
+ key,
+ preferences_notify_string_combo_cb,
+ widget);
+ if (id) {
+ preferences_add_id (preferences, id);
+ }
+}
+
+static void
+preferences_hookup_sensitivity (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget)
+{
+ gboolean value;
+ guint id;
+
+ if (gossip_conf_get_bool (gossip_conf_get (), key, &value)) {
+ gtk_widget_set_sensitive (widget, value);
+ }
+
+ id = gossip_conf_notify_add (gossip_conf_get (),
+ key,
+ preferences_notify_sensitivity_cb,
+ widget);
+ if (id) {
+ preferences_add_id (preferences, id);
+ }
+}
+
+static void
+preferences_spin_button_value_changed_cb (GtkWidget *button,
+ gpointer user_data)
+{
+ const gchar *key;
+
+ key = g_object_get_data (G_OBJECT (button), "key");
+
+ gossip_conf_set_int (gossip_conf_get (),
+ key,
+ gtk_spin_button_get_value (GTK_SPIN_BUTTON (button)));
+}
+
+static void
+preferences_entry_value_changed_cb (GtkWidget *entry,
+ gpointer user_data)
+{
+ const gchar *key;
+
+ key = g_object_get_data (G_OBJECT (entry), "key");
+
+ gossip_conf_set_string (gossip_conf_get (),
+ key,
+ gtk_entry_get_text (GTK_ENTRY (entry)));
+}
+
+static void
+preferences_toggle_button_toggled_cb (GtkWidget *button,
+ gpointer user_data)
+{
+ const gchar *key;
+
+ key = g_object_get_data (G_OBJECT (button), "key");
+
+ gossip_conf_set_bool (gossip_conf_get (),
+ key,
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
+}
+
+static void
+preferences_radio_button_toggled_cb (GtkWidget *button,
+ gpointer user_data)
+{
+ const gchar *key;
+ const gchar *value = NULL;
+
+ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) {
+ return;
+ }
+
+ key = g_object_get_data (G_OBJECT (button), "key");
+
+ if (key && strcmp (key, GOSSIP_PREFS_CONTACTS_SORT_CRITERIUM) == 0) {
+ GSList *group;
+ GType type;
+ GEnumClass *enum_class;
+ GEnumValue *enum_value;
+
+ group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
+
+ /* Get string from index */
+ type = gossip_contact_list_store_sort_get_type ();
+ enum_class = G_ENUM_CLASS (g_type_class_peek (type));
+ enum_value = g_enum_get_value (enum_class, g_slist_index (group, button));
+
+ if (!enum_value) {
+ g_warning ("No GEnumValue for GossipContactListSort with GtkRadioButton index:%d",
+ g_slist_index (group, button));
+ return;
+ }
+
+ value = enum_value->value_nick;
+ }
+
+ gossip_conf_set_string (gossip_conf_get (), key, value);
+}
+
+static void
+preferences_string_combo_changed_cb (GtkWidget *combo,
+ gpointer user_data)
+{
+ const gchar *key;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *name;
+
+ key = g_object_get_data (G_OBJECT (combo), "key");
+
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) {
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+
+ gtk_tree_model_get (model, &iter,
+ COL_COMBO_NAME, &name,
+ -1);
+ gossip_conf_set_string (gossip_conf_get (), key, name);
+ g_free (name);
+ }
+}
+
+static void
+preferences_response_cb (GtkWidget *widget,
+ gint response,
+ GossipPreferences *preferences)
+{
+ gtk_widget_destroy (widget);
+}
+
+static void
+preferences_destroy_cb (GtkWidget *widget,
+ GossipPreferences *preferences)
+{
+ GList *l;
+
+ for (l = preferences->notify_ids; l; l = l->next) {
+ guint id;
+
+ id = GPOINTER_TO_UINT (l->data);
+ gossip_conf_notify_remove (gossip_conf_get (), id);
+ }
+
+ g_list_free (preferences->notify_ids);
+ g_free (preferences);
+}
+
+GtkWidget *
+gossip_preferences_show (GtkWindow *parent)
+{
+ static GossipPreferences *preferences;
+ GladeXML *glade;
+
+ if (preferences) {
+ gtk_window_present (GTK_WINDOW (preferences->dialog));
+ return preferences->dialog;
+ }
+
+ preferences = g_new0 (GossipPreferences, 1);
+
+ glade = gossip_glade_get_file (
+ "gossip-preferences.glade",
+ "preferences_dialog",
+ NULL,
+ "preferences_dialog", &preferences->dialog,
+ "notebook", &preferences->notebook,
+ "checkbutton_show_avatars", &preferences->checkbutton_show_avatars,
+ "checkbutton_compact_contact_list", &preferences->checkbutton_compact_contact_list,
+ "checkbutton_show_smileys", &preferences->checkbutton_show_smileys,
+ "combobox_chat_theme", &preferences->combobox_chat_theme,
+ "checkbutton_theme_chat_room", &preferences->checkbutton_theme_chat_room,
+ "checkbutton_separate_chat_windows", &preferences->checkbutton_separate_chat_windows,
+ "radiobutton_contact_list_sort_by_name", &preferences->radiobutton_contact_list_sort_by_name,
+ "radiobutton_contact_list_sort_by_state", &preferences->radiobutton_contact_list_sort_by_state,
+ "checkbutton_sounds_for_messages", &preferences->checkbutton_sounds_for_messages,
+ "checkbutton_sounds_when_busy", &preferences->checkbutton_sounds_when_busy,
+ "checkbutton_sounds_when_away", &preferences->checkbutton_sounds_when_away,
+ "checkbutton_popups_when_available", &preferences->checkbutton_popups_when_available,
+ "treeview_spell_checker", &preferences->treeview_spell_checker,
+ "checkbutton_spell_checker", &preferences->checkbutton_spell_checker,
+ NULL);
+
+ gossip_glade_connect (glade,
+ preferences,
+ "preferences_dialog", "destroy", preferences_destroy_cb,
+ "preferences_dialog", "response", preferences_response_cb,
+ NULL);
+
+ g_object_unref (glade);
+
+ g_object_add_weak_pointer (G_OBJECT (preferences->dialog), (gpointer) &preferences);
+
+ preferences_themes_setup (preferences);
+
+ preferences_setup_widgets (preferences);
+
+ preferences_languages_setup (preferences);
+ preferences_languages_add (preferences);
+ preferences_languages_load (preferences);
+
+ if (gossip_spell_supported ()) {
+ GtkWidget *page;
+
+ page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (preferences->notebook), 2);
+ gtk_widget_show (page);
+ }
+
+ if (parent) {
+ gtk_window_set_transient_for (GTK_WINDOW (preferences->dialog),
+ GTK_WINDOW (parent));
+ }
+
+ gtk_widget_show (preferences->dialog);
+
+ return preferences->dialog;
+}
+
diff --git a/trunk/libempathy-gtk/gossip-preferences.glade b/trunk/libempathy-gtk/gossip-preferences.glade
new file mode 100644
index 000000000..c5cd51470
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-preferences.glade
@@ -0,0 +1,1092 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+<requires lib="gnome"/>
+
+<widget class="GtkDialog" id="preferences_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Preferences</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="icon_name">gtk-preferences</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox5">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area5">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="button_close">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkNotebook" id="notebook">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_tabs">True</property>
+ <property name="show_border">True</property>
+ <property name="tab_pos">GTK_POS_TOP</property>
+ <property name="scrollable">False</property>
+ <property name="enable_popup">False</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox197">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+
+ <child>
+ <widget class="GtkFrame" id="frame3">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment11">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox199">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_show_avatars">
+ <property name="visible">True</property>
+ <property name="tooltip" translatable="yes">Avatars are user chosen images shown in the contact list</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Show _avatars</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">True</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_compact_contact_list">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Show co_mpact contact list</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_show_smileys">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Show _smileys as images</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">True</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label611">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Appearance&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkFrame" id="frame4">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment12">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_separate_chat_windows">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Open new chats in separate windows</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label612">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Behaviour&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkFrame" id="frame13">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment31">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox217">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkRadioButton" id="radiobutton_contact_list_sort_by_name">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Sort by _name</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkRadioButton" id="radiobutton_contact_list_sort_by_state">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Sort by s_tate</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radiobutton_contact_list_sort_by_name</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label644">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Contact List&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label602">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">General</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="outer_vbox">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+
+ <child>
+ <widget class="GtkFrame" id="frame5">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment13">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox106">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_sounds_for_messages">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Play sound when messages arrive</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_sounds_when_busy">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Enable sounds when _busy</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_sounds_when_away">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Enable sounds when _away</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label613">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Audio&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkFrame" id="frame6">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment14">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_popups_when_available">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Display notifications when contacts come _online</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label614">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Visual&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label607">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Notifications</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox168">
+ <property name="border_width">12</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+
+ <child>
+ <widget class="GtkFrame" id="frame7">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment15">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox201">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox153">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox154">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow18">
+ <property name="visible">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="treeview_spell_checker">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox155">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkImage" id="image422">
+ <property name="visible">True</property>
+ <property name="stock">gtk-dialog-info</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label616">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;small&gt;The list of languages reflects only the languages for which you have a dictionary installed.&lt;/small&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label615">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Languages&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkFrame" id="frame8">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment16">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_spell_checker">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Enable spell checking</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label617">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Options&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label567">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Spell Checking</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox206">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+
+ <child>
+ <widget class="GtkFrame" id="frame11">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment19">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox207">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox139">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+
+ <child>
+ <widget class="GtkLabel" id="label586">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Chat Th_eme:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">combobox_chat_theme</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkComboBox" id="combobox_chat_theme">
+ <property name="visible">True</property>
+ <property name="items" translatable="yes"></property>
+ <property name="add_tearoffs">False</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label626">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Appearance&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkFrame" id="frame12">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="label_yalign">0.5</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+
+ <child>
+ <widget class="GtkAlignment" id="alignment20">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">1</property>
+ <property name="yscale">1</property>
+ <property name="top_padding">6</property>
+ <property name="bottom_padding">0</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">0</property>
+
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_theme_chat_room">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Use for chat rooms</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label627">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Options&lt;/b&gt;</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">False</property>
+ <property name="tab_fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label624">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Themes</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/trunk/libempathy-gtk/gossip-preferences.h b/trunk/libempathy-gtk/gossip-preferences.h
new file mode 100644
index 000000000..35b26621f
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-preferences.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_PREFERENCES_H__
+#define __GOSSIP_PREFERENCES_H__
+
+#include <gtk/gtkwindow.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_PREFS_PATH "/apps/empathy"
+
+#define GOSSIP_PREFS_SOUNDS_FOR_MESSAGES GOSSIP_PREFS_PATH "/notifications/sounds_for_messages"
+#define GOSSIP_PREFS_SOUNDS_WHEN_AWAY GOSSIP_PREFS_PATH "/notifications/sounds_when_away"
+#define GOSSIP_PREFS_SOUNDS_WHEN_BUSY GOSSIP_PREFS_PATH "/notifications/sounds_when_busy"
+#define GOSSIP_PREFS_POPUPS_WHEN_AVAILABLE GOSSIP_PREFS_PATH "/notifications/popups_when_available"
+#define GOSSIP_PREFS_CHAT_SHOW_SMILEYS GOSSIP_PREFS_PATH "/conversation/graphical_smileys"
+#define GOSSIP_PREFS_CHAT_THEME GOSSIP_PREFS_PATH "/conversation/theme"
+#define GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM GOSSIP_PREFS_PATH "/conversation/theme_chat_room"
+#define GOSSIP_PREFS_CHAT_SPELL_CHECKER_LANGUAGES GOSSIP_PREFS_PATH "/conversation/spell_checker_languages"
+#define GOSSIP_PREFS_CHAT_SPELL_CHECKER_ENABLED GOSSIP_PREFS_PATH "/conversation/spell_checker_enabled"
+#define GOSSIP_PREFS_UI_SEPARATE_CHAT_WINDOWS GOSSIP_PREFS_PATH "/ui/separate_chat_windows"
+#define GOSSIP_PREFS_UI_MAIN_WINDOW_HIDDEN GOSSIP_PREFS_PATH "/ui/main_window_hidden"
+#define GOSSIP_PREFS_UI_AVATAR_DIRECTORY GOSSIP_PREFS_PATH "/ui/avatar_directory"
+#define GOSSIP_PREFS_UI_SHOW_AVATARS GOSSIP_PREFS_PATH "/ui/show_avatars"
+#define GOSSIP_PREFS_UI_COMPACT_CONTACT_LIST GOSSIP_PREFS_PATH "/ui/compact_contact_list"
+#define GOSSIP_PREFS_CONTACTS_SHOW_OFFLINE GOSSIP_PREFS_PATH "/contacts/show_offline"
+#define GOSSIP_PREFS_CONTACTS_SORT_CRITERIUM GOSSIP_PREFS_PATH "/contacts/sort_criterium"
+#define GOSSIP_PREFS_HINTS_CLOSE_MAIN_WINDOW GOSSIP_PREFS_PATH "/hints/close_main_window"
+
+GtkWidget * gossip_preferences_show (GtkWindow *parent);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_PREFERENCES_H__ */
+
+
diff --git a/trunk/libempathy-gtk/gossip-presence-chooser.c b/trunk/libempathy-gtk/gossip-presence-chooser.c
new file mode 100644
index 000000000..e92de9efa
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-presence-chooser.c
@@ -0,0 +1,1022 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <libmissioncontrol/mission-control.h>
+
+#include <libempathy/gossip-utils.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/empathy-marshal.h>
+
+#include "gossip-ui-utils.h"
+#include "empathy-images.h"
+#include "gossip-presence-chooser.h"
+#include "gossip-status-presets.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_PRESENCE_CHOOSER, GossipPresenceChooserPriv))
+
+#define DEBUG_DOMAIN "PresenceChooser"
+
+/* Flashing delay for icons (milliseconds). */
+#define FLASH_TIMEOUT 500
+
+typedef struct {
+ MissionControl *mc;
+
+ GtkWidget *hbox;
+ GtkWidget *image;
+ GtkWidget *label;
+ GtkWidget *menu;
+
+ McPresence last_state;
+
+ McPresence flash_state_1;
+ McPresence flash_state_2;
+ guint flash_timeout_id;
+
+ /* The handle the kind of unnessecary scroll support. */
+ guint scroll_timeout_id;
+ McPresence scroll_state;
+ gchar *scroll_status;
+} GossipPresenceChooserPriv;
+
+typedef struct {
+ McPresence state;
+ const gchar *status;
+} StateAndStatus;
+
+/* States to be listed in the menu */
+static McPresence states[] = {MC_PRESENCE_AVAILABLE,
+ MC_PRESENCE_DO_NOT_DISTURB,
+ MC_PRESENCE_AWAY};
+
+static void gossip_presence_chooser_class_init (GossipPresenceChooserClass *klass);
+static void gossip_presence_chooser_init (GossipPresenceChooser *chooser);
+static void presence_chooser_finalize (GObject *object);
+static void presence_chooser_presence_changed_cb (MissionControl *mc,
+ McPresence state,
+ GossipPresenceChooser *chooser);
+static void presence_chooser_reset_scroll_timeout (GossipPresenceChooser *chooser);
+static gboolean presence_chooser_scroll_timeout_cb (GossipPresenceChooser *chooser);
+static gboolean presence_chooser_scroll_event_cb (GossipPresenceChooser *chooser,
+ GdkEventScroll *event,
+ gpointer user_data);
+static GList * presence_chooser_get_presets (GossipPresenceChooser *chooser);
+static StateAndStatus *presence_chooser_state_and_status_new (McPresence state,
+ const gchar *status);
+static gboolean presence_chooser_flash_timeout_cb (GossipPresenceChooser *chooser);
+void gossip_presence_chooser_flash_start (GossipPresenceChooser *chooser,
+ McPresence state_1,
+ McPresence state_2);
+void gossip_presence_chooser_flash_stop (GossipPresenceChooser *chooser,
+ McPresence state);
+gboolean gossip_presence_chooser_is_flashing (GossipPresenceChooser *chooser);
+static gboolean presence_chooser_button_press_event_cb (GtkWidget *chooser,
+ GdkEventButton *event,
+ gpointer user_data);
+static void presence_chooser_toggled_cb (GtkWidget *chooser,
+ gpointer user_data);
+static void presence_chooser_menu_popup (GossipPresenceChooser *chooser);
+static void presence_chooser_menu_popdown (GossipPresenceChooser *chooser);
+static void presence_chooser_menu_selection_done_cb (GtkMenuShell *menushell,
+ GossipPresenceChooser *chooser);
+static void presence_chooser_menu_destroy_cb (GtkWidget *menu,
+ GossipPresenceChooser *chooser);
+static void presence_chooser_menu_detach (GtkWidget *attach_widget,
+ GtkMenu *menu);
+static void presence_chooser_menu_align_func (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ GtkWidget *widget);
+static void presence_chooser_menu_add_item (GtkWidget *menu,
+ const gchar *str,
+ McPresence state,
+ gboolean custom);
+static void presence_chooser_clear_activate_cb (GtkWidget *item,
+ gpointer user_data);
+static void presence_chooser_clear_response_cb (GtkWidget *widget,
+ gint response,
+ gpointer user_data);
+static void presence_chooser_noncustom_activate_cb (GtkWidget *item,
+ gpointer user_data);
+static void presence_chooser_set_state (McPresence state,
+ const gchar *status,
+ gboolean save);
+static void presence_chooser_custom_activate_cb (GtkWidget *item,
+ gpointer user_data);
+static void presence_chooser_show_dialog (McPresence state);
+static void presence_chooser_dialog_response_cb (GtkWidget *dialog,
+ gint response,
+ gpointer user_data);
+
+G_DEFINE_TYPE (GossipPresenceChooser, gossip_presence_chooser, GTK_TYPE_TOGGLE_BUTTON);
+
+static void
+gossip_presence_chooser_class_init (GossipPresenceChooserClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = presence_chooser_finalize;
+
+ g_type_class_add_private (object_class, sizeof (GossipPresenceChooserPriv));
+}
+
+static void
+gossip_presence_chooser_init (GossipPresenceChooser *chooser)
+{
+ GossipPresenceChooserPriv *priv;
+ GtkWidget *arrow;
+ GtkWidget *alignment;
+ McPresence state;
+
+ priv = GET_PRIV (chooser);
+
+ gtk_button_set_relief (GTK_BUTTON (chooser), GTK_RELIEF_NONE);
+ gtk_button_set_focus_on_click (GTK_BUTTON (chooser), FALSE);
+
+ alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
+ gtk_widget_show (alignment);
+ gtk_container_add (GTK_CONTAINER (chooser), alignment);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 1, 0);
+
+ priv->hbox = gtk_hbox_new (FALSE, 1);
+ gtk_widget_show (priv->hbox);
+ gtk_container_add (GTK_CONTAINER (alignment), priv->hbox);
+
+ priv->image = gtk_image_new ();
+ gtk_widget_show (priv->image);
+ gtk_box_pack_start (GTK_BOX (priv->hbox), priv->image, FALSE, TRUE, 0);
+
+ priv->label = gtk_label_new (NULL);
+ gtk_widget_show (priv->label);
+ gtk_box_pack_start (GTK_BOX (priv->hbox), priv->label, TRUE, TRUE, 0);
+ gtk_label_set_ellipsize (GTK_LABEL (priv->label), PANGO_ELLIPSIZE_END);
+ gtk_misc_set_alignment (GTK_MISC (priv->label), 0, 0.5);
+ gtk_misc_set_padding (GTK_MISC (priv->label), 4, 1);
+
+ alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
+ gtk_widget_show (alignment);
+ gtk_box_pack_start (GTK_BOX (priv->hbox), alignment, FALSE, FALSE, 0);
+
+ arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
+ gtk_widget_show (arrow);
+ gtk_container_add (GTK_CONTAINER (alignment), arrow);
+
+ g_signal_connect (chooser, "toggled",
+ G_CALLBACK (presence_chooser_toggled_cb),
+ NULL);
+ g_signal_connect (chooser, "button-press-event",
+ G_CALLBACK (presence_chooser_button_press_event_cb),
+ NULL);
+ g_signal_connect (chooser, "scroll-event",
+ G_CALLBACK (presence_chooser_scroll_event_cb),
+ NULL);
+
+ priv->mc = gossip_mission_control_new ();
+ state = mission_control_get_presence_actual (priv->mc, NULL);
+ presence_chooser_presence_changed_cb (priv->mc, state, chooser);
+ dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->mc),
+ "PresenceStatusActual",
+ G_CALLBACK (presence_chooser_presence_changed_cb),
+ chooser, NULL);
+}
+
+static void
+presence_chooser_finalize (GObject *object)
+{
+ GossipPresenceChooserPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ if (priv->flash_timeout_id) {
+ g_source_remove (priv->flash_timeout_id);
+ }
+
+ if (priv->scroll_timeout_id) {
+ g_source_remove (priv->scroll_timeout_id);
+ }
+
+ dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->mc),
+ "PresenceStatusActual",
+ G_CALLBACK (presence_chooser_presence_changed_cb),
+ object);
+ g_object_unref (priv->mc);
+
+ G_OBJECT_CLASS (gossip_presence_chooser_parent_class)->finalize (object);
+}
+
+GtkWidget *
+gossip_presence_chooser_new (void)
+{
+ GtkWidget *chooser;
+
+ chooser = g_object_new (GOSSIP_TYPE_PRESENCE_CHOOSER, NULL);
+
+ return chooser;
+}
+
+static void
+presence_chooser_presence_changed_cb (MissionControl *mc,
+ McPresence state,
+ GossipPresenceChooser *chooser)
+{
+ GossipPresenceChooserPriv *priv;
+ gchar *status;
+
+ priv = GET_PRIV (chooser);
+
+ status = mission_control_get_presence_message_actual (priv->mc, NULL);
+ if (G_STR_EMPTY (status)) {
+ g_free (status);
+ status = g_strdup (gossip_presence_state_get_default_status (state));
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Presence changed to %s (%d)",
+ status, state);
+
+ presence_chooser_reset_scroll_timeout (chooser);
+ gossip_presence_chooser_flash_stop (chooser, state);
+ gtk_label_set_text (GTK_LABEL (priv->label), status);
+
+ g_free (status);
+}
+
+static void
+presence_chooser_reset_scroll_timeout (GossipPresenceChooser *chooser)
+{
+ GossipPresenceChooserPriv *priv;
+
+ priv = GET_PRIV (chooser);
+
+ if (priv->scroll_timeout_id) {
+ g_source_remove (priv->scroll_timeout_id);
+ priv->scroll_timeout_id = 0;
+ }
+
+ g_free (priv->scroll_status);
+ priv->scroll_status = NULL;
+}
+
+static gboolean
+presence_chooser_scroll_timeout_cb (GossipPresenceChooser *chooser)
+{
+ GossipPresenceChooserPriv *priv;
+
+ priv = GET_PRIV (chooser);
+
+ priv->scroll_timeout_id = 0;
+
+ gossip_debug (DEBUG_DOMAIN, "Setting presence to %s (%d)",
+ priv->scroll_status, priv->scroll_state);
+
+ mission_control_set_presence (priv->mc,
+ priv->scroll_state,
+ priv->scroll_status,
+ NULL, NULL);
+
+ g_free (priv->scroll_status);
+ priv->scroll_status = NULL;
+
+ return FALSE;
+}
+
+static gboolean
+presence_chooser_scroll_event_cb (GossipPresenceChooser *chooser,
+ GdkEventScroll *event,
+ gpointer user_data)
+{
+ GossipPresenceChooserPriv *priv;
+ GList *list, *l;
+ const gchar *current_status;
+ StateAndStatus *sas;
+ gboolean match;
+
+ priv = GET_PRIV (chooser);
+
+ switch (event->direction) {
+ case GDK_SCROLL_UP:
+ break;
+ case GDK_SCROLL_DOWN:
+ break;
+ default:
+ return FALSE;
+ }
+
+ current_status = gtk_label_get_text (GTK_LABEL (priv->label));
+
+ /* Get the list of presets, which in this context means all the items
+ * without a trailing "...".
+ */
+ list = presence_chooser_get_presets (chooser);
+ sas = NULL;
+ match = FALSE;
+ for (l = list; l; l = l->next) {
+ sas = l->data;
+
+ if (sas->state == priv->last_state &&
+ strcmp (sas->status, current_status) == 0) {
+ sas = NULL;
+ match = TRUE;
+ if (event->direction == GDK_SCROLL_UP) {
+ if (l->prev) {
+ sas = l->prev->data;
+ }
+ }
+ else if (event->direction == GDK_SCROLL_DOWN) {
+ if (l->next) {
+ sas = l->next->data;
+ }
+ }
+ break;
+ }
+
+ sas = NULL;
+ }
+
+ if (sas) {
+ presence_chooser_reset_scroll_timeout (chooser);
+
+ priv->scroll_status = g_strdup (sas->status);
+ priv->scroll_state = sas->state;
+
+ priv->scroll_timeout_id =
+ g_timeout_add (500,
+ (GSourceFunc) presence_chooser_scroll_timeout_cb,
+ chooser);
+
+ gossip_presence_chooser_flash_stop (chooser, sas->state);
+ gtk_label_set_text (GTK_LABEL (priv->label), sas->status);
+ }
+ else if (!match) {
+ const gchar *status;
+ /* If we didn't get any match at all, it means the last state
+ * was a custom one. Just switch to the first one.
+ */
+ status = gossip_presence_state_get_default_status (states[0]);
+
+ gossip_debug (DEBUG_DOMAIN, "Setting presence to %s (%d)",
+ status, states[0]);
+
+ presence_chooser_reset_scroll_timeout (chooser);
+ mission_control_set_presence (priv->mc,
+ states[0],
+ status,
+ NULL, NULL);
+ }
+
+ g_list_foreach (list, (GFunc) g_free, NULL);
+ g_list_free (list);
+
+ return TRUE;
+}
+
+static GList *
+presence_chooser_get_presets (GossipPresenceChooser *chooser)
+{
+ GList *list = NULL;
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (states); i++) {
+ GList *presets, *p;
+ StateAndStatus *sas;
+ const gchar *status;
+
+ status = gossip_presence_state_get_default_status (states[i]);
+ sas = presence_chooser_state_and_status_new (states[i], status);
+ list = g_list_append (list, sas);
+
+ presets = gossip_status_presets_get (states[i], 5);
+ for (p = presets; p; p = p->next) {
+ sas = presence_chooser_state_and_status_new (states[i], p->data);
+ list = g_list_append (list, sas);
+ }
+ g_list_free (presets);
+ }
+
+ return list;
+}
+
+static StateAndStatus *
+presence_chooser_state_and_status_new (McPresence state,
+ const gchar *status)
+{
+ StateAndStatus *sas;
+
+ sas = g_new0 (StateAndStatus, 1);
+
+ sas->state = state;
+ sas->status = status;
+
+ return sas;
+}
+
+static gboolean
+presence_chooser_flash_timeout_cb (GossipPresenceChooser *chooser)
+{
+ GossipPresenceChooserPriv *priv;
+ McPresence state;
+ static gboolean on = FALSE;
+
+ priv = GET_PRIV (chooser);
+
+ if (on) {
+ state = priv->flash_state_1;
+ } else {
+ state = priv->flash_state_2;
+ }
+
+ gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
+ gossip_icon_name_for_presence_state (state),
+ GTK_ICON_SIZE_MENU);
+
+ on = !on;
+
+ return TRUE;
+}
+
+void
+gossip_presence_chooser_flash_start (GossipPresenceChooser *chooser,
+ McPresence state_1,
+ McPresence state_2)
+{
+ GossipPresenceChooserPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_PRESENCE_CHOOSER (chooser));
+
+ priv = GET_PRIV (chooser);
+
+ if (priv->flash_timeout_id != 0) {
+ return;
+ }
+
+ priv->flash_state_1 = state_1;
+ priv->flash_state_2 = state_2;
+
+ priv->flash_timeout_id = g_timeout_add (FLASH_TIMEOUT,
+ (GSourceFunc) presence_chooser_flash_timeout_cb,
+ chooser);
+}
+
+void
+gossip_presence_chooser_flash_stop (GossipPresenceChooser *chooser,
+ McPresence state)
+{
+ GossipPresenceChooserPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_PRESENCE_CHOOSER (chooser));
+
+ priv = GET_PRIV (chooser);
+
+ if (priv->flash_timeout_id) {
+ g_source_remove (priv->flash_timeout_id);
+ priv->flash_timeout_id = 0;
+ }
+
+ gtk_image_set_from_icon_name (GTK_IMAGE (priv->image),
+ gossip_icon_name_for_presence_state (state),
+ GTK_ICON_SIZE_MENU);
+
+ priv->last_state = state;
+}
+
+gboolean
+gossip_presence_chooser_is_flashing (GossipPresenceChooser *chooser)
+{
+ GossipPresenceChooserPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_PRESENCE_CHOOSER (chooser), FALSE);
+
+ priv = GET_PRIV (chooser);
+
+ if (priv->flash_timeout_id) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+presence_chooser_button_press_event_cb (GtkWidget *chooser,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ if (event->button != 1 || event->type != GDK_BUTTON_PRESS) {
+ return FALSE;
+ }
+
+ if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (chooser))) {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chooser), TRUE);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+presence_chooser_toggled_cb (GtkWidget *chooser,
+ gpointer user_data)
+{
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (chooser))) {
+ presence_chooser_menu_popup (GOSSIP_PRESENCE_CHOOSER (chooser));
+ } else {
+ presence_chooser_menu_popdown (GOSSIP_PRESENCE_CHOOSER (chooser));
+ }
+}
+
+static void
+presence_chooser_menu_popup (GossipPresenceChooser *chooser)
+{
+ GossipPresenceChooserPriv *priv;
+ GtkWidget *menu;
+
+ priv = GET_PRIV (chooser);
+
+ if (priv->menu) {
+ return;
+ }
+
+ menu = gossip_presence_chooser_create_menu ();
+
+ g_signal_connect_after (menu, "selection-done",
+ G_CALLBACK (presence_chooser_menu_selection_done_cb),
+ chooser);
+
+ g_signal_connect (menu, "destroy",
+ G_CALLBACK (presence_chooser_menu_destroy_cb),
+ chooser);
+
+ gtk_menu_attach_to_widget (GTK_MENU (menu),
+ GTK_WIDGET (chooser),
+ presence_chooser_menu_detach);
+
+ gtk_menu_popup (GTK_MENU (menu),
+ NULL, NULL,
+ (GtkMenuPositionFunc) presence_chooser_menu_align_func,
+ chooser,
+ 1,
+ gtk_get_current_event_time ());
+
+ priv->menu = menu;
+}
+
+static void
+presence_chooser_menu_popdown (GossipPresenceChooser *chooser)
+{
+ GossipPresenceChooserPriv *priv;
+
+ priv = GET_PRIV (chooser);
+
+ if (priv->menu) {
+ gtk_widget_destroy (priv->menu);
+ }
+}
+
+static void
+presence_chooser_menu_selection_done_cb (GtkMenuShell *menushell,
+ GossipPresenceChooser *chooser)
+{
+ gtk_widget_destroy (GTK_WIDGET (menushell));
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chooser), FALSE);
+}
+
+static void
+presence_chooser_menu_destroy_cb (GtkWidget *menu,
+ GossipPresenceChooser *chooser)
+{
+ GossipPresenceChooserPriv *priv;
+
+ priv = GET_PRIV (chooser);
+
+ priv->menu = NULL;
+}
+
+static void
+presence_chooser_menu_detach (GtkWidget *attach_widget,
+ GtkMenu *menu)
+{
+ /* We don't need to do anything, but attaching the menu means
+ * we don't own the ref count and it is cleaned up properly.
+ */
+}
+
+static void
+presence_chooser_menu_align_func (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ GtkWidget *widget)
+{
+ GtkRequisition req;
+ GdkScreen *screen;
+ gint screen_height;
+
+ gtk_widget_size_request (GTK_WIDGET (menu), &req);
+
+ gdk_window_get_origin (widget->window, x, y);
+
+ *x += widget->allocation.x + 1;
+ *y += widget->allocation.y;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (menu));
+ screen_height = gdk_screen_get_height (screen);
+
+ if (req.height > screen_height) {
+ /* Too big for screen height anyway. */
+ *y = 0;
+ return;
+ }
+
+ if ((*y + req.height + widget->allocation.height) > screen_height) {
+ /* Can't put it below the button. */
+ *y -= req.height;
+ *y += 1;
+ } else {
+ /* Put menu below button. */
+ *y += widget->allocation.height;
+ *y -= 1;
+ }
+
+ *push_in = FALSE;
+}
+
+GtkWidget *
+gossip_presence_chooser_create_menu (void)
+{
+ const gchar *status;
+ GtkWidget *menu;
+ GtkWidget *item;
+ GtkWidget *image;
+ guint i;
+
+ menu = gtk_menu_new ();
+
+ for (i = 0; i < G_N_ELEMENTS (states); i++) {
+ GList *list, *l;
+
+ status = gossip_presence_state_get_default_status (states[i]);
+ presence_chooser_menu_add_item (menu,
+ status,
+ states[i],
+ FALSE);
+
+ list = gossip_status_presets_get (states[i], 5);
+ for (l = list; l; l = l->next) {
+ presence_chooser_menu_add_item (menu,
+ l->data,
+ states[i],
+ FALSE);
+ }
+ g_list_free (list);
+
+ presence_chooser_menu_add_item (menu,
+ _("Custom message..."),
+ states[i],
+ TRUE);
+
+ /* Separator. */
+ item = gtk_menu_item_new ();
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+ }
+
+ /* Offline to disconnect */
+ status = gossip_presence_state_get_default_status (MC_PRESENCE_OFFLINE);
+ presence_chooser_menu_add_item (menu,
+ status,
+ MC_PRESENCE_OFFLINE,
+ FALSE);
+ /* Separator. */
+ item = gtk_menu_item_new ();
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ /* Clear list */
+ item = gtk_image_menu_item_new_with_label (_("Clear List..."));
+ image = gtk_image_new_from_stock (GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (image);
+ gtk_widget_show (item);
+
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (presence_chooser_clear_activate_cb),
+ NULL);
+
+ return menu;
+}
+
+static void
+presence_chooser_menu_add_item (GtkWidget *menu,
+ const gchar *str,
+ McPresence state,
+ gboolean custom)
+{
+ GtkWidget *item;
+ GtkWidget *image;
+ const gchar *icon_name;
+
+ item = gtk_image_menu_item_new_with_label (str);
+ icon_name = gossip_icon_name_for_presence_state (state);
+
+ if (custom) {
+ g_signal_connect (item, "activate",
+ G_CALLBACK (presence_chooser_custom_activate_cb),
+ NULL);
+ } else {
+ g_signal_connect (item, "activate",
+ G_CALLBACK (presence_chooser_noncustom_activate_cb),
+ NULL);
+ }
+
+ image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image);
+
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+ gtk_widget_show (item);
+
+ g_object_set_data_full (G_OBJECT (item),
+ "status", g_strdup (str),
+ (GDestroyNotify) g_free);
+
+ g_object_set_data (G_OBJECT (item), "state", GINT_TO_POINTER (state));
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+}
+
+static void
+presence_chooser_clear_activate_cb (GtkWidget *item,
+ gpointer user_data)
+{
+ GtkWidget *dialog;
+ GtkWidget *toplevel;
+ GtkWindow *parent = NULL;
+
+ toplevel = gtk_widget_get_toplevel (item);
+ if (GTK_WIDGET_TOPLEVEL (toplevel) &&
+ GTK_IS_WINDOW (toplevel)) {
+ GtkWindow *window;
+ gboolean visible;
+
+ window = GTK_WINDOW (toplevel);
+ visible = gossip_window_get_is_visible (window);
+
+ if (visible) {
+ parent = window;
+ }
+ }
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (parent),
+ 0,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("Are you sure you want to clear the list?"));
+
+ gtk_message_dialog_format_secondary_text (
+ GTK_MESSAGE_DIALOG (dialog),
+ _("This will remove any custom messages you have "
+ "added to the list of preset status messages."));
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ _("Clear List"), GTK_RESPONSE_OK,
+ NULL);
+
+ gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), FALSE);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (presence_chooser_clear_response_cb),
+ NULL);
+
+ gtk_widget_show (dialog);
+}
+
+static void
+presence_chooser_clear_response_cb (GtkWidget *widget,
+ gint response,
+ gpointer user_data)
+{
+ if (response == GTK_RESPONSE_OK) {
+ gossip_status_presets_reset ();
+ }
+
+ gtk_widget_destroy (widget);
+}
+
+static void
+presence_chooser_noncustom_activate_cb (GtkWidget *item,
+ gpointer user_data)
+{
+ McPresence state;
+ const gchar *status;
+
+ status = g_object_get_data (G_OBJECT (item), "status");
+ state = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "state"));
+
+ presence_chooser_set_state (state, status, FALSE);
+}
+
+static void
+presence_chooser_set_state (McPresence state,
+ const gchar *status,
+ gboolean save)
+{
+ const gchar *default_status;
+ MissionControl *mc;
+
+ default_status = gossip_presence_state_get_default_status (state);
+
+ if (G_STR_EMPTY (status)) {
+ status = default_status;
+ } else {
+ /* Only store the value if it differs from the default ones. */
+ if (save && strcmp (status, default_status) != 0) {
+ gossip_status_presets_set_last (state, status);
+ }
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Setting presence to %s (%d)",
+ status, state);
+
+ mc = gossip_mission_control_new ();
+ mission_control_set_presence (mc, state, status, NULL, NULL);
+ g_object_unref (mc);
+}
+
+static void
+presence_chooser_custom_activate_cb (GtkWidget *item,
+ gpointer user_data)
+{
+ McPresence state;
+
+ state = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "state"));
+
+ presence_chooser_show_dialog (state);
+}
+
+static void
+presence_chooser_show_dialog (McPresence state)
+{
+ static GtkWidget *dialog = NULL;
+ static GtkListStore *store[LAST_MC_PRESENCE];
+ GladeXML *glade;
+ GtkWidget *image;
+ GtkWidget *combo;
+ GtkWidget *entry;
+ GtkWidget *checkbutton;
+ const gchar *default_status;
+
+ if (dialog) {
+ gtk_widget_destroy (dialog);
+ dialog = NULL;
+ } else {
+ guint i;
+
+ for (i = 0; i < LAST_MC_PRESENCE; i++) {
+ store[i] = NULL;
+ }
+ }
+
+ glade = gossip_glade_get_file ("gossip-presence-chooser.glade",
+ "status_message_dialog",
+ NULL,
+ "status_message_dialog", &dialog,
+ "comboentry_status", &combo,
+ "image_status", &image,
+ "checkbutton_add", &checkbutton,
+ NULL);
+
+ g_object_unref (glade);
+
+ g_signal_connect (dialog, "destroy",
+ G_CALLBACK (gtk_widget_destroyed),
+ &dialog);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (presence_chooser_dialog_response_cb),
+ NULL);
+
+ gtk_image_set_from_icon_name (GTK_IMAGE (image),
+ gossip_icon_name_for_presence_state (state),
+ GTK_ICON_SIZE_MENU);
+
+ if (!store[state]) {
+ GList *presets, *l;
+ GtkTreeIter iter;
+
+ store[state] = gtk_list_store_new (1, G_TYPE_STRING);
+
+ presets = gossip_status_presets_get (state, -1);
+ for (l = presets; l; l = l->next) {
+ gtk_list_store_append (store[state], &iter);
+ gtk_list_store_set (store[state], &iter, 0, l->data, -1);
+ }
+
+ g_list_free (presets);
+ }
+
+ default_status = gossip_presence_state_get_default_status (state);
+
+ entry = GTK_BIN (combo)->child;
+ gtk_entry_set_text (GTK_ENTRY (entry), default_status);
+ gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+ gtk_entry_set_width_chars (GTK_ENTRY (entry), 25);
+
+ gtk_combo_box_set_model (GTK_COMBO_BOX (combo), GTK_TREE_MODEL (store[state]));
+ gtk_combo_box_entry_set_text_column (GTK_COMBO_BOX_ENTRY (combo), 0);
+
+ /* FIXME: Set transian for a window ? */
+
+ g_object_set_data (G_OBJECT (dialog), "store", store[state]);
+ g_object_set_data (G_OBJECT (dialog), "entry", entry);
+ g_object_set_data (G_OBJECT (dialog), "checkbutton", checkbutton);
+ g_object_set_data (G_OBJECT (dialog), "state", GINT_TO_POINTER (state));
+
+ gtk_widget_show_all (dialog);
+}
+
+static void
+presence_chooser_dialog_response_cb (GtkWidget *dialog,
+ gint response,
+ gpointer user_data)
+{
+ if (response == GTK_RESPONSE_OK) {
+ GtkWidget *entry;
+ GtkWidget *checkbutton;
+ GtkListStore *store;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ McPresence state;
+ const gchar *status;
+ gboolean save;
+ gboolean duplicate = FALSE;
+ gboolean has_next;
+
+ entry = g_object_get_data (G_OBJECT (dialog), "entry");
+ status = gtk_entry_get_text (GTK_ENTRY (entry));
+ store = g_object_get_data (G_OBJECT (dialog), "store");
+ model = GTK_TREE_MODEL (store);
+
+ has_next = gtk_tree_model_get_iter_first (model, &iter);
+ while (has_next) {
+ gchar *str;
+
+ gtk_tree_model_get (model, &iter,
+ 0, &str,
+ -1);
+
+ if (strcmp (status, str) == 0) {
+ g_free (str);
+ duplicate = TRUE;
+ break;
+ }
+
+ g_free (str);
+
+ has_next = gtk_tree_model_iter_next (model, &iter);
+ }
+
+ if (!duplicate) {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, status, -1);
+ }
+
+ checkbutton = g_object_get_data (G_OBJECT (dialog), "checkbutton");
+ save = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbutton));
+ state = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dialog), "state"));
+
+ presence_chooser_set_state (state, status, save);
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
diff --git a/trunk/libempathy-gtk/gossip-presence-chooser.glade b/trunk/libempathy-gtk/gossip-presence-chooser.glade
new file mode 100644
index 000000000..f9f1d4eb0
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-presence-chooser.glade
@@ -0,0 +1,173 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+<requires lib="gnome"/>
+
+<widget class="GtkDialog" id="status_message_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Status Message Presets</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="modal">False</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox6">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area6">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="cancelbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="okbutton3">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox111">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox90">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkImage" id="image_status">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label468">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Enter status message:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkComboBoxEntry" id="comboentry_status">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="has_frame">True</property>
+ <property name="focus_on_click">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_add">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Add to status message list</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/trunk/libempathy-gtk/gossip-presence-chooser.h b/trunk/libempathy-gtk/gossip-presence-chooser.h
new file mode 100644
index 000000000..f78c96c19
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-presence-chooser.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_PRESENCE_CHOOSER_H__
+#define __GOSSIP_PRESENCE_CHOOSER_H__
+
+#include <gtk/gtk.h>
+
+#include <libempathy/gossip-presence.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_PRESENCE_CHOOSER (gossip_presence_chooser_get_type ())
+#define GOSSIP_PRESENCE_CHOOSER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_PRESENCE_CHOOSER, GossipPresenceChooser))
+#define GOSSIP_PRESENCE_CHOOSER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_PRESENCE_CHOOSER, GossipPresenceChooserClass))
+#define GOSSIP_IS_PRESENCE_CHOOSER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_PRESENCE_CHOOSER))
+#define GOSSIP_IS_PRESENCE_CHOOSER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_PRESENCE_CHOOSER))
+#define GOSSIP_PRESENCE_CHOOSER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_PRESENCE_CHOOSER, GossipPresenceChooserClass))
+
+typedef struct _GossipPresenceChooser GossipPresenceChooser;
+typedef struct _GossipPresenceChooserClass GossipPresenceChooserClass;
+
+struct _GossipPresenceChooser {
+ GtkToggleButton parent;
+};
+
+struct _GossipPresenceChooserClass {
+ GtkToggleButtonClass parent_class;
+};
+
+GType gossip_presence_chooser_get_type (void) G_GNUC_CONST;
+GtkWidget *gossip_presence_chooser_new (void);
+GtkWidget *gossip_presence_chooser_create_menu (void);
+void gossip_presence_chooser_flash_start (GossipPresenceChooser *chooser,
+ McPresence state_1,
+ McPresence state_2);
+void gossip_presence_chooser_flash_stop (GossipPresenceChooser *chooser,
+ McPresence state);
+gboolean gossip_presence_chooser_is_flashing (GossipPresenceChooser *chooser);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_PRESENCE_CHOOSER_H__ */
+
diff --git a/trunk/libempathy-gtk/gossip-private-chat.c b/trunk/libempathy-gtk/gossip-private-chat.c
new file mode 100644
index 000000000..12e83dccc
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-private-chat.c
@@ -0,0 +1,374 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+
+#include <libempathy/gossip-debug.h>
+#include <libempathy/empathy-tp-chat.h>
+#include <libempathy/empathy-tp-contact-list.h>
+#include <libempathy/empathy-contact-manager.h>
+//#include <libgossip/gossip-log.h>
+
+#include "gossip-private-chat.h"
+#include "gossip-chat-view.h"
+#include "gossip-chat.h"
+#include "gossip-preferences.h"
+//#include "gossip-sound.h"
+#include "empathy-images.h"
+#include "gossip-ui-utils.h"
+
+#define DEBUG_DOMAIN "PrivateChat"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_PRIVATE_CHAT, GossipPrivateChatPriv))
+
+struct _GossipPrivateChatPriv {
+ GossipContact *contact;
+ gchar *name;
+
+ gboolean is_online;
+
+ GtkWidget *widget;
+ GtkWidget *text_view_sw;
+};
+
+static void gossip_private_chat_class_init (GossipPrivateChatClass *klass);
+static void gossip_private_chat_init (GossipPrivateChat *chat);
+static void private_chat_finalize (GObject *object);
+static void private_chat_create_ui (GossipPrivateChat *chat);
+static void private_chat_contact_presence_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipPrivateChat *chat);
+static void private_chat_contact_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipPrivateChat *chat);
+static void private_chat_widget_destroy_cb (GtkWidget *widget,
+ GossipPrivateChat *chat);
+static const gchar * private_chat_get_name (GossipChat *chat);
+static gchar * private_chat_get_tooltip (GossipChat *chat);
+static const gchar * private_chat_get_status_icon_name (GossipChat *chat);
+static GtkWidget * private_chat_get_widget (GossipChat *chat);
+
+G_DEFINE_TYPE (GossipPrivateChat, gossip_private_chat, GOSSIP_TYPE_CHAT);
+
+static void
+gossip_private_chat_class_init (GossipPrivateChatClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GossipChatClass *chat_class = GOSSIP_CHAT_CLASS (klass);
+
+ object_class->finalize = private_chat_finalize;
+
+ chat_class->get_name = private_chat_get_name;
+ chat_class->get_tooltip = private_chat_get_tooltip;
+ chat_class->get_status_icon_name = private_chat_get_status_icon_name;
+ chat_class->get_widget = private_chat_get_widget;
+
+ g_type_class_add_private (object_class, sizeof (GossipPrivateChatPriv));
+}
+
+static void
+gossip_private_chat_init (GossipPrivateChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ priv->is_online = FALSE;
+
+ private_chat_create_ui (chat);
+}
+
+static void
+private_chat_finalize (GObject *object)
+{
+ GossipPrivateChat *chat;
+ GossipPrivateChatPriv *priv;
+
+ chat = GOSSIP_PRIVATE_CHAT (object);
+ priv = GET_PRIV (chat);
+
+ g_signal_handlers_disconnect_by_func (priv->contact,
+ private_chat_contact_updated_cb,
+ chat);
+ g_signal_handlers_disconnect_by_func (priv->contact,
+ private_chat_contact_presence_updated_cb,
+ chat);
+
+ if (priv->contact) {
+ g_object_unref (priv->contact);
+ }
+
+ g_free (priv->name);
+
+ G_OBJECT_CLASS (gossip_private_chat_parent_class)->finalize (object);
+}
+
+static void
+private_chat_create_ui (GossipPrivateChat *chat)
+{
+ GladeXML *glade;
+ GossipPrivateChatPriv *priv;
+ GtkWidget *input_text_view_sw;
+
+ priv = GET_PRIV (chat);
+
+ glade = gossip_glade_get_file ("gossip-chat.glade",
+ "chat_widget",
+ NULL,
+ "chat_widget", &priv->widget,
+ "chat_view_sw", &priv->text_view_sw,
+ "input_text_view_sw", &input_text_view_sw,
+ NULL);
+
+ gossip_glade_connect (glade,
+ chat,
+ "chat_widget", "destroy", private_chat_widget_destroy_cb,
+ NULL);
+
+ g_object_unref (glade);
+
+ g_object_set_data (G_OBJECT (priv->widget), "chat", g_object_ref (chat));
+
+ gtk_container_add (GTK_CONTAINER (priv->text_view_sw),
+ GTK_WIDGET (GOSSIP_CHAT (chat)->view));
+ gtk_widget_show (GTK_WIDGET (GOSSIP_CHAT (chat)->view));
+
+ gtk_container_add (GTK_CONTAINER (input_text_view_sw),
+ GOSSIP_CHAT (chat)->input_text_view);
+ gtk_widget_show (GOSSIP_CHAT (chat)->input_text_view);
+}
+
+static void
+private_chat_contact_presence_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipPrivateChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ gossip_debug (DEBUG_DOMAIN, "Presence update for contact: %s",
+ gossip_contact_get_id (contact));
+
+ if (!gossip_contact_is_online (contact)) {
+ if (priv->is_online) {
+ gchar *msg;
+
+ msg = g_strdup_printf (_("%s went offline"),
+ gossip_contact_get_name (priv->contact));
+ gossip_chat_view_append_event (GOSSIP_CHAT (chat)->view, msg);
+ g_free (msg);
+ }
+
+ priv->is_online = FALSE;
+
+ g_signal_emit_by_name (chat, "composing", FALSE);
+
+ } else {
+ if (!priv->is_online) {
+ gchar *msg;
+
+ msg = g_strdup_printf (_("%s has come online"),
+ gossip_contact_get_name (priv->contact));
+ gossip_chat_view_append_event (GOSSIP_CHAT (chat)->view, msg);
+ g_free (msg);
+ }
+
+ priv->is_online = TRUE;
+ }
+
+ g_signal_emit_by_name (chat, "status-changed");
+}
+
+static void
+private_chat_contact_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipPrivateChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ if (strcmp (priv->name, gossip_contact_get_name (contact)) != 0) {
+ g_free (priv->name);
+ priv->name = g_strdup (gossip_contact_get_name (contact));
+ g_signal_emit_by_name (chat, "name-changed", priv->name);
+ }
+}
+
+static void
+private_chat_widget_destroy_cb (GtkWidget *widget,
+ GossipPrivateChat *chat)
+{
+ gossip_debug (DEBUG_DOMAIN, "Destroyed");
+
+ g_object_unref (chat);
+}
+
+static const gchar *
+private_chat_get_name (GossipChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
+
+ priv = GET_PRIV (chat);
+
+ return priv->name;
+}
+
+static gchar *
+private_chat_get_tooltip (GossipChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+ const gchar *status;
+
+ g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
+
+ priv = GET_PRIV (chat);
+
+ status = gossip_contact_get_status (priv->contact);
+
+ return g_strdup_printf ("%s\n%s",
+ gossip_contact_get_id (priv->contact),
+ status);
+}
+
+static const gchar *
+private_chat_get_status_icon_name (GossipChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
+
+ priv = GET_PRIV (chat);
+
+ return gossip_icon_name_for_contact (priv->contact);
+}
+
+GossipContact *
+gossip_private_chat_get_contact (GossipPrivateChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
+
+ priv = GET_PRIV (chat);
+
+ return priv->contact;
+}
+
+static GtkWidget *
+private_chat_get_widget (GossipChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ return priv->widget;
+}
+
+static void
+private_chat_setup (GossipPrivateChat *chat,
+ GossipContact *contact,
+ EmpathyTpChat *tp_chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ gossip_chat_set_tp_chat (GOSSIP_CHAT (chat), tp_chat);
+
+ priv->contact = g_object_ref (contact);
+ GOSSIP_CHAT (chat)->account = g_object_ref (gossip_contact_get_account (contact));
+
+ priv->name = g_strdup (gossip_contact_get_name (contact));
+
+ g_signal_connect (priv->contact,
+ "notify::name",
+ G_CALLBACK (private_chat_contact_updated_cb),
+ chat);
+ g_signal_connect (priv->contact,
+ "notify::presence",
+ G_CALLBACK (private_chat_contact_presence_updated_cb),
+ chat);
+
+ priv->is_online = gossip_contact_is_online (priv->contact);
+}
+
+GossipPrivateChat *
+gossip_private_chat_new (McAccount *account,
+ TpChan *tp_chan)
+{
+ GossipPrivateChat *chat;
+ EmpathyTpChat *tp_chat;
+ EmpathyContactManager *manager;
+ EmpathyTpContactList *list;
+ GossipContact *contact;
+
+ g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
+
+ manager = empathy_contact_manager_new ();
+ list = empathy_contact_manager_get_list (manager, account);
+ contact = empathy_tp_contact_list_get_from_handle (list, tp_chan->handle);
+
+ chat = g_object_new (GOSSIP_TYPE_PRIVATE_CHAT, NULL);
+ tp_chat = empathy_tp_chat_new (account, tp_chan);
+
+ private_chat_setup (chat, contact, tp_chat);
+
+ g_object_unref (tp_chat);
+ g_object_unref (contact);
+ g_object_unref (manager);
+
+ return chat;
+}
+
+GossipPrivateChat *
+gossip_private_chat_new_with_contact (GossipContact *contact)
+{
+ GossipPrivateChat *chat;
+ EmpathyTpChat *tp_chat;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ chat = g_object_new (GOSSIP_TYPE_PRIVATE_CHAT, NULL);
+ tp_chat = empathy_tp_chat_new_with_contact (contact);
+
+ private_chat_setup (chat, contact, tp_chat);
+ g_object_unref (tp_chat);
+
+ return chat;
+}
+
diff --git a/trunk/libempathy-gtk/gossip-private-chat.h b/trunk/libempathy-gtk/gossip-private-chat.h
new file mode 100644
index 000000000..326d0f60e
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-private-chat.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __GOSSIP_PRIVATE_CHAT_H__
+#define __GOSSIP_PRIVATE_CHAT_H__
+
+#include <libtelepathy/tp-chan.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+#include <libempathy/gossip-contact.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_PRIVATE_CHAT (gossip_private_chat_get_type ())
+#define GOSSIP_PRIVATE_CHAT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_PRIVATE_CHAT, GossipPrivateChat))
+#define GOSSIP_PRIVATE_CHAT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_PRIVATE_CHAT, GossipPrivateChatClass))
+#define GOSSIP_IS_PRIVATE_CHAT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_PRIVATE_CHAT))
+#define GOSSIP_IS_PRIVATE_CHAT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_PRIVATE_CHAT))
+#define GOSSIP_PRIVATE_CHAT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_PRIVATE_CHAT, GossipPrivateChatClass))
+
+typedef struct _GossipPrivateChat GossipPrivateChat;
+typedef struct _GossipPrivateChatClass GossipPrivateChatClass;
+typedef struct _GossipPrivateChatPriv GossipPrivateChatPriv;
+
+#include "gossip-chat.h"
+
+struct _GossipPrivateChat {
+ GossipChat parent;
+};
+
+struct _GossipPrivateChatClass {
+ GossipChatClass parent;
+};
+
+GType gossip_private_chat_get_type (void);
+GossipPrivateChat * gossip_private_chat_new (McAccount *account,
+ TpChan *tp_chan);
+GossipPrivateChat * gossip_private_chat_new_with_contact (GossipContact *contact);
+GossipContact * gossip_private_chat_get_contact (GossipPrivateChat *chat);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_PRIVATE_CHAT_H__ */
diff --git a/trunk/libempathy-gtk/gossip-profile-chooser.c b/trunk/libempathy-gtk/gossip-profile-chooser.c
new file mode 100644
index 000000000..b55002896
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-profile-chooser.c
@@ -0,0 +1,106 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include <config.h>
+
+#include <gtk/gtk.h>
+#include <libmissioncontrol/mc-profile.h>
+
+#include <libempathy-gtk/gossip-ui-utils.h>
+
+#include "gossip-profile-chooser.h"
+
+enum {
+ COL_ICON,
+ COL_LABEL,
+ COL_PROFILE,
+ COL_COUNT
+};
+
+McProfile*
+gossip_profile_chooser_get_selected (GtkWidget *widget)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ McProfile *profile = NULL;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (widget));
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (widget), &iter)) {
+ gtk_tree_model_get (model, &iter,
+ COL_PROFILE, &profile,
+ -1);
+ }
+
+ return profile;
+}
+
+GtkWidget *
+gossip_profile_chooser_new (void)
+{
+ GList *profiles, *l;
+ GtkListStore *store;
+ GtkCellRenderer *renderer;
+ GtkWidget *combo_box;
+ GtkTreeIter iter;
+
+ /* set up combo box with new store */
+ store = gtk_list_store_new (COL_COUNT,
+ G_TYPE_STRING, /* Icon name */
+ G_TYPE_STRING, /* Label */
+ MC_TYPE_PROFILE); /* Profile */
+ combo_box = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
+
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, FALSE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer,
+ "icon-name", COL_ICON,
+ NULL);
+ g_object_set (renderer, "stock-size", GTK_ICON_SIZE_BUTTON, NULL);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer,
+ "text", COL_LABEL,
+ NULL);
+
+ profiles = mc_profiles_list ();
+ for (l = profiles; l; l = l->next) {
+ McProfile *profile;
+
+ profile = l->data;
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COL_ICON, mc_profile_get_icon_name (profile),
+ COL_LABEL, mc_profile_get_display_name (profile),
+ COL_PROFILE, profile,
+ -1);
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter);
+ }
+
+ mc_profiles_free_list (profiles);
+ g_object_unref (store);
+
+ return combo_box;
+}
+
diff --git a/trunk/libempathy-gtk/gossip-profile-chooser.h b/trunk/libempathy-gtk/gossip-profile-chooser.h
new file mode 100644
index 000000000..a563109a0
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-profile-chooser.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __GOSSIP_PROTOCOL_CHOOSER_H__
+#define __GOSSIP_PROTOCOL_CHOOSER_H__
+
+#include <libmissioncontrol/mc-profile.h>
+
+G_BEGIN_DECLS
+
+GtkWidget * gossip_profile_chooser_new (void);
+McProfile * gossip_profile_chooser_get_selected (GtkWidget *widget);
+
+G_END_DECLS
+#endif /* __GOSSIP_PROTOCOL_CHOOSER_H__ */
diff --git a/trunk/libempathy-gtk/gossip-spell.c b/trunk/libempathy-gtk/gossip-spell.c
new file mode 100644
index 000000000..db06e9f1d
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-spell.c
@@ -0,0 +1,457 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#ifdef HAVE_ASPELL
+#include <aspell.h>
+#endif
+
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-conf.h>
+
+#include "gossip-spell.h"
+#include "gossip-preferences.h"
+
+#define DEBUG_DOMAIN "Spell"
+
+#ifdef HAVE_ASPELL
+
+/* Note: We could use aspell_reset_cache (NULL); periodically if we wanted
+ * to...
+ */
+
+typedef struct {
+ AspellConfig *spell_config;
+ AspellCanHaveError *spell_possible_err;
+ AspellSpeller *spell_checker;
+} SpellLanguage;
+
+#define ISO_CODES_DATADIR ISO_CODES_PREFIX "/share/xml/iso-codes"
+#define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX "/share/locale"
+
+static GHashTable *iso_code_names = NULL;
+static GList *languages = NULL;
+static gboolean gossip_conf_notify_inited = FALSE;
+
+static void
+spell_iso_codes_parse_start_tag (GMarkupParseContext *ctx,
+ const gchar *element_name,
+ const gchar **attr_names,
+ const gchar **attr_values,
+ gpointer data,
+ GError **error)
+{
+ const gchar *ccode_longB, *ccode_longT, *ccode;
+ const gchar *lang_name;
+
+ if (!g_str_equal (element_name, "iso_639_entry") ||
+ attr_names == NULL || attr_values == NULL) {
+ return;
+ }
+
+ ccode = NULL;
+ ccode_longB = NULL;
+ ccode_longT = NULL;
+ lang_name = NULL;
+
+ while (*attr_names && *attr_values) {
+ if (g_str_equal (*attr_names, "iso_639_1_code")) {
+ if (**attr_values) {
+ ccode = *attr_values;
+ }
+ }
+ else if (g_str_equal (*attr_names, "iso_639_2B_code")) {
+ if (**attr_values) {
+ ccode_longB = *attr_values;
+ }
+ }
+ else if (g_str_equal (*attr_names, "iso_639_2T_code")) {
+ if (**attr_values) {
+ ccode_longT = *attr_values;
+ }
+ }
+ else if (g_str_equal (*attr_names, "name")) {
+ lang_name = *attr_values;
+ }
+
+ attr_names++;
+ attr_values++;
+ }
+
+ if (!lang_name) {
+ return;
+ }
+
+ if (ccode) {
+ g_hash_table_insert (iso_code_names,
+ g_strdup (ccode),
+ g_strdup (lang_name));
+ }
+
+ if (ccode_longB) {
+ g_hash_table_insert (iso_code_names,
+ g_strdup (ccode_longB),
+ g_strdup (lang_name));
+ }
+
+ if (ccode_longT) {
+ g_hash_table_insert (iso_code_names,
+ g_strdup (ccode_longT),
+ g_strdup (lang_name));
+ }
+}
+
+static void
+spell_iso_code_names_init (void)
+{
+ GError *err = NULL;
+ gchar *buf;
+ gsize buf_len;
+
+ iso_code_names = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_free);
+
+ bindtextdomain ("iso_639", ISO_CODES_LOCALESDIR);
+ bind_textdomain_codeset ("iso_639", "UTF-8");
+
+ /* FIXME: We should read this in chunks and pass to the parser. */
+ if (g_file_get_contents (ISO_CODES_DATADIR "/iso_639.xml", &buf, &buf_len, &err)) {
+ GMarkupParseContext *ctx;
+ GMarkupParser parser = {
+ spell_iso_codes_parse_start_tag,
+ NULL, NULL, NULL, NULL
+ };
+
+ ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
+ if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err)) {
+ g_warning ("Failed to parse '%s': %s",
+ ISO_CODES_DATADIR"/iso_639.xml",
+ err->message);
+ g_error_free (err);
+ }
+
+ g_markup_parse_context_free (ctx);
+ g_free (buf);
+ } else {
+ g_warning ("Failed to load '%s': %s",
+ ISO_CODES_DATADIR"/iso_639.xml", err->message);
+ g_error_free (err);
+ }
+}
+
+static void
+spell_notify_languages_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ GList *l;
+
+ gossip_debug (DEBUG_DOMAIN, "Resetting languages due to config change");
+
+ /* We just reset the languages list. */
+ for (l = languages; l; l = l->next) {
+ SpellLanguage *lang;
+
+ lang = l->data;
+
+ delete_aspell_config (lang->spell_config);
+ delete_aspell_speller (lang->spell_checker);
+
+ g_slice_free (SpellLanguage, lang);
+ }
+
+ g_list_free (languages);
+ languages = NULL;
+}
+
+static void
+spell_setup_languages (void)
+{
+ gchar *str;
+
+ if (!gossip_conf_notify_inited) {
+ gossip_conf_notify_add (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_LANGUAGES,
+ spell_notify_languages_cb, NULL);
+
+ gossip_conf_notify_inited = TRUE;
+ }
+
+ if (languages) {
+ gossip_debug (DEBUG_DOMAIN, "No languages to setup");
+ return;
+ }
+
+ if (gossip_conf_get_string (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_LANGUAGES,
+ &str) && str) {
+ gchar **strv;
+ gint i;
+
+ strv = g_strsplit (str, ",", -1);
+
+ i = 0;
+ while (strv && strv[i]) {
+ SpellLanguage *lang;
+
+ gossip_debug (DEBUG_DOMAIN, "Setting up language:'%s'", strv[i]);
+
+ lang = g_slice_new0 (SpellLanguage);
+
+ lang->spell_config = new_aspell_config();
+
+ aspell_config_replace (lang->spell_config, "encoding", "utf-8");
+ aspell_config_replace (lang->spell_config, "lang", strv[i++]);
+
+ lang->spell_possible_err = new_aspell_speller (lang->spell_config);
+
+ if (aspell_error_number (lang->spell_possible_err) == 0) {
+ lang->spell_checker = to_aspell_speller (lang->spell_possible_err);
+ languages = g_list_append (languages, lang);
+ } else {
+ delete_aspell_config (lang->spell_config);
+ g_slice_free (SpellLanguage, lang);
+ }
+ }
+
+ if (strv) {
+ g_strfreev (strv);
+ }
+
+ g_free (str);
+ }
+}
+
+const char *
+gossip_spell_get_language_name (const char *code)
+{
+ const gchar *name;
+
+ g_return_val_if_fail (code != NULL, NULL);
+
+ if (!iso_code_names) {
+ spell_iso_code_names_init ();
+ }
+
+ name = g_hash_table_lookup (iso_code_names, code);
+ if (!name) {
+ return NULL;
+ }
+
+ return dgettext ("iso_639", name);
+}
+
+GList *
+gossip_spell_get_language_codes (void)
+{
+ AspellConfig *config;
+ AspellDictInfoList *dlist;
+ AspellDictInfoEnumeration *dels;
+ const AspellDictInfo *entry;
+ GList *codes = NULL;
+
+ config = new_aspell_config ();
+ dlist = get_aspell_dict_info_list (config);
+ dels = aspell_dict_info_list_elements (dlist);
+
+ while ((entry = aspell_dict_info_enumeration_next (dels)) != 0) {
+ if (g_list_find_custom (codes, entry->code, (GCompareFunc) strcmp)) {
+ continue;
+ }
+
+ codes = g_list_append (codes, g_strdup (entry->code));
+ }
+
+ delete_aspell_dict_info_enumeration (dels);
+ delete_aspell_config (config);
+
+ return codes;
+}
+
+void
+gossip_spell_free_language_codes (GList *codes)
+{
+ g_list_foreach (codes, (GFunc) g_free, NULL);
+ g_list_free (codes);
+}
+
+gboolean
+gossip_spell_check (const gchar *word)
+{
+ GList *l;
+ gint n_langs;
+ gboolean correct = FALSE;
+ gint len;
+ const gchar *p;
+ gunichar c;
+ gboolean digit;
+
+ g_return_val_if_fail (word != NULL, FALSE);
+
+ spell_setup_languages ();
+
+ if (!languages) {
+ gossip_debug (DEBUG_DOMAIN, "No languages to check against");
+ return TRUE;
+ }
+
+ /* Ignore certain cases like numbers, etc. */
+ for (p = word, digit = TRUE; *p && digit; p = g_utf8_next_char (p)) {
+ c = g_utf8_get_char (p);
+ digit = g_unichar_isdigit (c);
+ }
+
+ if (digit) {
+ /* We don't spell check digits. */
+ gossip_debug (DEBUG_DOMAIN, "Not spell checking word:'%s', it is all digits", word);
+ return TRUE;
+ }
+
+ len = strlen (word);
+ n_langs = g_list_length (languages);
+ for (l = languages; l; l = l->next) {
+ SpellLanguage *lang;
+
+ lang = l->data;
+
+ correct = aspell_speller_check (lang->spell_checker, word, len);
+ if (n_langs > 1 && correct) {
+ break;
+ }
+ }
+
+ return correct;
+}
+
+GList *
+gossip_spell_get_suggestions (const gchar *word)
+{
+ GList *l1;
+ GList *l2 = NULL;
+ const AspellWordList *suggestions;
+ AspellStringEnumeration *elements;
+ const char *next;
+ gint len;
+
+ g_return_val_if_fail (word != NULL, NULL);
+
+ spell_setup_languages ();
+
+ len = strlen (word);
+
+ for (l1 = languages; l1; l1 = l1->next) {
+ SpellLanguage *lang;
+
+ lang = l1->data;
+
+ suggestions = aspell_speller_suggest (lang->spell_checker,
+ word, len);
+
+ elements = aspell_word_list_elements (suggestions);
+
+ while ((next = aspell_string_enumeration_next (elements))) {
+ l2 = g_list_append (l2, g_strdup (next));
+ }
+
+ delete_aspell_string_enumeration (elements);
+ }
+
+ return l2;
+}
+
+gboolean
+gossip_spell_supported (void)
+{
+ if (g_getenv ("GOSSIP_SPELL_DISABLED")) {
+ gossip_debug (DEBUG_DOMAIN, "GOSSIP_SPELL_DISABLE env variable defined");
+ return FALSE;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Support enabled");
+
+ return TRUE;
+}
+
+#else /* not HAVE_ASPELL */
+
+gboolean
+gossip_spell_supported (void)
+{
+ gossip_debug (DEBUG_DOMAIN, "Support disabled");
+
+ return FALSE;
+}
+
+GList *
+gossip_spell_get_suggestions (const gchar *word)
+{
+ gossip_debug (DEBUG_DOMAIN, "Support disabled, could not get suggestions");
+
+ return NULL;
+}
+
+gboolean
+gossip_spell_check (const gchar *word)
+{
+ gossip_debug (DEBUG_DOMAIN, "Support disabled, could not check spelling");
+
+ return TRUE;
+}
+
+const char *
+gossip_spell_get_language_name (const char *lang)
+{
+ gossip_debug (DEBUG_DOMAIN, "Support disabled, could not get language name");
+
+ return NULL;
+}
+
+GList *
+gossip_spell_get_language_codes (void)
+{
+ gossip_debug (DEBUG_DOMAIN, "Support disabled, could not get language codes");
+
+ return NULL;
+}
+
+void
+gossip_spell_free_language_codes (GList *codes)
+{
+}
+
+#endif /* HAVE_ASPELL */
+
+
+void
+gossip_spell_free_suggestions (GList *suggestions)
+{
+ g_list_foreach (suggestions, (GFunc) g_free, NULL);
+ g_list_free (suggestions);
+}
+
diff --git a/trunk/libempathy-gtk/gossip-spell.h b/trunk/libempathy-gtk/gossip-spell.h
new file mode 100644
index 000000000..f2d841b3a
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-spell.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ */
+
+#ifndef __GOSSIP_SPELL_H__
+#define __GOSSIP_SPELL_H__
+
+G_BEGIN_DECLS
+
+gboolean gossip_spell_supported (void);
+const gchar *gossip_spell_get_language_name (const gchar *code);
+GList *gossip_spell_get_language_codes (void);
+void gossip_spell_free_language_codes (GList *codes);
+gboolean gossip_spell_check (const gchar *word);
+GList * gossip_spell_get_suggestions (const gchar *word);
+void gossip_spell_free_suggestions (GList *suggestions);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_SPELL_H__ */
diff --git a/trunk/libempathy-gtk/gossip-status-presets.c b/trunk/libempathy-gtk/gossip-status-presets.c
new file mode 100644
index 000000000..b27238d4f
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-status-presets.c
@@ -0,0 +1,389 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-utils.h>
+
+#include "gossip-status-presets.h"
+
+#define DEBUG_DOMAIN "StatusPresets"
+
+#define STATUS_PRESETS_XML_FILENAME "status-presets.xml"
+#define STATUS_PRESETS_DTD_FILENAME "gossip-status-presets.dtd"
+#define STATUS_PRESETS_MAX_EACH 15
+
+typedef struct {
+ gchar *status;
+ McPresence state;
+} StatusPreset;
+
+static StatusPreset *status_preset_new (McPresence state,
+ const gchar *status);
+static void status_preset_free (StatusPreset *status);
+static void status_presets_file_parse (const gchar *filename);
+const gchar * status_presets_get_state_as_str (McPresence state);
+static gboolean status_presets_file_save (void);
+static void status_presets_set_default (McPresence state,
+ const gchar *status);
+
+static GList *presets = NULL;
+static StatusPreset *default_preset = NULL;
+
+static StatusPreset *
+status_preset_new (McPresence state,
+ const gchar *status)
+{
+ StatusPreset *preset;
+
+ preset = g_new0 (StatusPreset, 1);
+
+ preset->status = g_strdup (status);
+ preset->state = state;
+
+ return preset;
+}
+
+static void
+status_preset_free (StatusPreset *preset)
+{
+ g_free (preset->status);
+ g_free (preset);
+}
+
+static void
+status_presets_file_parse (const gchar *filename)
+{
+ xmlParserCtxtPtr ctxt;
+ xmlDocPtr doc;
+ xmlNodePtr presets_node;
+ xmlNodePtr node;
+
+ gossip_debug (DEBUG_DOMAIN, "Attempting to parse file:'%s'...", filename);
+
+ ctxt = xmlNewParserCtxt ();
+
+ /* Parse and validate the file. */
+ doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
+ if (!doc) {
+ g_warning ("Failed to parse file:'%s'", filename);
+ xmlFreeParserCtxt (ctxt);
+ return;
+ }
+
+ if (!gossip_xml_validate (doc, STATUS_PRESETS_DTD_FILENAME)) {
+ g_warning ("Failed to validate file:'%s'", filename);
+ xmlFreeDoc(doc);
+ xmlFreeParserCtxt (ctxt);
+ return;
+ }
+
+ /* The root node, presets. */
+ presets_node = xmlDocGetRootElement (doc);
+
+ node = presets_node->children;
+ while (node) {
+ if (strcmp ((gchar *) node->name, "status") == 0 ||
+ strcmp ((gchar *) node->name, "default") == 0) {
+ McPresence state;
+ gchar *status;
+ gchar *state_str;
+ StatusPreset *preset;
+ gboolean is_default = FALSE;
+
+ if (strcmp ((gchar *) node->name, "default") == 0) {
+ is_default = TRUE;
+ }
+
+ status = (gchar *) xmlNodeGetContent (node);
+ state_str = (gchar *) xmlGetProp (node, "presence");
+
+ if (state_str) {
+ state = gossip_presence_state_from_str (state_str);
+
+ if (is_default) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Default status preset state is:'%s', status:'%s'",
+ state_str, status);
+
+ status_presets_set_default (state, status);
+ } else {
+ preset = status_preset_new (state, status);
+ presets = g_list_append (presets, preset);
+ }
+ }
+
+ xmlFree (status);
+ xmlFree (state_str);
+ }
+
+ node = node->next;
+ }
+
+ /* Use the default if not set */
+ if (!default_preset) {
+ status_presets_set_default (MC_PRESENCE_OFFLINE, NULL);
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Parsed %d status presets", g_list_length (presets));
+
+ xmlFreeDoc (doc);
+ xmlFreeParserCtxt (ctxt);
+}
+
+void
+gossip_status_presets_get_all (void)
+{
+ gchar *dir;
+ gchar *file_with_path;
+
+ /* If already set up clean up first. */
+ if (presets) {
+ g_list_foreach (presets, (GFunc) status_preset_free, NULL);
+ g_list_free (presets);
+ presets = NULL;
+ }
+
+ dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
+ g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
+ file_with_path = g_build_filename (dir, STATUS_PRESETS_XML_FILENAME, NULL);
+ g_free (dir);
+
+ if (g_file_test (file_with_path, G_FILE_TEST_EXISTS)) {
+ status_presets_file_parse (file_with_path);
+ }
+
+ g_free (file_with_path);
+}
+
+static gboolean
+status_presets_file_save (void)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root;
+ GList *l;
+ gchar *dir;
+ gchar *file;
+ gint count[LAST_MC_PRESENCE];
+ gint i;
+
+ for (i = 0; i < LAST_MC_PRESENCE; i++) {
+ count[i] = 0;
+ }
+
+ dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
+ g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
+ file = g_build_filename (dir, STATUS_PRESETS_XML_FILENAME, NULL);
+ g_free (dir);
+
+ doc = xmlNewDoc ("1.0");
+ root = xmlNewNode (NULL, "presets");
+ xmlDocSetRootElement (doc, root);
+
+ if (default_preset) {
+ xmlNodePtr subnode;
+ xmlChar *state;
+
+ state = (gchar*) gossip_presence_state_to_str (default_preset->state);
+
+ subnode = xmlNewTextChild (root, NULL, "default",
+ default_preset->status);
+ xmlNewProp (subnode, "presence", state);
+ }
+
+ for (l = presets; l; l = l->next) {
+ StatusPreset *sp;
+ xmlNodePtr subnode;
+ xmlChar *state;
+
+ sp = l->data;
+ state = (gchar*) gossip_presence_state_to_str (sp->state);
+
+ count[sp->state]++;
+ if (count[sp->state] > STATUS_PRESETS_MAX_EACH) {
+ continue;
+ }
+
+ subnode = xmlNewTextChild (root, NULL,
+ "status", sp->status);
+ xmlNewProp (subnode, "presence", state);
+ }
+
+ /* Make sure the XML is indented properly */
+ xmlIndentTreeOutput = 1;
+
+ gossip_debug (DEBUG_DOMAIN, "Saving file:'%s'", file);
+ xmlSaveFormatFileEnc (file, doc, "utf-8", 1);
+ xmlFreeDoc (doc);
+
+ g_free (file);
+
+ return TRUE;
+}
+
+GList *
+gossip_status_presets_get (McPresence state,
+ gint max_number)
+{
+ GList *list = NULL;
+ GList *l;
+ gint i;
+
+ i = 0;
+ for (l = presets; l; l = l->next) {
+ StatusPreset *sp;
+
+ sp = l->data;
+
+ if (sp->state != state) {
+ continue;
+ }
+
+ list = g_list_append (list, sp->status);
+ i++;
+
+ if (max_number != -1 && i >= max_number) {
+ break;
+ }
+ }
+
+ return list;
+}
+
+void
+gossip_status_presets_set_last (McPresence state,
+ const gchar *status)
+{
+ GList *l;
+ StatusPreset *preset;
+ gint num;
+
+ /* Remove any duplicate. */
+ for (l = presets; l; l = l->next) {
+ preset = l->data;
+
+ if (state == preset->state) {
+ if (strcmp (status, preset->status) == 0) {
+ status_preset_free (preset);
+ presets = g_list_delete_link (presets, l);
+ break;
+ }
+ }
+ }
+
+ preset = status_preset_new (state, status);
+ presets = g_list_prepend (presets, preset);
+
+ num = 0;
+ for (l = presets; l; l = l->next) {
+ preset = l->data;
+
+ if (state != preset->state) {
+ continue;
+ }
+
+ num++;
+
+ if (num > STATUS_PRESETS_MAX_EACH) {
+ status_preset_free (preset);
+ presets = g_list_delete_link (presets, l);
+ break;
+ }
+ }
+
+ status_presets_file_save ();
+}
+
+void
+gossip_status_presets_reset (void)
+{
+ g_list_foreach (presets, (GFunc) status_preset_free, NULL);
+ g_list_free (presets);
+
+ presets = NULL;
+
+ status_presets_set_default (MC_PRESENCE_AVAILABLE, NULL);
+
+ status_presets_file_save ();
+}
+
+McPresence
+gossip_status_presets_get_default_state (void)
+{
+ if (!default_preset) {
+ return MC_PRESENCE_OFFLINE;
+ }
+
+ return default_preset->state;
+}
+
+const gchar *
+gossip_status_presets_get_default_status (void)
+{
+ if (!default_preset ||
+ !default_preset->status) {
+ return NULL;
+ }
+
+ return default_preset->status;
+}
+
+static void
+status_presets_set_default (McPresence state,
+ const gchar *status)
+{
+ if (default_preset) {
+ status_preset_free (default_preset);
+ }
+
+ default_preset = status_preset_new (state, status);
+}
+
+void
+gossip_status_presets_set_default (McPresence state,
+ const gchar *status)
+{
+ status_presets_set_default (state, status);
+ status_presets_file_save ();
+}
+
+void
+gossip_status_presets_clear_default (void)
+{
+ if (default_preset) {
+ status_preset_free (default_preset);
+ default_preset = NULL;
+ }
+
+ status_presets_file_save ();
+}
diff --git a/trunk/libempathy-gtk/gossip-status-presets.dtd b/trunk/libempathy-gtk/gossip-status-presets.dtd
new file mode 100644
index 000000000..b7acc9620
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-status-presets.dtd
@@ -0,0 +1,14 @@
+<!--
+ DTD for Gossips status presets.
+ by Martyn Russell <martyn@imendio.com>
+-->
+
+<!-- Root element. -->
+<!ELEMENT presets ((default?),status*)>
+
+<!ELEMENT default (#PCDATA)>
+<!ATTLIST default presence CDATA #REQUIRED>
+
+<!ELEMENT status (#PCDATA)>
+<!ATTLIST status presence CDATA #REQUIRED>
+
diff --git a/trunk/libempathy-gtk/gossip-status-presets.h b/trunk/libempathy-gtk/gossip-status-presets.h
new file mode 100644
index 000000000..4b2d92555
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-status-presets.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_STATUS_PRESETS_H__
+#define __GOSSIP_STATUS_PRESETS_H__
+
+#include <libempathy/gossip-presence.h>
+
+G_BEGIN_DECLS
+
+void gossip_status_presets_get_all (void);
+GList * gossip_status_presets_get (McPresence state,
+ gint max_number);
+void gossip_status_presets_set_last (McPresence state,
+ const gchar *status);
+void gossip_status_presets_reset (void);
+McPresence gossip_status_presets_get_default_state (void);
+const gchar * gossip_status_presets_get_default_status (void);
+void gossip_status_presets_set_default (McPresence state,
+ const gchar *status);
+void gossip_status_presets_clear_default (void);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_STATUS_PRESETS_H__ */
diff --git a/trunk/libempathy-gtk/gossip-theme-manager.c b/trunk/libempathy-gtk/gossip-theme-manager.c
new file mode 100644
index 000000000..6d5905e5a
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-theme-manager.c
@@ -0,0 +1,1045 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Richard Hult <richard@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include <libempathy/gossip-conf.h>
+#include <libempathy/gossip-utils.h>
+
+#include "gossip-chat-view.h"
+#include "gossip-preferences.h"
+#include "gossip-theme-manager.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_THEME_MANAGER, GossipThemeManagerPriv))
+
+typedef struct {
+ gchar *name;
+ guint name_notify_id;
+ guint room_notify_id;
+
+ gboolean show_avatars;
+ guint show_avatars_notify_id;
+
+ gboolean irc_style;
+} GossipThemeManagerPriv;
+
+static void theme_manager_finalize (GObject *object);
+static void theme_manager_notify_name_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void theme_manager_notify_room_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void theme_manager_notify_show_avatars_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void theme_manager_ensure_tag_by_name (GtkTextBuffer *buffer,
+ const gchar *name);
+static gboolean theme_manager_ensure_theme_exists (const gchar *name);
+static GtkTextTag *theme_manager_init_tag_by_name (GtkTextTagTable *table,
+ const gchar *name);
+static void theme_manager_add_tag (GtkTextTagTable *table,
+ GtkTextTag *tag);
+static void theme_manager_fixup_tag_table (GossipThemeManager *theme_manager,
+ GossipChatView *view);
+static void theme_manager_apply_theme_classic (GossipThemeManager *manager,
+ GossipChatView *view);
+static void theme_manager_apply_theme_clean (GossipThemeManager *manager,
+ GossipChatView *view);
+static void theme_manager_apply_theme_blue (GossipThemeManager *manager,
+ GossipChatView *view);
+static void theme_manager_apply_theme (GossipThemeManager *manager,
+ GossipChatView *view,
+ const gchar *name);
+
+enum {
+ THEME_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static const gchar *themes[] = {
+ "classic", N_("Classic"),
+ "simple", N_("Simple"),
+ "clean", N_("Clean"),
+ "blue", N_("Blue"),
+ NULL
+};
+
+G_DEFINE_TYPE (GossipThemeManager, gossip_theme_manager, G_TYPE_OBJECT);
+
+static void
+gossip_theme_manager_class_init (GossipThemeManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ signals[THEME_CHANGED] =
+ g_signal_new ("theme-changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_type_class_add_private (object_class, sizeof (GossipThemeManagerPriv));
+
+ object_class->finalize = theme_manager_finalize;
+}
+
+static void
+gossip_theme_manager_init (GossipThemeManager *manager)
+{
+ GossipThemeManagerPriv *priv;
+
+ priv = GET_PRIV (manager);
+
+ priv->name_notify_id =
+ gossip_conf_notify_add (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_THEME,
+ theme_manager_notify_name_cb,
+ manager);
+
+ priv->room_notify_id =
+ gossip_conf_notify_add (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
+ theme_manager_notify_room_cb,
+ manager);
+
+ gossip_conf_get_string (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_THEME,
+ &priv->name);
+
+ /* Unused right now, but will be used soon. */
+ priv->show_avatars_notify_id =
+ gossip_conf_notify_add (gossip_conf_get (),
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ theme_manager_notify_show_avatars_cb,
+ manager);
+
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ &priv->show_avatars);
+}
+
+static void
+theme_manager_finalize (GObject *object)
+{
+ GossipThemeManagerPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ gossip_conf_notify_remove (gossip_conf_get (), priv->name_notify_id);
+ gossip_conf_notify_remove (gossip_conf_get (), priv->room_notify_id);
+ gossip_conf_notify_remove (gossip_conf_get (), priv->show_avatars_notify_id);
+
+ g_free (priv->name);
+
+ G_OBJECT_CLASS (gossip_theme_manager_parent_class)->finalize (object);
+}
+
+static void
+theme_manager_notify_name_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ GossipThemeManager *manager;
+ GossipThemeManagerPriv *priv;
+ gchar *name;
+
+ manager = user_data;
+ priv = GET_PRIV (manager);
+
+ g_free (priv->name);
+
+ name = NULL;
+ if (!gossip_conf_get_string (conf, key, &name) ||
+ name == NULL || name[0] == 0) {
+ priv->name = g_strdup ("classic");
+ g_free (name);
+ } else {
+ priv->name = name;
+ }
+
+ g_signal_emit (manager, signals[THEME_CHANGED], 0, NULL);
+}
+
+static void
+theme_manager_notify_room_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ g_signal_emit (user_data, signals[THEME_CHANGED], 0, NULL);
+}
+
+static void
+theme_manager_notify_show_avatars_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ GossipThemeManager *manager;
+ GossipThemeManagerPriv *priv;
+ gboolean value;
+
+ manager = user_data;
+ priv = GET_PRIV (manager);
+
+ if (!gossip_conf_get_bool (conf, key, &value)) {
+ priv->show_avatars = FALSE;
+ } else {
+ priv->show_avatars = value;
+ }
+}
+
+static void
+theme_manager_ensure_tag_by_name (GtkTextBuffer *buffer,
+ const gchar *name)
+{
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+
+ table = gtk_text_buffer_get_tag_table (buffer);
+ tag = gtk_text_tag_table_lookup (table, name);
+
+ if (!tag) {
+ gtk_text_buffer_create_tag (buffer,
+ name,
+ NULL);
+ }
+}
+
+static gboolean
+theme_manager_ensure_theme_exists (const gchar *name)
+{
+ gint i;
+
+ if (G_STR_EMPTY (name)) {
+ return FALSE;
+ }
+
+ for (i = 0; themes[i]; i += 2) {
+ if (strcmp (themes[i], name) == 0) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static GtkTextTag *
+theme_manager_init_tag_by_name (GtkTextTagTable *table,
+ const gchar *name)
+{
+ GtkTextTag *tag;
+
+ tag = gtk_text_tag_table_lookup (table, name);
+
+ if (!tag) {
+ return gtk_text_tag_new (name);
+ }
+
+ /* Clear the old values so that we don't affect the new theme. */
+ g_object_set (tag,
+ "background-set", FALSE,
+ "foreground-set", FALSE,
+ "invisible-set", FALSE,
+ "justification-set", FALSE,
+ "paragraph-background-set", FALSE,
+ "pixels-above-lines-set", FALSE,
+ "pixels-below-lines-set", FALSE,
+ "rise-set", FALSE,
+ "scale-set", FALSE,
+ "size-set", FALSE,
+ "style-set", FALSE,
+ "weight-set", FALSE,
+ NULL);
+
+ return tag;
+}
+
+static void
+theme_manager_add_tag (GtkTextTagTable *table,
+ GtkTextTag *tag)
+{
+ gchar *name;
+ GtkTextTag *check_tag;
+
+ g_object_get (tag, "name", &name, NULL);
+ check_tag = gtk_text_tag_table_lookup (table, name);
+ g_free (name);
+ if (check_tag) {
+ return;
+ }
+
+ gtk_text_tag_table_add (table, tag);
+
+ g_object_unref (tag);
+}
+
+static void
+theme_manager_fixup_tag_table (GossipThemeManager *theme_manager,
+ GossipChatView *view)
+{
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ /* "Fancy" style tags. */
+ theme_manager_ensure_tag_by_name (buffer, "fancy-header-self");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-header-self-avatar");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-avatar-self");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-line-top-self");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-line-bottom-self");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-body-self");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-action-self");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-highlight-self");
+
+ theme_manager_ensure_tag_by_name (buffer, "fancy-header-other");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-header-other-avatar");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-avatar-other");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-line-top-other");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-line-bottom-other");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-body-other");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-action-other");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-highlight-other");
+
+ theme_manager_ensure_tag_by_name (buffer, "fancy-spacing");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-time");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-event");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-invite");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-link");
+
+ /* IRC style tags. */
+ theme_manager_ensure_tag_by_name (buffer, "irc-nick-self");
+ theme_manager_ensure_tag_by_name (buffer, "irc-body-self");
+ theme_manager_ensure_tag_by_name (buffer, "irc-action-self");
+
+ theme_manager_ensure_tag_by_name (buffer, "irc-nick-other");
+ theme_manager_ensure_tag_by_name (buffer, "irc-body-other");
+ theme_manager_ensure_tag_by_name (buffer, "irc-action-other");
+
+ theme_manager_ensure_tag_by_name (buffer, "irc-nick-highlight");
+ theme_manager_ensure_tag_by_name (buffer, "irc-spacing");
+ theme_manager_ensure_tag_by_name (buffer, "irc-time");
+ theme_manager_ensure_tag_by_name (buffer, "irc-event");
+ theme_manager_ensure_tag_by_name (buffer, "irc-invite");
+ theme_manager_ensure_tag_by_name (buffer, "irc-link");
+}
+
+static void
+theme_manager_apply_theme_classic (GossipThemeManager *manager,
+ GossipChatView *view)
+{
+ GossipThemeManagerPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+
+ priv = GET_PRIV (manager);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ priv->irc_style = TRUE;
+
+ tag = theme_manager_init_tag_by_name (table, "irc-spacing");
+ g_object_set (tag,
+ "size", 2000,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-nick-self");
+ g_object_set (tag,
+ "foreground", "sea green",
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-body-self");
+ g_object_set (tag,
+ /* To get the default theme color: */
+ "foreground-set", FALSE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-action-self");
+ g_object_set (tag,
+ "foreground", "brown4",
+ "style", PANGO_STYLE_ITALIC,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-nick-highlight");
+ g_object_set (tag,
+ "foreground", "indian red",
+ "weight", PANGO_WEIGHT_BOLD,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-nick-other");
+ g_object_set (tag,
+ "foreground", "skyblue4",
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-body-other");
+ g_object_set (tag,
+ /* To get the default theme color: */
+ "foreground-set", FALSE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-action-other");
+ g_object_set (tag,
+ "foreground", "brown4",
+ "style", PANGO_STYLE_ITALIC,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-time");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ "justification", GTK_JUSTIFY_CENTER,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-event");
+ g_object_set (tag,
+ "foreground", "PeachPuff4",
+ "justification", GTK_JUSTIFY_LEFT,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-invite");
+ g_object_set (tag,
+ "foreground", "sienna",
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-link");
+ g_object_set (tag,
+ "foreground", "steelblue",
+ "underline", PANGO_UNDERLINE_SINGLE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+}
+
+static void
+theme_manager_apply_theme_simple (GossipThemeManager *manager,
+ GossipChatView *view)
+{
+ GossipThemeManagerPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+ GtkWidget *widget;
+ GtkStyle *style;
+
+ priv = GET_PRIV (manager);
+
+ widget = gtk_entry_new ();
+ style = gtk_widget_get_style (widget);
+ gtk_widget_destroy (widget);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ priv->irc_style = FALSE;
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-spacing");
+ g_object_set (tag,
+ "size", 3000,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-self");
+ g_object_set (tag,
+ "weight", PANGO_WEIGHT_BOLD,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-self-avatar");
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-avatar-self");
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-top-self");
+ g_object_set (tag,
+ "size", 6 * PANGO_SCALE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-bottom-self");
+ g_object_set (tag,
+ "size", 1,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-body-self");
+ g_object_set (tag,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-action-self");
+ g_object_set (tag,
+ "foreground-gdk", &style->base[GTK_STATE_SELECTED],
+ "style", PANGO_STYLE_ITALIC,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-highlight-self");
+ g_object_set (tag,
+ "weight", PANGO_WEIGHT_BOLD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-other");
+ g_object_set (tag,
+ "weight", PANGO_WEIGHT_BOLD,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-other-avatar");
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-avatar-other");
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-top-other");
+ g_object_set (tag,
+ "size", 6 * PANGO_SCALE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-bottom-other");
+ g_object_set (tag,
+ "size", 1,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-body-other");
+ g_object_set (tag,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-action-other");
+ g_object_set (tag,
+ "foreground-gdk", &style->base[GTK_STATE_SELECTED],
+ "style", PANGO_STYLE_ITALIC,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-highlight-other");
+ g_object_set (tag,
+ "weight", PANGO_WEIGHT_BOLD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-time");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ "justification", GTK_JUSTIFY_CENTER,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-event");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ "justification", GTK_JUSTIFY_LEFT,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-invite");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-link");
+ g_object_set (tag,
+ "foreground-gdk", &style->base[GTK_STATE_SELECTED],
+ "underline", PANGO_UNDERLINE_SINGLE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+}
+
+static void
+theme_manager_apply_theme_clean (GossipThemeManager *manager,
+ GossipChatView *view)
+{
+ GossipThemeManagerPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+
+ priv = GET_PRIV (manager);
+
+ /* Inherit the simple theme. */
+ theme_manager_apply_theme_simple (manager, view);
+
+#define ELEGANT_HEAD "#efefdf"
+#define ELEGANT_LINE "#e3e3d3"
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-spacing");
+ g_object_set (tag,
+ "size", PANGO_SCALE * 10,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-self");
+ g_object_set (tag,
+ "foreground", "black",
+ "weight", PANGO_WEIGHT_BOLD,
+ "paragraph-background", ELEGANT_HEAD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-avatar-self");
+ g_object_set (tag,
+ "paragraph-background", ELEGANT_HEAD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-top-self");
+ g_object_set (tag,
+ "size", 1 * PANGO_SCALE,
+ "paragraph-background", ELEGANT_LINE,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-bottom-self");
+ g_object_set (tag,
+ "size", 1 * PANGO_SCALE,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-action-self");
+ g_object_set (tag,
+ "foreground", "brown4",
+ "style", PANGO_STYLE_ITALIC,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-highlight-self");
+ g_object_set (tag,
+ "foreground", "black",
+ "weight", PANGO_WEIGHT_BOLD,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-other");
+ g_object_set (tag,
+ "foreground", "black",
+ "weight", PANGO_WEIGHT_BOLD,
+ "paragraph-background", ELEGANT_HEAD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-avatar-other");
+ g_object_set (tag,
+ "paragraph-background", ELEGANT_HEAD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-top-other");
+ g_object_set (tag,
+ "size", 1 * PANGO_SCALE,
+ "paragraph-background", ELEGANT_LINE,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-bottom-other");
+ g_object_set (tag,
+ "size", 1 * PANGO_SCALE,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-action-other");
+ g_object_set (tag,
+ "foreground", "brown4",
+ "style", PANGO_STYLE_ITALIC,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-time");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ "justification", GTK_JUSTIFY_CENTER,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-event");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ "justification", GTK_JUSTIFY_LEFT,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-invite");
+ g_object_set (tag,
+ "foreground", "sienna",
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-link");
+ g_object_set (tag,
+ "foreground", "#49789e",
+ "underline", PANGO_UNDERLINE_SINGLE,
+ NULL);
+}
+
+static void
+theme_manager_apply_theme_blue (GossipThemeManager *manager,
+ GossipChatView *view)
+{
+ GossipThemeManagerPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+
+ priv = GET_PRIV (manager);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ priv->irc_style = FALSE;
+
+#define BLUE_BODY_SELF "#dcdcdc"
+#define BLUE_HEAD_SELF "#b9b9b9"
+#define BLUE_LINE_SELF "#aeaeae"
+
+#define BLUE_BODY_OTHER "#adbdc8"
+#define BLUE_HEAD_OTHER "#88a2b4"
+#define BLUE_LINE_OTHER "#7f96a4"
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-spacing");
+ g_object_set (tag,
+ "size", 3000,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-self");
+ g_object_set (tag,
+ "foreground", "black",
+ "paragraph-background", BLUE_HEAD_SELF,
+ "weight", PANGO_WEIGHT_BOLD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-self-avatar");
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-avatar-self");
+ g_object_set (tag,
+ "paragraph-background", BLUE_HEAD_SELF,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-top-self");
+ g_object_set (tag,
+ "size", 1,
+ "paragraph-background", BLUE_LINE_SELF,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-bottom-self");
+ g_object_set (tag,
+ "size", 1,
+ "paragraph-background", BLUE_LINE_SELF,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-body-self");
+ g_object_set (tag,
+ "foreground", "black",
+ "paragraph-background", BLUE_BODY_SELF,
+ "pixels-above-lines", 4,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-action-self");
+ g_object_set (tag,
+ "foreground", "brown4",
+ "style", PANGO_STYLE_ITALIC,
+ "paragraph-background", BLUE_BODY_SELF,
+ "pixels-above-lines", 4,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-highlight-self");
+ g_object_set (tag,
+ "foreground", "black",
+ "weight", PANGO_WEIGHT_BOLD,
+ "paragraph-background", BLUE_BODY_SELF,
+ "pixels-above-lines", 4,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-other");
+ g_object_set (tag,
+ "foreground", "black",
+ "paragraph-background", BLUE_HEAD_OTHER,
+ "weight", PANGO_WEIGHT_BOLD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-other-avatar");
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-avatar-other");
+ g_object_set (tag,
+ "paragraph-background", BLUE_HEAD_OTHER,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-top-other");
+ g_object_set (tag,
+ "size", 1,
+ "paragraph-background", BLUE_LINE_OTHER,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-bottom-other");
+ g_object_set (tag,
+ "size", 1,
+ "paragraph-background", BLUE_LINE_OTHER,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-body-other");
+ g_object_set (tag,
+ "foreground", "black",
+ "paragraph-background", BLUE_BODY_OTHER,
+ "pixels-above-lines", 4,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-action-other");
+ g_object_set (tag,
+ "foreground", "brown4",
+ "style", PANGO_STYLE_ITALIC,
+ "paragraph-background", BLUE_BODY_OTHER,
+ "pixels-above-lines", 4,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-highlight-other");
+ g_object_set (tag,
+ "foreground", "black",
+ "weight", PANGO_WEIGHT_BOLD,
+ "paragraph-background", BLUE_BODY_OTHER,
+ "pixels-above-lines", 4,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-time");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ "justification", GTK_JUSTIFY_CENTER,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-event");
+ g_object_set (tag,
+ "foreground", BLUE_LINE_OTHER,
+ "justification", GTK_JUSTIFY_LEFT,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-invite");
+ g_object_set (tag,
+ "foreground", "sienna",
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-link");
+ g_object_set (tag,
+ "foreground", "#49789e",
+ "underline", PANGO_UNDERLINE_SINGLE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+}
+
+static void
+theme_manager_apply_theme (GossipThemeManager *manager,
+ GossipChatView *view,
+ const gchar *name)
+{
+ GossipThemeManagerPriv *priv;
+ gint margin;
+
+ priv = GET_PRIV (manager);
+
+ /* Make sure all tags are present. Note: not useful now but when we have
+ * user defined theme it will be.
+ */
+ theme_manager_fixup_tag_table (manager, view);
+
+ if (theme_manager_ensure_theme_exists (name)) {
+ if (strcmp (name, "clean") == 0) {
+ theme_manager_apply_theme_clean (manager, view);
+ margin = 3;
+ }
+ else if (strcmp (name, "simple") == 0) {
+ theme_manager_apply_theme_simple (manager, view);
+ margin = 3;
+ }
+ else if (strcmp (name, "blue") == 0) {
+ theme_manager_apply_theme_blue (manager, view);
+ margin = 0;
+ } else {
+ theme_manager_apply_theme_classic (manager, view);
+ margin = 3;
+ }
+ } else {
+ theme_manager_apply_theme_classic (manager, view);
+ margin = 3;
+ }
+
+ gossip_chat_view_set_margin (view, margin);
+ gossip_chat_view_set_irc_style (view, priv->irc_style);
+}
+
+GossipThemeManager *
+gossip_theme_manager_get (void)
+{
+ static GossipThemeManager *manager = NULL;
+
+ if (!manager) {
+ manager = g_object_new (GOSSIP_TYPE_THEME_MANAGER, NULL);
+ }
+
+ return manager;
+}
+
+const gchar **
+gossip_theme_manager_get_themes (void)
+{
+ return themes;
+}
+
+void
+gossip_theme_manager_apply (GossipThemeManager *manager,
+ GossipChatView *view,
+ const gchar *name)
+{
+ GossipThemeManagerPriv *priv;
+
+ priv = GET_PRIV (manager);
+
+ theme_manager_apply_theme (manager, view, name);
+}
+
+void
+gossip_theme_manager_apply_saved (GossipThemeManager *manager,
+ GossipChatView *view)
+{
+ GossipThemeManagerPriv *priv;
+
+ priv = GET_PRIV (manager);
+
+ theme_manager_apply_theme (manager, view, priv->name);
+}
+
+/* FIXME: A bit ugly. We should probably change the scheme so that instead of
+ * the manager signalling, views are registered and applied to automatically.
+ */
+void
+gossip_theme_manager_update_show_avatars (GossipThemeManager *manager,
+ GossipChatView *view,
+ gboolean show)
+{
+ GossipThemeManagerPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextTag *tag_text_self, *tag_text_other;
+ GtkTextTag *tag_image_self, *tag_image_other;
+
+ priv = GET_PRIV (manager);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ tag_text_self = gtk_text_tag_table_lookup (table, "fancy-header-self-avatar");
+ tag_text_other = gtk_text_tag_table_lookup (table, "fancy-header-other-avatar");
+
+ tag_image_self = gtk_text_tag_table_lookup (table, "fancy-avatar-self");
+ tag_image_other = gtk_text_tag_table_lookup (table, "fancy-avatar-other");
+
+ if (!show) {
+ g_object_set (tag_text_self,
+ "rise", 0,
+ NULL);
+ g_object_set (tag_text_other,
+ "rise", 0,
+ NULL);
+ g_object_set (tag_image_self,
+ "invisible", TRUE,
+ NULL);
+ g_object_set (tag_image_other,
+ "invisible", TRUE,
+ NULL);
+ } else {
+ GtkTextAttributes *attrs;
+ gint size;
+ gint rise;
+
+ attrs = gtk_text_view_get_default_attributes (GTK_TEXT_VIEW (view));
+ size = pango_font_description_get_size (attrs->font);
+ rise = MAX (0, (32 * PANGO_SCALE - size) / 2.0);
+
+ g_object_set (tag_text_self,
+ "rise", rise,
+ NULL);
+ g_object_set (tag_text_other,
+ "rise", rise,
+ NULL);
+ g_object_set (tag_image_self,
+ "invisible", FALSE,
+ NULL);
+ g_object_set (tag_image_other,
+ "invisible", FALSE,
+ NULL);
+
+ gtk_text_attributes_unref (attrs);
+ }
+}
+
diff --git a/trunk/libempathy-gtk/gossip-theme-manager.h b/trunk/libempathy-gtk/gossip-theme-manager.h
new file mode 100644
index 000000000..bb7e3cf70
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-theme-manager.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Richard Hult <richard@imendio.com>
+ */
+
+#ifndef __GOSSIP_THEME_MANAGER_H__
+#define __GOSSIP_THEME_MANAGER_H__
+
+#include <glib-object.h>
+
+#include "gossip-chat-view.h"
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_THEME_MANAGER (gossip_theme_manager_get_type ())
+#define GOSSIP_THEME_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_THEME_MANAGER, GossipThemeManager))
+#define GOSSIP_THEME_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GOSSIP_TYPE_THEME_MANAGER, GossipThemeManagerClass))
+#define GOSSIP_IS_THEME_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_THEME_MANAGER))
+#define GOSSIP_IS_THEME_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_THEME_MANAGER))
+#define GOSSIP_THEME_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_THEME_MANAGER, GossipThemeManagerClass))
+
+typedef struct _GossipThemeManager GossipThemeManager;
+typedef struct _GossipThemeManagerClass GossipThemeManagerClass;
+
+struct _GossipThemeManager {
+ GObject parent;
+};
+
+struct _GossipThemeManagerClass {
+ GObjectClass parent_class;
+};
+
+GType gossip_theme_manager_get_type (void) G_GNUC_CONST;
+GossipThemeManager *gossip_theme_manager_get (void);
+const gchar ** gossip_theme_manager_get_themes (void);
+void gossip_theme_manager_apply (GossipThemeManager *manager,
+ GossipChatView *view,
+ const gchar *theme);
+void gossip_theme_manager_apply_saved (GossipThemeManager *manager,
+ GossipChatView *view);
+void gossip_theme_manager_update_show_avatars (GossipThemeManager *manager,
+ GossipChatView *view,
+ gboolean show);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_THEME_MANAGER_H__ */
diff --git a/trunk/libempathy-gtk/gossip-ui-utils.c b/trunk/libempathy-gtk/gossip-ui-utils.c
new file mode 100644
index 000000000..cf12a51bb
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-ui-utils.c
@@ -0,0 +1,1336 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ *
+ * Part of this file is copied from GtkSourceView (gtksourceiter.c):
+ * Paolo Maggi
+ * Jeroen Zwartepoorte
+ */
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <libgnome/libgnome.h>
+
+#include <libmissioncontrol/mc-profile.h>
+
+#include <libempathy/gossip-paths.h>
+#include <libempathy/gossip-debug.h>
+
+#include "gossip-ui-utils.h"
+#include "empathy-images.h"
+
+#define DEBUG_DOMAIN "UiUtils"
+
+struct SizeData {
+ gint width;
+ gint height;
+ gboolean preserve_aspect_ratio;
+};
+
+static GladeXML *
+get_glade_file (const gchar *filename,
+ const gchar *root,
+ const gchar *domain,
+ const gchar *first_required_widget,
+ va_list args)
+{
+ gchar *path;
+ GladeXML *gui;
+ const char *name;
+ GtkWidget **widget_ptr;
+
+ path = gossip_paths_get_glade_path (filename);
+ gui = glade_xml_new (path, root, domain);
+ g_free (path);
+
+ if (!gui) {
+ g_warning ("Couldn't find necessary glade file '%s'", filename);
+ return NULL;
+ }
+
+ for (name = first_required_widget; name; name = va_arg (args, char *)) {
+ widget_ptr = va_arg (args, void *);
+
+ *widget_ptr = glade_xml_get_widget (gui, name);
+
+ if (!*widget_ptr) {
+ g_warning ("Glade file '%s' is missing widget '%s'.",
+ filename, name);
+ continue;
+ }
+ }
+
+ return gui;
+}
+
+void
+gossip_glade_get_file_simple (const gchar *filename,
+ const gchar *root,
+ const gchar *domain,
+ const gchar *first_required_widget, ...)
+{
+ va_list args;
+ GladeXML *gui;
+
+ va_start (args, first_required_widget);
+
+ gui = get_glade_file (filename,
+ root,
+ domain,
+ first_required_widget,
+ args);
+
+ va_end (args);
+
+ if (!gui) {
+ return;
+ }
+
+ g_object_unref (gui);
+}
+
+GladeXML *
+gossip_glade_get_file (const gchar *filename,
+ const gchar *root,
+ const gchar *domain,
+ const gchar *first_required_widget, ...)
+{
+ va_list args;
+ GladeXML *gui;
+
+ va_start (args, first_required_widget);
+
+ gui = get_glade_file (filename,
+ root,
+ domain,
+ first_required_widget,
+ args);
+
+ va_end (args);
+
+ if (!gui) {
+ return NULL;
+ }
+
+ return gui;
+}
+
+void
+gossip_glade_connect (GladeXML *gui,
+ gpointer user_data,
+ gchar *first_widget, ...)
+{
+ va_list args;
+ const gchar *name;
+ const gchar *signal;
+ GtkWidget *widget;
+ gpointer *callback;
+
+ va_start (args, first_widget);
+
+ for (name = first_widget; name; name = va_arg (args, char *)) {
+ signal = va_arg (args, void *);
+ callback = va_arg (args, void *);
+
+ widget = glade_xml_get_widget (gui, name);
+ if (!widget) {
+ g_warning ("Glade file is missing widget '%s', aborting",
+ name);
+ continue;
+ }
+
+ g_signal_connect (widget,
+ signal,
+ G_CALLBACK (callback),
+ user_data);
+ }
+
+ va_end (args);
+}
+
+void
+gossip_glade_setup_size_group (GladeXML *gui,
+ GtkSizeGroupMode mode,
+ gchar *first_widget, ...)
+{
+ va_list args;
+ GtkWidget *widget;
+ GtkSizeGroup *size_group;
+ const gchar *name;
+
+ va_start (args, first_widget);
+
+ size_group = gtk_size_group_new (mode);
+
+ for (name = first_widget; name; name = va_arg (args, char *)) {
+ widget = glade_xml_get_widget (gui, name);
+ if (!widget) {
+ g_warning ("Glade file is missing widget '%s'", name);
+ continue;
+ }
+
+ gtk_size_group_add_widget (size_group, widget);
+ }
+
+ g_object_unref (size_group);
+
+ va_end (args);
+}
+
+GdkPixbuf *
+gossip_pixbuf_from_icon_name (const gchar *icon_name,
+ GtkIconSize icon_size)
+{
+ GtkIconTheme *theme;
+ GdkPixbuf *pixbuf = NULL;
+ GError *error = NULL;
+ gint w, h;
+ gint size = 48;
+
+ theme = gtk_icon_theme_get_default ();
+
+ if (gtk_icon_size_lookup (icon_size, &w, &h)) {
+ size = (w + h) / 2;
+ }
+
+ pixbuf = gtk_icon_theme_load_icon (theme,
+ icon_name,
+ size,
+ 0,
+ &error);
+ if (error) {
+ gossip_debug (DEBUG_DOMAIN, "Error loading icon: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+gossip_pixbuf_from_smiley (GossipSmiley type,
+ GtkIconSize icon_size)
+{
+ const gchar *icon_id;
+
+ switch (type) {
+ case GOSSIP_SMILEY_NORMAL: /* :) */
+ icon_id = "stock_smiley-1";
+ break;
+ case GOSSIP_SMILEY_WINK: /* ;) */
+ icon_id = "stock_smiley-3";
+ break;
+ case GOSSIP_SMILEY_BIGEYE: /* =) */
+ icon_id = "stock_smiley-2";
+ break;
+ case GOSSIP_SMILEY_NOSE: /* :-) */
+ icon_id = "stock_smiley-7";
+ break;
+ case GOSSIP_SMILEY_CRY: /* :'( */
+ icon_id = "stock_smiley-11";
+ break;
+ case GOSSIP_SMILEY_SAD: /* :( */
+ icon_id = "stock_smiley-4";
+ break;
+ case GOSSIP_SMILEY_SCEPTICAL: /* :/ */
+ icon_id = "stock_smiley-9";
+ break;
+ case GOSSIP_SMILEY_BIGSMILE: /* :D */
+ icon_id = "stock_smiley-6";
+ break;
+ case GOSSIP_SMILEY_INDIFFERENT: /* :| */
+ icon_id = "stock_smiley-8";
+ break;
+ case GOSSIP_SMILEY_TOUNGE: /* :p */
+ icon_id = "stock_smiley-10";
+ break;
+ case GOSSIP_SMILEY_SHOCKED: /* :o */
+ icon_id = "stock_smiley-5";
+ break;
+ case GOSSIP_SMILEY_COOL: /* 8) */
+ icon_id = "stock_smiley-15";
+ break;
+ case GOSSIP_SMILEY_SORRY: /* *| */
+ icon_id = "stock_smiley-12";
+ break;
+ case GOSSIP_SMILEY_KISS: /* :* */
+ icon_id = "stock_smiley-13";
+ break;
+ case GOSSIP_SMILEY_SHUTUP: /* :# */
+ icon_id = "stock_smiley-14";
+ break;
+ case GOSSIP_SMILEY_YAWN: /* |O */
+ icon_id = "";
+ break;
+ case GOSSIP_SMILEY_CONFUSED: /* :$ */
+ icon_id = "stock_smiley-17";
+ break;
+ case GOSSIP_SMILEY_ANGEL: /* O) */
+ icon_id = "stock_smiley-18";
+ break;
+ case GOSSIP_SMILEY_OOOH: /* :x */
+ icon_id = "stock_smiley-19";
+ break;
+ case GOSSIP_SMILEY_LOOKAWAY: /* *) */
+ icon_id = "stock_smiley-20";
+ break;
+ case GOSSIP_SMILEY_BLUSH: /* *S */
+ icon_id = "stock_smiley-23";
+ break;
+ case GOSSIP_SMILEY_COOLBIGSMILE: /* 8D */
+ icon_id = "stock_smiley-25";
+ break;
+ case GOSSIP_SMILEY_ANGRY: /* :@ */
+ icon_id = "stock_smiley-16";
+ break;
+ case GOSSIP_SMILEY_BOSS: /* @) */
+ icon_id = "stock_smiley-21";
+ break;
+ case GOSSIP_SMILEY_MONKEY: /* #) */
+ icon_id = "stock_smiley-22";
+ break;
+ case GOSSIP_SMILEY_SILLY: /* O) */
+ icon_id = "stock_smiley-24";
+ break;
+ case GOSSIP_SMILEY_SICK: /* +o( */
+ icon_id = "stock_smiley-26";
+ break;
+
+ default:
+ g_assert_not_reached ();
+ icon_id = NULL;
+ }
+
+
+ return gossip_pixbuf_from_icon_name (icon_id, icon_size);
+}
+
+const gchar *
+gossip_icon_name_from_account (McAccount *account)
+{
+ McProfile *profile;
+
+ profile = mc_account_get_profile (account);
+
+ return mc_profile_get_icon_name (profile);
+}
+
+const gchar *
+gossip_icon_name_for_presence_state (McPresence state)
+{
+ switch (state) {
+ case MC_PRESENCE_AVAILABLE:
+ return EMPATHY_IMAGE_AVAILABLE;
+ case MC_PRESENCE_DO_NOT_DISTURB:
+ return EMPATHY_IMAGE_BUSY;
+ case MC_PRESENCE_AWAY:
+ return EMPATHY_IMAGE_AWAY;
+ case MC_PRESENCE_EXTENDED_AWAY:
+ return EMPATHY_IMAGE_EXT_AWAY;
+ case MC_PRESENCE_HIDDEN:
+ case MC_PRESENCE_OFFLINE:
+ case MC_PRESENCE_UNSET:
+ return EMPATHY_IMAGE_OFFLINE;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return NULL;
+}
+
+const gchar *
+gossip_icon_name_for_presence (GossipPresence *presence)
+{
+ McPresence state;
+
+ g_return_val_if_fail (GOSSIP_IS_PRESENCE (presence),
+ EMPATHY_IMAGE_OFFLINE);
+
+ state = gossip_presence_get_state (presence);
+
+ return gossip_icon_name_for_presence_state (state);
+}
+
+const gchar *
+gossip_icon_name_for_contact (GossipContact *contact)
+{
+ GossipPresence *presence;
+ GossipSubscription subscription;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact),
+ EMPATHY_IMAGE_OFFLINE);
+
+ presence = gossip_contact_get_presence (contact);
+
+ if (presence) {
+ return gossip_icon_name_for_presence (presence);
+ }
+
+ subscription = gossip_contact_get_subscription (contact);
+
+ if (subscription != GOSSIP_SUBSCRIPTION_BOTH &&
+ subscription != GOSSIP_SUBSCRIPTION_TO) {
+ return EMPATHY_IMAGE_PENDING;
+ }
+
+ return EMPATHY_IMAGE_OFFLINE;
+}
+
+GdkPixbuf *
+gossip_pixbuf_avatar_from_contact (GossipContact *contact)
+{
+ GdkPixbuf *pixbuf;
+ GdkPixbufLoader *loader;
+ GossipAvatar *avatar;
+ GError *error = NULL;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ avatar = gossip_contact_get_avatar (contact);
+ if (!avatar) {
+ return NULL;
+ }
+
+ loader = gdk_pixbuf_loader_new ();
+
+ if (!gdk_pixbuf_loader_write (loader, avatar->data, avatar->len, &error)) {
+ g_warning ("Couldn't write avatar image:%p with "
+ "length:%" G_GSIZE_FORMAT " to pixbuf loader: %s",
+ avatar->data, avatar->len, error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ gdk_pixbuf_loader_close (loader, NULL);
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+
+ g_object_ref (pixbuf);
+ g_object_unref (loader);
+
+ return pixbuf;
+}
+
+static void
+pixbuf_from_avatar_size_prepared_cb (GdkPixbufLoader *loader,
+ int width,
+ int height,
+ struct SizeData *data)
+{
+ g_return_if_fail (width > 0 && height > 0);
+
+ if (data->preserve_aspect_ratio && (data->width > 0 || data->height > 0)) {
+ if (width < data->width && height < data->height) {
+ width = width;
+ height = height;
+ }
+
+ if (data->width < 0) {
+ width = width * (double) data->height / (gdouble) height;
+ height = data->height;
+ } else if (data->height < 0) {
+ height = height * (double) data->width / (double) width;
+ width = data->width;
+ } else if ((double) height * (double) data->width >
+ (double) width * (double) data->height) {
+ width = 0.5 + (double) width * (double) data->height / (double) height;
+ height = data->height;
+ } else {
+ height = 0.5 + (double) height * (double) data->width / (double) width;
+ width = data->width;
+ }
+ } else {
+ if (data->width > 0) {
+ width = data->width;
+ }
+
+ if (data->height > 0) {
+ height = data->height;
+ }
+ }
+
+ gdk_pixbuf_loader_set_size (loader, width, height);
+}
+
+GdkPixbuf *
+gossip_pixbuf_from_avatar_scaled (GossipAvatar *avatar,
+ gint width,
+ gint height)
+{
+ GdkPixbuf *pixbuf;
+ GdkPixbufLoader *loader;
+ struct SizeData data;
+ GError *error = NULL;
+
+ if (!avatar) {
+ return NULL;
+ }
+
+ data.width = width;
+ data.height = height;
+ data.preserve_aspect_ratio = TRUE;
+
+ loader = gdk_pixbuf_loader_new ();
+
+ g_signal_connect (loader, "size-prepared",
+ G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
+ &data);
+
+ if (!gdk_pixbuf_loader_write (loader, avatar->data, avatar->len, &error)) {
+ g_warning ("Couldn't write avatar image:%p with "
+ "length:%" G_GSIZE_FORMAT " to pixbuf loader: %s",
+ avatar->data, avatar->len, error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ gdk_pixbuf_loader_close (loader, NULL);
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+
+ g_object_ref (pixbuf);
+ g_object_unref (loader);
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+gossip_pixbuf_avatar_from_contact_scaled (GossipContact *contact,
+ gint width,
+ gint height)
+{
+ GossipAvatar *avatar;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ avatar = gossip_contact_get_avatar (contact);
+
+ return gossip_pixbuf_from_avatar_scaled (avatar, width, height);
+}
+/* Stolen from GtkSourceView, hence the weird intendation. Please keep it like
+ * that to make it easier to apply changes from the original code.
+ */
+#define GTK_TEXT_UNKNOWN_CHAR 0xFFFC
+
+/* this function acts like g_utf8_offset_to_pointer() except that if it finds a
+ * decomposable character it consumes the decomposition length from the given
+ * offset. So it's useful when the offset was calculated for the normalized
+ * version of str, but we need a pointer to str itself. */
+static const gchar *
+pointer_from_offset_skipping_decomp (const gchar *str, gint offset)
+{
+ gchar *casefold, *normal;
+ const gchar *p, *q;
+
+ p = str;
+ while (offset > 0)
+ {
+ q = g_utf8_next_char (p);
+ casefold = g_utf8_casefold (p, q - p);
+ normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ offset -= g_utf8_strlen (normal, -1);
+ g_free (casefold);
+ g_free (normal);
+ p = q;
+ }
+ return p;
+}
+
+static const gchar *
+g_utf8_strcasestr (const gchar *haystack, const gchar *needle)
+{
+ gsize needle_len;
+ gsize haystack_len;
+ const gchar *ret = NULL;
+ gchar *p;
+ gchar *casefold;
+ gchar *caseless_haystack;
+ gint i;
+
+ g_return_val_if_fail (haystack != NULL, NULL);
+ g_return_val_if_fail (needle != NULL, NULL);
+
+ casefold = g_utf8_casefold (haystack, -1);
+ caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ needle_len = g_utf8_strlen (needle, -1);
+ haystack_len = g_utf8_strlen (caseless_haystack, -1);
+
+ if (needle_len == 0)
+ {
+ ret = (gchar *)haystack;
+ goto finally_1;
+ }
+
+ if (haystack_len < needle_len)
+ {
+ ret = NULL;
+ goto finally_1;
+ }
+
+ p = (gchar*)caseless_haystack;
+ needle_len = strlen (needle);
+ i = 0;
+
+ while (*p)
+ {
+ if ((strncmp (p, needle, needle_len) == 0))
+ {
+ ret = pointer_from_offset_skipping_decomp (haystack, i);
+ goto finally_1;
+ }
+
+ p = g_utf8_next_char (p);
+ i++;
+ }
+
+finally_1:
+ g_free (caseless_haystack);
+
+ return ret;
+}
+
+static gboolean
+g_utf8_caselessnmatch (const char *s1, const char *s2,
+ gssize n1, gssize n2)
+{
+ gchar *casefold;
+ gchar *normalized_s1;
+ gchar *normalized_s2;
+ gint len_s1;
+ gint len_s2;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (s1 != NULL, FALSE);
+ g_return_val_if_fail (s2 != NULL, FALSE);
+ g_return_val_if_fail (n1 > 0, FALSE);
+ g_return_val_if_fail (n2 > 0, FALSE);
+
+ casefold = g_utf8_casefold (s1, n1);
+ normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ casefold = g_utf8_casefold (s2, n2);
+ normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ len_s1 = strlen (normalized_s1);
+ len_s2 = strlen (normalized_s2);
+
+ if (len_s1 < len_s2)
+ goto finally_2;
+
+ ret = (strncmp (normalized_s1, normalized_s2, len_s2) == 0);
+
+finally_2:
+ g_free (normalized_s1);
+ g_free (normalized_s2);
+
+ return ret;
+}
+
+static void
+forward_chars_with_skipping (GtkTextIter *iter,
+ gint count,
+ gboolean skip_invisible,
+ gboolean skip_nontext,
+ gboolean skip_decomp)
+{
+ gint i;
+
+ g_return_if_fail (count >= 0);
+
+ i = count;
+
+ while (i > 0)
+ {
+ gboolean ignored = FALSE;
+
+ /* minimal workaround to avoid the infinite loop of bug #168247.
+ * It doesn't fix the problemjust the symptom...
+ */
+ if (gtk_text_iter_is_end (iter))
+ return;
+
+ if (skip_nontext && gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR)
+ ignored = TRUE;
+
+ if (!ignored && skip_invisible &&
+ /* _gtk_text_btree_char_is_invisible (iter)*/ FALSE)
+ ignored = TRUE;
+
+ if (!ignored && skip_decomp)
+ {
+ /* being UTF8 correct sucks; this accounts for extra
+ offsets coming from canonical decompositions of
+ UTF8 characters (e.g. accented characters) which
+ g_utf8_normalize() performs */
+ gchar *normal;
+ gchar buffer[6];
+ gint buffer_len;
+
+ buffer_len = g_unichar_to_utf8 (gtk_text_iter_get_char (iter), buffer);
+ normal = g_utf8_normalize (buffer, buffer_len, G_NORMALIZE_NFD);
+ i -= (g_utf8_strlen (normal, -1) - 1);
+ g_free (normal);
+ }
+
+ gtk_text_iter_forward_char (iter);
+
+ if (!ignored)
+ --i;
+ }
+}
+
+static gboolean
+lines_match (const GtkTextIter *start,
+ const gchar **lines,
+ gboolean visible_only,
+ gboolean slice,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end)
+{
+ GtkTextIter next;
+ gchar *line_text;
+ const gchar *found;
+ gint offset;
+
+ if (*lines == NULL || **lines == '\0')
+ {
+ if (match_start)
+ *match_start = *start;
+ if (match_end)
+ *match_end = *start;
+ return TRUE;
+ }
+
+ next = *start;
+ gtk_text_iter_forward_line (&next);
+
+ /* No more text in buffer, but *lines is nonempty */
+ if (gtk_text_iter_equal (start, &next))
+ return FALSE;
+
+ if (slice)
+ {
+ if (visible_only)
+ line_text = gtk_text_iter_get_visible_slice (start, &next);
+ else
+ line_text = gtk_text_iter_get_slice (start, &next);
+ }
+ else
+ {
+ if (visible_only)
+ line_text = gtk_text_iter_get_visible_text (start, &next);
+ else
+ line_text = gtk_text_iter_get_text (start, &next);
+ }
+
+ if (match_start) /* if this is the first line we're matching */
+ {
+ found = g_utf8_strcasestr (line_text, *lines);
+ }
+ else
+ {
+ /* If it's not the first line, we have to match from the
+ * start of the line.
+ */
+ if (g_utf8_caselessnmatch (line_text, *lines, strlen (line_text),
+ strlen (*lines)))
+ found = line_text;
+ else
+ found = NULL;
+ }
+
+ if (found == NULL)
+ {
+ g_free (line_text);
+ return FALSE;
+ }
+
+ /* Get offset to start of search string */
+ offset = g_utf8_strlen (line_text, found - line_text);
+
+ next = *start;
+
+ /* If match start needs to be returned, set it to the
+ * start of the search string.
+ */
+ forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
+ if (match_start)
+ {
+ *match_start = next;
+ }
+
+ /* Go to end of search string */
+ forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
+
+ g_free (line_text);
+
+ ++lines;
+
+ if (match_end)
+ *match_end = next;
+
+ /* pass NULL for match_start, since we don't need to find the
+ * start again.
+ */
+ return lines_match (&next, lines, visible_only, slice, NULL, match_end);
+}
+
+/* strsplit () that retains the delimiter as part of the string. */
+static gchar **
+strbreakup (const char *string,
+ const char *delimiter,
+ gint max_tokens)
+{
+ GSList *string_list = NULL, *slist;
+ gchar **str_array, *s, *casefold, *new_string;
+ guint i, n = 1;
+
+ g_return_val_if_fail (string != NULL, NULL);
+ g_return_val_if_fail (delimiter != NULL, NULL);
+
+ if (max_tokens < 1)
+ max_tokens = G_MAXINT;
+
+ s = strstr (string, delimiter);
+ if (s)
+ {
+ guint delimiter_len = strlen (delimiter);
+
+ do
+ {
+ guint len;
+
+ len = s - string + delimiter_len;
+ new_string = g_new (gchar, len + 1);
+ strncpy (new_string, string, len);
+ new_string[len] = 0;
+ casefold = g_utf8_casefold (new_string, -1);
+ g_free (new_string);
+ new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+ string_list = g_slist_prepend (string_list, new_string);
+ n++;
+ string = s + delimiter_len;
+ s = strstr (string, delimiter);
+ } while (--max_tokens && s);
+ }
+
+ if (*string)
+ {
+ n++;
+ casefold = g_utf8_casefold (string, -1);
+ new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+ string_list = g_slist_prepend (string_list, new_string);
+ }
+
+ str_array = g_new (gchar*, n);
+
+ i = n - 1;
+
+ str_array[i--] = NULL;
+ for (slist = string_list; slist; slist = slist->next)
+ str_array[i--] = slist->data;
+
+ g_slist_free (string_list);
+
+ return str_array;
+}
+
+gboolean
+gossip_text_iter_forward_search (const GtkTextIter *iter,
+ const gchar *str,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end,
+ const GtkTextIter *limit)
+{
+ gchar **lines = NULL;
+ GtkTextIter match;
+ gboolean retval = FALSE;
+ GtkTextIter search;
+ gboolean visible_only;
+ gboolean slice;
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (str != NULL, FALSE);
+
+ if (limit && gtk_text_iter_compare (iter, limit) >= 0)
+ return FALSE;
+
+ if (*str == '\0') {
+ /* If we can move one char, return the empty string there */
+ match = *iter;
+
+ if (gtk_text_iter_forward_char (&match)) {
+ if (limit && gtk_text_iter_equal (&match, limit)) {
+ return FALSE;
+ }
+
+ if (match_start) {
+ *match_start = match;
+ }
+ if (match_end) {
+ *match_end = match;
+ }
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+ }
+
+ visible_only = TRUE;
+ slice = FALSE;
+
+ /* locate all lines */
+ lines = strbreakup (str, "\n", -1);
+
+ search = *iter;
+
+ do {
+ /* This loop has an inefficient worst-case, where
+ * gtk_text_iter_get_text () is called repeatedly on
+ * a single line.
+ */
+ GtkTextIter end;
+
+ if (limit && gtk_text_iter_compare (&search, limit) >= 0) {
+ break;
+ }
+
+ if (lines_match (&search, (const gchar**)lines,
+ visible_only, slice, &match, &end)) {
+ if (limit == NULL ||
+ (limit && gtk_text_iter_compare (&end, limit) <= 0)) {
+ retval = TRUE;
+
+ if (match_start) {
+ *match_start = match;
+ }
+ if (match_end) {
+ *match_end = end;
+ }
+ }
+ break;
+ }
+ } while (gtk_text_iter_forward_line (&search));
+
+ g_strfreev ((gchar**)lines);
+
+ return retval;
+}
+
+static const gchar *
+g_utf8_strrcasestr (const gchar *haystack, const gchar *needle)
+{
+ gsize needle_len;
+ gsize haystack_len;
+ const gchar *ret = NULL;
+ gchar *p;
+ gchar *casefold;
+ gchar *caseless_haystack;
+ gint i;
+
+ g_return_val_if_fail (haystack != NULL, NULL);
+ g_return_val_if_fail (needle != NULL, NULL);
+
+ casefold = g_utf8_casefold (haystack, -1);
+ caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ needle_len = g_utf8_strlen (needle, -1);
+ haystack_len = g_utf8_strlen (caseless_haystack, -1);
+
+ if (needle_len == 0)
+ {
+ ret = (gchar *)haystack;
+ goto finally_1;
+ }
+
+ if (haystack_len < needle_len)
+ {
+ ret = NULL;
+ goto finally_1;
+ }
+
+ i = haystack_len - needle_len;
+ p = g_utf8_offset_to_pointer (caseless_haystack, i);
+ needle_len = strlen (needle);
+
+ while (p >= caseless_haystack)
+ {
+ if (strncmp (p, needle, needle_len) == 0)
+ {
+ ret = pointer_from_offset_skipping_decomp (haystack, i);
+ goto finally_1;
+ }
+
+ p = g_utf8_prev_char (p);
+ i--;
+ }
+
+finally_1:
+ g_free (caseless_haystack);
+
+ return ret;
+}
+
+static gboolean
+backward_lines_match (const GtkTextIter *start,
+ const gchar **lines,
+ gboolean visible_only,
+ gboolean slice,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end)
+{
+ GtkTextIter line, next;
+ gchar *line_text;
+ const gchar *found;
+ gint offset;
+
+ if (*lines == NULL || **lines == '\0')
+ {
+ if (match_start)
+ *match_start = *start;
+ if (match_end)
+ *match_end = *start;
+ return TRUE;
+ }
+
+ line = next = *start;
+ if (gtk_text_iter_get_line_offset (&next) == 0)
+ {
+ if (!gtk_text_iter_backward_line (&next))
+ return FALSE;
+ }
+ else
+ gtk_text_iter_set_line_offset (&next, 0);
+
+ if (slice)
+ {
+ if (visible_only)
+ line_text = gtk_text_iter_get_visible_slice (&next, &line);
+ else
+ line_text = gtk_text_iter_get_slice (&next, &line);
+ }
+ else
+ {
+ if (visible_only)
+ line_text = gtk_text_iter_get_visible_text (&next, &line);
+ else
+ line_text = gtk_text_iter_get_text (&next, &line);
+ }
+
+ if (match_start) /* if this is the first line we're matching */
+ {
+ found = g_utf8_strrcasestr (line_text, *lines);
+ }
+ else
+ {
+ /* If it's not the first line, we have to match from the
+ * start of the line.
+ */
+ if (g_utf8_caselessnmatch (line_text, *lines, strlen (line_text),
+ strlen (*lines)))
+ found = line_text;
+ else
+ found = NULL;
+ }
+
+ if (found == NULL)
+ {
+ g_free (line_text);
+ return FALSE;
+ }
+
+ /* Get offset to start of search string */
+ offset = g_utf8_strlen (line_text, found - line_text);
+
+ forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
+
+ /* If match start needs to be returned, set it to the
+ * start of the search string.
+ */
+ if (match_start)
+ {
+ *match_start = next;
+ }
+
+ /* Go to end of search string */
+ forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
+
+ g_free (line_text);
+
+ ++lines;
+
+ if (match_end)
+ *match_end = next;
+
+ /* try to match the rest of the lines forward, passing NULL
+ * for match_start so lines_match will try to match the entire
+ * line */
+ return lines_match (&next, lines, visible_only,
+ slice, NULL, match_end);
+}
+
+gboolean
+gossip_text_iter_backward_search (const GtkTextIter *iter,
+ const gchar *str,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end,
+ const GtkTextIter *limit)
+{
+ gchar **lines = NULL;
+ GtkTextIter match;
+ gboolean retval = FALSE;
+ GtkTextIter search;
+ gboolean visible_only;
+ gboolean slice;
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (str != NULL, FALSE);
+
+ if (limit && gtk_text_iter_compare (iter, limit) <= 0)
+ return FALSE;
+
+ if (*str == '\0')
+ {
+ /* If we can move one char, return the empty string there */
+ match = *iter;
+
+ if (gtk_text_iter_backward_char (&match))
+ {
+ if (limit && gtk_text_iter_equal (&match, limit))
+ return FALSE;
+
+ if (match_start)
+ *match_start = match;
+ if (match_end)
+ *match_end = match;
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ visible_only = TRUE;
+ slice = TRUE;
+
+ /* locate all lines */
+ lines = strbreakup (str, "\n", -1);
+
+ search = *iter;
+
+ while (TRUE)
+ {
+ /* This loop has an inefficient worst-case, where
+ * gtk_text_iter_get_text () is called repeatedly on
+ * a single line.
+ */
+ GtkTextIter end;
+
+ if (limit && gtk_text_iter_compare (&search, limit) <= 0)
+ break;
+
+ if (backward_lines_match (&search, (const gchar**)lines,
+ visible_only, slice, &match, &end))
+ {
+ if (limit == NULL || (limit &&
+ gtk_text_iter_compare (&end, limit) > 0))
+ {
+ retval = TRUE;
+
+ if (match_start)
+ *match_start = match;
+ if (match_end)
+ *match_end = end;
+ }
+ break;
+ }
+
+ if (gtk_text_iter_get_line_offset (&search) == 0)
+ {
+ if (!gtk_text_iter_backward_line (&search))
+ break;
+ }
+ else
+ {
+ gtk_text_iter_set_line_offset (&search, 0);
+ }
+ }
+
+ g_strfreev ((gchar**)lines);
+
+ return retval;
+}
+
+static gboolean
+window_get_is_on_current_workspace (GtkWindow *window)
+{
+ GdkWindow *gdk_window;
+
+ gdk_window = GTK_WIDGET (window)->window;
+ if (gdk_window) {
+ return !(gdk_window_get_state (gdk_window) &
+ GDK_WINDOW_STATE_ICONIFIED);
+ } else {
+ return FALSE;
+ }
+}
+
+/* Checks if the window is visible as in visible on the current workspace. */
+gboolean
+gossip_window_get_is_visible (GtkWindow *window)
+{
+ gboolean visible;
+
+ g_return_val_if_fail (window != NULL, FALSE);
+
+ g_object_get (window,
+ "visible", &visible,
+ NULL);
+
+ return visible && window_get_is_on_current_workspace (window);
+}
+
+/* Takes care of moving the window to the current workspace. */
+void
+gossip_window_present (GtkWindow *window,
+ gboolean steal_focus)
+{
+ gboolean visible;
+ gboolean on_current;
+ guint32 timestamp;
+
+ g_return_if_fail (window != NULL);
+
+ /* There are three cases: hidden, visible, visible on another
+ * workspace.
+ */
+
+ g_object_get (window,
+ "visible", &visible,
+ NULL);
+
+ on_current = window_get_is_on_current_workspace (window);
+
+ if (visible && !on_current) {
+ /* Hide it so present brings it to the current workspace. */
+ gtk_widget_hide (GTK_WIDGET (window));
+ }
+
+ timestamp = gtk_get_current_event_time ();
+ if (steal_focus && timestamp != GDK_CURRENT_TIME) {
+ gtk_window_present_with_time (window, timestamp);
+ } else {
+ gtk_window_present (window);
+ }
+}
+
+/* The URL opening code can't handle schemeless strings, so we try to be
+ * smart and add http if there is no scheme or doesn't look like a mail
+ * address. This should work in most cases, and let us click on strings
+ * like "www.gnome.org".
+ */
+static gchar *
+fixup_url (const gchar *url)
+{
+ gchar *real_url;
+
+ if (!g_str_has_prefix (url, "http://") &&
+ !strstr (url, ":/") &&
+ !strstr (url, "@")) {
+ real_url = g_strdup_printf ("http://%s", url);
+ } else {
+ real_url = g_strdup (url);
+ }
+
+ return real_url;
+}
+
+void
+gossip_url_show (const char *url)
+{
+ gchar *real_url;
+ GError *error = NULL;
+
+ real_url = fixup_url (url);
+ gnome_url_show (real_url, &error);
+ if (error) {
+ g_warning ("Couldn't show URL:'%s'", real_url);
+ g_error_free (error);
+ }
+
+ g_free (real_url);
+}
+
+static void
+link_button_hook (GtkLinkButton *button,
+ const gchar *link,
+ gpointer user_data)
+{
+ gossip_url_show (link);
+}
+
+GtkWidget *
+gossip_link_button_new (const gchar *url,
+ const gchar *title)
+{
+ static gboolean hook = FALSE;
+
+ if (!hook) {
+ hook = TRUE;
+ gtk_link_button_set_uri_hook (link_button_hook, NULL, NULL);
+ }
+
+ return gtk_link_button_new_with_label (url, title);
+}
+
+/* FIXME: Do this in a proper way at some point, probably in GTK+? */
+void
+gossip_window_set_default_icon_name (const gchar *name)
+{
+ gtk_window_set_default_icon_name (name);
+}
+
+void
+gossip_toggle_button_set_state_quietly (GtkWidget *widget,
+ GCallback callback,
+ gpointer user_data,
+ gboolean active)
+{
+ g_return_if_fail (GTK_IS_TOGGLE_BUTTON (widget));
+
+ g_signal_handlers_block_by_func (widget, callback, user_data);
+ g_object_set (widget, "active", active, NULL);
+ g_signal_handlers_unblock_by_func (widget, callback, user_data);
+}
+
diff --git a/trunk/libempathy-gtk/gossip-ui-utils.h b/trunk/libempathy-gtk/gossip-ui-utils.h
new file mode 100644
index 000000000..d1625cfe3
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-ui-utils.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ * Copyright (C) 2007 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ *
+ * Part of this file is copied from GtkSourceView (gtksourceiter.c):
+ * Paolo Maggi
+ * Jeroen Zwartepoorte
+ */
+
+#ifndef __GOSSIP_UI_UTILS_H__
+#define __GOSSIP_UI_UTILS_H__
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <libmissioncontrol/mc-account.h>
+#include <libmissioncontrol/mc-profile.h>
+
+#include <libempathy/gossip-presence.h>
+#include <libempathy/gossip-contact.h>
+#include <libempathy/gossip-avatar.h>
+
+#include "gossip-chat-view.h"
+
+G_BEGIN_DECLS
+
+#define G_STR_EMPTY(x) ((x) == NULL || (x)[0] == '\0')
+
+/* Glade */
+void gossip_glade_get_file_simple (const gchar *filename,
+ const gchar *root,
+ const gchar *domain,
+ const gchar *first_required_widget,
+ ...);
+GladeXML * gossip_glade_get_file (const gchar *filename,
+ const gchar *root,
+ const gchar *domain,
+ const gchar *first_required_widget,
+ ...);
+void gossip_glade_connect (GladeXML *gui,
+ gpointer user_data,
+ gchar *first_widget,
+ ...);
+void gossip_glade_setup_size_group (GladeXML *gui,
+ GtkSizeGroupMode mode,
+ gchar *first_widget,
+ ...);
+/* Pixbufs */
+GdkPixbuf * gossip_pixbuf_from_icon_name (const gchar *icon_name,
+ GtkIconSize icon_size);
+GdkPixbuf * gossip_pixbuf_from_smiley (GossipSmiley type,
+ GtkIconSize icon_size);
+const gchar * gossip_icon_name_from_account (McAccount *account);
+const gchar * gossip_icon_name_for_presence_state (McPresence state);
+const gchar * gossip_icon_name_for_presence (GossipPresence *presence);
+const gchar * gossip_icon_name_for_contact (GossipContact *contact);
+GdkPixbuf * gossip_pixbuf_from_avatar_scaled (GossipAvatar *avatar,
+ gint width,
+ gint height);
+GdkPixbuf * gossip_pixbuf_avatar_from_contact (GossipContact *contact);
+GdkPixbuf * gossip_pixbuf_avatar_from_contact_scaled (GossipContact *contact,
+ gint width,
+ gint height);
+/* Text view */
+gboolean gossip_text_iter_forward_search (const GtkTextIter *iter,
+ const gchar *str,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end,
+ const GtkTextIter *limit);
+gboolean gossip_text_iter_backward_search (const GtkTextIter *iter,
+ const gchar *str,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end,
+ const GtkTextIter *limit);
+
+/* Windows */
+gboolean gossip_window_get_is_visible (GtkWindow *window);
+void gossip_window_present (GtkWindow *window,
+ gboolean steal_focus);
+void gossip_window_set_default_icon_name (const gchar *name);
+
+void gossip_url_show (const char *url);
+
+void gossip_toggle_button_set_state_quietly (GtkWidget *widget,
+ GCallback callback,
+ gpointer user_data,
+ gboolean active);
+GtkWidget *gossip_link_button_new (const gchar *url,
+ const gchar *title);
+
+
+G_END_DECLS
+
+#endif /* __GOSSIP_UI_UTILS_H__ */