aboutsummaryrefslogtreecommitdiffstats
path: root/libempathy-gtk
diff options
context:
space:
mode:
Diffstat (limited to 'libempathy-gtk')
-rw-r--r--libempathy-gtk/Makefile.am68
-rw-r--r--libempathy-gtk/empathy-accounts.glade843
-rw-r--r--libempathy-gtk/empathy-chat.glade700
-rw-r--r--libempathy-gtk/empathy.schemas.in237
-rw-r--r--libempathy-gtk/gossip-account-widget-generic.c309
-rw-r--r--libempathy-gtk/gossip-account-widget-generic.h38
-rw-r--r--libempathy-gtk/gossip-accounts-dialog.c1032
-rw-r--r--libempathy-gtk/gossip-accounts-dialog.h34
-rw-r--r--libempathy-gtk/gossip-cell-renderer-expander.c482
-rw-r--r--libempathy-gtk/gossip-cell-renderer-expander.h59
-rw-r--r--libempathy-gtk/gossip-cell-renderer-text.c368
-rw-r--r--libempathy-gtk/gossip-cell-renderer-text.h56
-rw-r--r--libempathy-gtk/gossip-chat-manager.c327
-rw-r--r--libempathy-gtk/gossip-chat-manager.h65
-rw-r--r--libempathy-gtk/gossip-chat-manager.loT7
-rw-r--r--libempathy-gtk/gossip-chat-view.c2151
-rw-r--r--libempathy-gtk/gossip-chat-view.h134
-rw-r--r--libempathy-gtk/gossip-chat-window.c1894
-rw-r--r--libempathy-gtk/gossip-chat-window.h74
-rw-r--r--libempathy-gtk/gossip-chat.c1295
-rw-r--r--libempathy-gtk/gossip-chat.h139
-rw-r--r--libempathy-gtk/gossip-contact-groups.c286
-rw-r--r--libempathy-gtk/gossip-contact-groups.dtd17
-rw-r--r--libempathy-gtk/gossip-contact-groups.h38
-rw-r--r--libempathy-gtk/gossip-contact-list.c2419
-rw-r--r--libempathy-gtk/gossip-contact-list.h72
-rw-r--r--libempathy-gtk/gossip-preferences.c885
-rw-r--r--libempathy-gtk/gossip-preferences.h55
-rw-r--r--libempathy-gtk/gossip-private-chat.c495
-rw-r--r--libempathy-gtk/gossip-private-chat.h62
-rw-r--r--libempathy-gtk/gossip-profile-chooser.c102
-rw-r--r--libempathy-gtk/gossip-profile-chooser.h34
-rw-r--r--libempathy-gtk/gossip-spell.c457
-rw-r--r--libempathy-gtk/gossip-spell.h39
-rw-r--r--libempathy-gtk/gossip-stock.c105
-rw-r--r--libempathy-gtk/gossip-stock.h60
-rw-r--r--libempathy-gtk/gossip-theme-manager.c1045
-rw-r--r--libempathy-gtk/gossip-theme-manager.h64
-rw-r--r--libempathy-gtk/gossip-ui-utils.c1360
-rw-r--r--libempathy-gtk/gossip-ui-utils.h115
40 files changed, 18022 insertions, 0 deletions
diff --git a/libempathy-gtk/Makefile.am b/libempathy-gtk/Makefile.am
new file mode 100644
index 000000000..162715684
--- /dev/null
+++ b/libempathy-gtk/Makefile.am
@@ -0,0 +1,68 @@
+AM_CPPFLAGS = \
+ -I. \
+ -I$(top_srcdir) \
+ -DDATADIR=\""$(datadir)"\" \
+ -DLOCALEDIR=\""$(datadir)/locale"\" \
+ $(EMPATHY_CFLAGS) \
+ $(WARN_CFLAGS)
+
+noinst_LTLIBRARIES = libempathy-gtk.la
+
+libempathy_gtk_la_SOURCES = \
+ gossip-accounts-dialog.c gossip-accounts-dialog.h \
+ gossip-account-widget-generic.c gossip-account-widget-generic.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-stock.c gossip-stock.h \
+ gossip-spell.c gossip-spell.h \
+ gossip-contact-groups.c gossip-contact-groups.h \
+ gossip-contact-list.c gossip-contact-list.h \
+ gossip-preferences.c gossip-preferences.h \
+ gossip-theme-manager.c gossip-theme-manager.h \
+ gossip-chat.c gossip-chat.h \
+ gossip-chat-view.c gossip-chat-view.h \
+ gossip-chat-window.c gossip-chat-window.h \
+ gossip-private-chat.c gossip-private-chat.h \
+ gossip-ui-utils.c gossip-ui-utils.h
+
+libempathy_gtk_la_LIBADD = \
+ $(top_builddir)/libempathy/libempathy.la \
+ $(EMPATHY_LIBS)
+
+libempathy_gtk_includedir = $(includedir)/empathy/
+
+gladedir = $(datadir)/empathy
+glade_DATA = \
+ empathy-accounts.glade \
+ empathy-chat.glade
+
+dtddir = $(datadir)/empathy
+dtd_DATA = \
+ gossip-contact-groups.dtd
+
+schemasdir = $(GCONF_SCHEMA_FILE_DIR)
+schemas_in_files = empathy.schemas.in
+schemas_DATA = $(schemas_in_files:.schemas.in=.schemas)
+@INTLTOOL_SCHEMAS_RULE@
+
+if GCONF_SCHEMAS_INSTALL
+install-data-local:
+ if test -z "$(DESTDIR)" ; then \
+ for p in $(schemas_DATA) ; do \
+ GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(srcdir)/$$p ; \
+ done \
+ fi
+else
+install-data-local:
+endif
+
+EXTRA_DIST = \
+ $(glade_DATA) \
+ $(dtd_DATA) \
+ $(schemas_in_files) \
+ $(schemas_DATA)
+
+DISTCLEANFILES = \
+ $(schemas_DATA)
+
diff --git a/libempathy-gtk/empathy-accounts.glade b/libempathy-gtk/empathy-accounts.glade
new file mode 100644
index 000000000..a7bb391d2
--- /dev/null
+++ b/libempathy-gtk/empathy-accounts.glade
@@ -0,0 +1,843 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--*- mode: xml -*-->
+<glade-interface>
+ <widget class="GtkDialog" id="accounts_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Accounts</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox3">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkHBox" id="hbox146">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="spacing">18</property>
+ <child>
+ <widget class="GtkVBox" id="vbox195">
+ <property name="visible">True</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>
+ <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="enable_search">False</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="vbox196">
+ <property name="visible">True</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>
+ </widget>
+ <packing>
+ <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="spacing">6</property>
+ <property name="homogeneous">True</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>
+ </widget>
+ </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>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <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="spacing">18</property>
+ <child>
+ <widget class="GtkVBox" id="vbox_details">
+ <property name="visible">True</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="shadow_type">GTK_SHADOW_NONE</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">20</property>
+ <child>
+ <widget class="GtkVBox" id="vbox213">
+ <property name="visible">True</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="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label_type">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Jabber</property>
+ </widget>
+ <packing>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_name">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Imendio </property>
+ <property name="width_chars">0</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkImage" id="image_type">
+ <property name="visible">True</property>
+ <property name="yalign">0</property>
+ <property name="stock">gtk-cut</property>
+ <property name="icon_size">6</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ </widget>
+ </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_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <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="shadow_type">GTK_SHADOW_NONE</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment_settings">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">20</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_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkFrame" id="frame_new_account">
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment29">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">20</property>
+ <child>
+ <widget class="GtkVBox" id="vbox216">
+ <property name="visible">True</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="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label638">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Name:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_name</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label640">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Type:</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox180">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkEntry" id="entry_name">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip" translatable="yes">A unique name for this account to identify it personally to you.</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ </child>
+ </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="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox181">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkButton" id="button_cancel">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button_create">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment30">
+ <property name="visible">True</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <child>
+ <widget class="GtkHBox" id="hbox181">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkImage" id="image838">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label642">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Cr_eate!</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ <property name="position">1</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_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkFrame" id="frame_no_account">
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment21">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">12</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_markup">True</property>
+ <property name="wrap">True</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_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <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_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkWindow" id="account_jabber_settings">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">jabber account settings</property>
+ <property name="resizable">False</property>
+ <child>
+ <widget class="GtkTable" id="vbox_jabber_settings">
+ <property name="visible">True</property>
+ <property name="n_rows">6</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label_id">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Login I_D:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_id</property>
+ </widget>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_password">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Pass_word:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_password</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_resource">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Reso_urce:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_resource</property>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_server">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Server:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_server</property>
+ </widget>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_port">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Port:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_port</property>
+ </widget>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_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="invisible_char">*</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="invisible_char">*</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="GtkEntry" id="entry_port">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">*</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="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="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox174">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkEntry" id="entry_password">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button_forget">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip" translatable="yes">Forget password and clear the entry.</property>
+ <child>
+ <widget class="GtkImage" id="image834">
+ <property name="visible">True</property>
+ <property name="stock">gtk-clear</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </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="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button_change_password">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">C_hange</property>
+ <property name="use_underline">True</property>
+ </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">GTK_FILL</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="invisible_char">*</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button_register">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">R_egister</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkWindow" id="account_msn_settings">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">msn account settings</property>
+ <property name="resizable">False</property>
+ <child>
+ <widget class="GtkTable" id="vbox_msn_settings">
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label_id">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Login I_D:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_id</property>
+ </widget>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_password">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Pass_word:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_password</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox182">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkEntry" id="entry_password">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button_forget">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip" translatable="yes">Forget password and clear the entry.</property>
+ <child>
+ <widget class="GtkImage" id="image839">
+ <property name="visible">True</property>
+ <property name="stock">gtk-clear</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </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="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry_id">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_server">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Server:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_server</property>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</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="invisible_char">*</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="GtkLabel" id="label_port">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Port:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_port</property>
+ </widget>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry_port">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">*</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>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
diff --git a/libempathy-gtk/empathy-chat.glade b/libempathy-gtk/empathy-chat.glade
new file mode 100644
index 000000000..e8bd47cfe
--- /dev/null
+++ b/libempathy-gtk/empathy-chat.glade
@@ -0,0 +1,700 @@
+<?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="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/libempathy-gtk/empathy.schemas.in b/libempathy-gtk/empathy.schemas.in
new file mode 100644
index 000000000..243e6ce27
--- /dev/null
+++ b/libempathy-gtk/empathy.schemas.in
@@ -0,0 +1,237 @@
+<gconfschemafile>
+ <schemalist>
+
+ <schema>
+ <key>/schemas/apps/empathy/ui/show_offline</key>
+ <applyto>/apps/empathy/ui/show_offline</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Show offline contacts</short>
+ <long>
+ Whether or not to show contacts that are offline in the contact list.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/ui/show_avatars</key>
+ <applyto>/apps/empathy/ui/show_avatars</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Show avatars</short>
+ <long>
+ Whether or not to show avatars for contacts in the contact
+ list and chat windows.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/ui/compact_contact_list</key>
+ <applyto>/apps/empathy/ui/compact_contact_list</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Compact contact list</short>
+ <long>
+ Whether to show the contact list in compact mode or not.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/ui/main_window_hidden</key>
+ <applyto>/apps/empathy/ui/main_window_hidden</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Hide main window</short>
+ <long>
+ Hide the main window.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/ui/avatar_directory</key>
+ <applyto>/apps/empathy/ui/avatar_directory</applyto>
+ <owner>empathy</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>Default directory to select an avatar image from</short>
+ <long>
+ The last directory that an avatar image was chosen from.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/notifications/play_sounds</key>
+ <applyto>/apps/empathy/notifications/play_sounds</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Use notification sounds</short>
+ <long>
+ Whether or not to play a sound when messages arrive.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/notifications/sound_when_away</key>
+ <applyto>/apps/empathy/notifications/sound_when_away</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Enable sound when away</short>
+ <long>
+ Whether or not to play sounds when away.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/notifications/sound_when_busy</key>
+ <applyto>/apps/empathy/notifications/sound_when_busy</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Enable sound when busy</short>
+ <long>
+ Whether or not to play sounds when busy.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/notifications/popup_when_contact_available</key>
+ <applyto>/apps/empathy/notifications/popup_when_contact_available</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Enable popup when contact is available</short>
+ <long>
+ Whether or not to show a popup when a contact becomes available.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/ui/separate_chat_windows</key>
+ <applyto>/apps/empathy/ui/separate_chat_windows</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Open new chats in separate windows</short>
+ <long>
+ Always open a separate chat window for new chats.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/conversation/graphical_smileys</key>
+ <applyto>/apps/empathy/conversation/graphical_smileys</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Use graphical smileys</short>
+ <long>
+ Whether or not to convert smileys into graphical images in
+ conversations.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/conversation/theme</key>
+ <applyto>/apps/empathy/conversation/theme</applyto>
+ <owner>empathy</owner>
+ <type>string</type>
+ <default>classic</default>
+ <locale name="C">
+ <short>Chat window theme</short>
+ <long>
+ The theme that is used to display the conversation in chat windows.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/conversation/theme_chat_room</key>
+ <applyto>/apps/empathy/conversation/theme_chat_room</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Use theme for chat rooms</short>
+ <long>
+ Whether to use the theme for chat rooms or not.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/conversation/spell_checker_languages</key>
+ <applyto>/apps/empathy/conversation/spell_checker_languages</applyto>
+ <owner>empathy</owner>
+ <type>string</type>
+ <default>en</default>
+ <locale name="C">
+ <short>Spell checking languages</short>
+ <long>
+ Comma separated list of spell checker languages to use (e.g. en, fr, nl).
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/conversation/spell_checker_enabled</key>
+ <applyto>/apps/empathy/conversation/spell_checker_enabled</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Enable spell checker</short>
+ <long>
+ Whether or not to check words typed against the languages you
+ want to check with.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/hints/close_main_window</key>
+ <applyto>/apps/empathy/hints/close_main_window</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Show hint about closing the main window</short>
+ <long>
+ Whether or not to show the message dialog about closing the
+ main window with the 'x' button in the title bar.
+ </long>
+ </locale>
+ </schema>
+
+ </schemalist>
+</gconfschemafile>
+
+
+
+
diff --git a/libempathy-gtk/gossip-account-widget-generic.c b/libempathy-gtk/gossip-account-widget-generic.c
new file mode 100644
index 000000000..7f7846397
--- /dev/null
+++ b/libempathy-gtk/gossip-account-widget-generic.c
@@ -0,0 +1,309 @@
+/* -*- 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: 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;
+
+ 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/libempathy-gtk/gossip-account-widget-generic.h b/libempathy-gtk/gossip-account-widget-generic.h
new file mode 100644
index 000000000..1a3db63ae
--- /dev/null
+++ b/libempathy-gtk/gossip-account-widget-generic.h
@@ -0,0 +1,38 @@
+/* -*- 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: 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/libempathy-gtk/gossip-accounts-dialog.c b/libempathy-gtk/gossip-accounts-dialog.c
new file mode 100644
index 000000000..43d974778
--- /dev/null
+++ b/libempathy-gtk/gossip-accounts-dialog.c
@@ -0,0 +1,1032 @@
+/* -*- 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>
+ * 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/empathy-session.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"
+
+#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_cancel;
+
+ GtkWidget *image_type;
+ GtkWidget *label_name;
+ GtkWidget *label_type;
+ GtkWidget *settings_widget;
+
+ gboolean connecting_show;
+ guint connecting_id;
+ gboolean account_changed;
+} 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_cancel_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;
+ MissionControl *mc;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
+ selection = gtk_tree_view_get_selection (view);
+
+ mc = empathy_session_get_mission_control ();
+ 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 (mc, account, NULL);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COL_NAME, name,
+ COL_STATUS, status,
+ COL_ACCOUNT_POINTER, account,
+ -1);
+
+ accounts_dialog_status_changed_cb (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, "blah") == 0) {
+ } 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;
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gossip_pixbuf_from_account (account, GTK_ICON_SIZE_DIALOG);
+ gtk_image_set_from_pixbuf (GTK_IMAGE (dialog->image_type), pixbuf);
+ if (pixbuf) {
+ g_object_unref (pixbuf);
+ }
+
+ profile = mc_account_get_profile (account);
+
+ 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;
+ GdkPixbuf *pixbuf;
+ TelepathyConnectionStatus status;
+
+ gtk_tree_model_get (model, iter,
+ COL_STATUS, &status,
+ COL_ACCOUNT_POINTER, &account,
+ -1);
+
+ pixbuf = gossip_pixbuf_from_account (account, 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)
+{
+ MissionControl *mc;
+ 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;
+ }
+ }
+
+ mc = empathy_session_get_mission_control ();
+ status = mission_control_get_connection_status (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_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ 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)
+{
+ MissionControl *mc;
+ McAccount *account;
+
+ mc = empathy_session_get_mission_control ();
+ 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);
+
+ 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;
+ }
+}
+
+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_cancel_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)
+{
+ MissionControl *mc;
+ McAccountMonitor *monitor;
+ GList *accounts, *l;
+
+ mc = empathy_session_get_mission_control ();
+ monitor = mc_account_monitor_new ();
+
+ /* Disconnect signals */
+ g_signal_handlers_disconnect_by_func (monitor,
+ accounts_dialog_account_added_cb,
+ dialog);
+ g_signal_handlers_disconnect_by_func (monitor,
+ accounts_dialog_account_removed_cb,
+ dialog);
+ dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (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_free (dialog);
+}
+
+GtkWidget *
+gossip_accounts_dialog_show (void)
+{
+ static GossipAccountsDialog *dialog = NULL;
+ MissionControl *mc;
+ McAccountMonitor *monitor;
+ 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 ("empathy-accounts.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_cancel", &dialog->button_cancel,
+ "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_cancel", "clicked", accounts_dialog_button_cancel_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 */
+ mc = empathy_session_get_mission_control ();
+ monitor = mc_account_monitor_new ();
+
+ /* FIXME: connect account-enabled/disabled too */
+ g_signal_connect (monitor, "account-created",
+ G_CALLBACK (accounts_dialog_account_added_cb),
+ dialog);
+ g_signal_connect (monitor, "account-deleted",
+ G_CALLBACK (accounts_dialog_account_removed_cb),
+ dialog);
+ dbus_g_proxy_connect_signal (DBUS_G_PROXY (mc), "AccountStatusChanged",
+ G_CALLBACK (accounts_dialog_status_changed_cb),
+ dialog, NULL);
+
+ accounts_dialog_model_setup (dialog);
+ accounts_dialog_setup (dialog);
+
+ gtk_widget_show (dialog->window);
+
+ accounts_dialog_model_select_first (dialog);
+
+ return dialog->window;
+}
+
diff --git a/libempathy-gtk/gossip-accounts-dialog.h b/libempathy-gtk/gossip-accounts-dialog.h
new file mode 100644
index 000000000..b6b0593c7
--- /dev/null
+++ b/libempathy-gtk/gossip-accounts-dialog.h
@@ -0,0 +1,34 @@
+/* -*- 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_ACCOUNTS_DIALOG_H__
+#define __GOSSIP_ACCOUNTS_DIALOG_H__
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+GtkWidget *gossip_accounts_dialog_show (void);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_ACCOUNTS_DIALOG_H__ */
diff --git a/libempathy-gtk/gossip-cell-renderer-expander.c b/libempathy-gtk/gossip-cell-renderer-expander.c
new file mode 100644
index 000000000..e116ace7b
--- /dev/null
+++ b/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/libempathy-gtk/gossip-cell-renderer-expander.h b/libempathy-gtk/gossip-cell-renderer-expander.h
new file mode 100644
index 000000000..7df474668
--- /dev/null
+++ b/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/libempathy-gtk/gossip-cell-renderer-text.c b/libempathy-gtk/gossip-cell-renderer-text.c
new file mode 100644
index 000000000..2b54eedf5
--- /dev/null
+++ b/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/libempathy-gtk/gossip-cell-renderer-text.h b/libempathy-gtk/gossip-cell-renderer-text.h
new file mode 100644
index 000000000..9b5a413b7
--- /dev/null
+++ b/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/libempathy-gtk/gossip-chat-manager.c b/libempathy-gtk/gossip-chat-manager.c
new file mode 100644
index 000000000..86cd0ea3f
--- /dev/null
+++ b/libempathy-gtk/gossip-chat-manager.c
@@ -0,0 +1,327 @@
+/* -*- 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>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include <libgossip/gossip-account.h>
+#include <libgossip/gossip-event.h>
+#include <libgossip/gossip-session.h>
+#include <libgossip/gossip-message.h>
+#include <libgossip/gossip-debug.h>
+#include <libgossip/gossip-event-manager.h>
+
+#include "gossip-app.h"
+#include "gossip-chat.h"
+#include "gossip-chat-manager.h"
+
+#define DEBUG_DOMAIN "ChatManager"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHAT_MANAGER, GossipChatManagerPriv))
+
+typedef struct _GossipChatManagerPriv GossipChatManagerPriv;
+
+struct _GossipChatManagerPriv {
+ GHashTable *chats;
+ GHashTable *events;
+};
+
+static void chat_manager_finalize (GObject *object);
+static void chat_manager_new_message_cb (GossipSession *session,
+ GossipMessage *msg,
+ GossipChatManager *manager);
+static void chat_manager_event_activated_cb (GossipEventManager *event_manager,
+ GossipEvent *event,
+ GObject *object);
+static void chat_manager_get_chats_foreach (GossipContact *contact,
+ GossipPrivateChat *chat,
+ GList **chats);
+static void chat_manager_chat_removed_cb (GossipChatManager *manager,
+ GossipChat *chat,
+ gboolean is_last_ref);
+
+G_DEFINE_TYPE (GossipChatManager, gossip_chat_manager, G_TYPE_OBJECT);
+
+static void
+gossip_chat_manager_class_init (GossipChatManagerClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = chat_manager_finalize;
+
+ g_type_class_add_private (object_class, sizeof (GossipChatManagerPriv));
+}
+
+static void
+gossip_chat_manager_init (GossipChatManager *manager)
+{
+ GossipChatManagerPriv *priv;
+
+ priv = GET_PRIV (manager);
+
+ priv->chats = g_hash_table_new_full (gossip_contact_hash,
+ gossip_contact_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) g_object_unref);
+
+ priv->events = g_hash_table_new_full (gossip_contact_hash,
+ gossip_contact_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) g_object_unref);
+
+ /* Connect to signals on GossipSession to listen for new messages */
+ g_signal_connect (gossip_app_get_session (),
+ "new-message",
+ G_CALLBACK (chat_manager_new_message_cb),
+ manager);
+}
+
+static void
+chat_manager_finalize (GObject *object)
+{
+ GossipChatManagerPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ g_hash_table_destroy (priv->chats);
+ g_hash_table_destroy (priv->events);
+
+ G_OBJECT_CLASS (gossip_chat_manager_parent_class)->finalize (object);
+}
+
+static void
+chat_manager_new_message_cb (GossipSession *session,
+ GossipMessage *message,
+ GossipChatManager *manager)
+{
+ GossipChatManagerPriv *priv;
+ GossipPrivateChat *chat;
+ GossipContact *sender;
+ GossipEvent *event = NULL;
+ GossipEvent *old_event;
+
+ priv = GET_PRIV (manager);
+
+ sender = gossip_message_get_sender (message);
+ chat = g_hash_table_lookup (priv->chats, sender);
+
+ old_event = g_hash_table_lookup (priv->events, sender);
+
+ /* Add event to event manager if one doesn't exist already. */
+ if (!chat) {
+ gossip_debug (DEBUG_DOMAIN, "New chat for: %s",
+ gossip_contact_get_id (sender));
+ chat = gossip_chat_manager_get_chat (manager, sender);
+
+ if (!old_event) {
+ event = gossip_event_new (GOSSIP_EVENT_NEW_MESSAGE);
+ }
+ } else {
+ GossipChatWindow *window;
+
+ window = gossip_chat_get_window (GOSSIP_CHAT (chat));
+
+ if (!window && !old_event) {
+ event = gossip_event_new (GOSSIP_EVENT_NEW_MESSAGE);
+ }
+ }
+
+ gossip_private_chat_append_message (chat, message);
+
+ if (event) {
+ gchar *str;
+
+ str = g_strdup_printf (_("New message from %s"),
+ gossip_contact_get_name (sender));
+ g_object_set (event,
+ "message", str,
+ "data", message,
+ NULL);
+ g_free (str);
+
+ gossip_event_manager_add (gossip_app_get_event_manager (),
+ event,
+ chat_manager_event_activated_cb,
+ G_OBJECT (manager));
+
+ g_hash_table_insert (priv->events,
+ g_object_ref (sender),
+ g_object_ref (event));
+ }
+}
+
+static void
+chat_manager_event_activated_cb (GossipEventManager *event_manager,
+ GossipEvent *event,
+ GObject *object)
+{
+ GossipMessage *message;
+ GossipContact *contact;
+
+ message = GOSSIP_MESSAGE (gossip_event_get_data (event));
+ contact = gossip_message_get_sender (message);
+
+ gossip_chat_manager_show_chat (GOSSIP_CHAT_MANAGER (object), contact);
+}
+
+static void
+chat_manager_get_chats_foreach (GossipContact *contact,
+ GossipPrivateChat *chat,
+ GList **chats)
+{
+ const gchar *contact_id;
+
+ contact_id = gossip_contact_get_id (contact);
+ *chats = g_list_prepend (*chats, g_strdup (contact_id));
+}
+
+GossipChatManager *
+gossip_chat_manager_new (void)
+{
+ return g_object_new (GOSSIP_TYPE_CHAT_MANAGER, NULL);
+}
+
+static void
+chat_manager_chat_removed_cb (GossipChatManager *manager,
+ GossipChat *chat,
+ gboolean is_last_ref)
+{
+ GossipChatManagerPriv *priv;
+ GossipContact *contact;
+
+ if (!is_last_ref) {
+ return;
+ }
+
+ priv = GET_PRIV (manager);
+
+ contact = gossip_chat_get_contact (chat);
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Removing an old chat:'%s'",
+ gossip_contact_get_id (contact));
+
+ g_hash_table_remove (priv->chats, contact);
+}
+
+GossipPrivateChat *
+gossip_chat_manager_get_chat (GossipChatManager *manager,
+ GossipContact *contact)
+{
+ GossipChatManagerPriv *priv;
+ GossipPrivateChat *chat;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_MANAGER (manager), NULL);
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ priv = GET_PRIV (manager);
+
+ chat = g_hash_table_lookup (priv->chats, contact);
+
+ if (!chat) {
+ GossipSession *session;
+ GossipAccount *account;
+ GossipContact *own_contact;
+
+ session = gossip_app_get_session ();
+ account = gossip_contact_get_account (contact);
+ own_contact = gossip_session_get_own_contact (session, account);
+
+ chat = gossip_private_chat_new (own_contact, contact);
+ g_hash_table_insert (priv->chats,
+ g_object_ref (contact),
+ chat);
+ g_object_add_toggle_ref (G_OBJECT (chat),
+ (GToggleNotify) chat_manager_chat_removed_cb,
+ manager);
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Creating a new chat:'%s'",
+ gossip_contact_get_id (contact));
+ }
+
+ return chat;
+}
+
+GList *
+gossip_chat_manager_get_chats (GossipChatManager *manager)
+{
+ GossipChatManagerPriv *priv;
+ GList *chats = NULL;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_MANAGER (manager), NULL);
+
+ priv = GET_PRIV (manager);
+
+ g_hash_table_foreach (priv->chats,
+ (GHFunc) chat_manager_get_chats_foreach,
+ &chats);
+
+ chats = g_list_sort (chats, (GCompareFunc) strcmp);
+
+ return chats;
+}
+
+void
+gossip_chat_manager_remove_events (GossipChatManager *manager,
+ GossipContact *contact)
+{
+ GossipChatManagerPriv *priv;
+ GossipEvent *event;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_MANAGER (manager));
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Removing events for contact:'%s'",
+ gossip_contact_get_id (contact));
+
+ priv = GET_PRIV (manager);
+
+ event = g_hash_table_lookup (priv->events, contact);
+ if (event) {
+ gossip_event_manager_remove (gossip_app_get_event_manager (),
+ event, G_OBJECT (manager));
+ g_hash_table_remove (priv->events, contact);
+ }
+}
+
+void
+gossip_chat_manager_show_chat (GossipChatManager *manager,
+ GossipContact *contact)
+{
+ GossipPrivateChat *chat;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_MANAGER (manager));
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+ chat = gossip_chat_manager_get_chat (manager, contact);
+
+ gossip_chat_present (GOSSIP_CHAT (chat));
+
+ gossip_chat_manager_remove_events(manager, contact);
+}
diff --git a/libempathy-gtk/gossip-chat-manager.h b/libempathy-gtk/gossip-chat-manager.h
new file mode 100644
index 000000000..7500b594a
--- /dev/null
+++ b/libempathy-gtk/gossip-chat-manager.h
@@ -0,0 +1,65 @@
+/* -*- 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>
+ */
+
+#ifndef __GOSSIP_CHAT_MANAGER_H__
+#define __GOSSIP_CHAT_MANAGER_H__
+
+#include <glib-object.h>
+
+#include <libempathy/gossip-contact.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CHAT_MANAGER (gossip_chat_manager_get_type ())
+#define GOSSIP_CHAT_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CHAT_MANAGER, GossipChatManager))
+#define GOSSIP_CHAT_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_CHAT_MANAGER, GossipChatManagerClass))
+#define GOSSIP_IS_CHAT_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CHAT_MANAGER))
+#define GOSSIP_IS_CHAT_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CHAT_MANAGER))
+#define GOSSIP_CHAT_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CHAT_MANAGER, GossipChatManagerClass))
+
+typedef struct _GossipChatManager GossipChatManager;
+typedef struct _GossipChatManagerClass GossipChatManagerClass;
+
+#include "gossip-private-chat.h"
+
+struct _GossipChatManager {
+ GObject parent;
+};
+
+struct _GossipChatManagerClass {
+ GObjectClass parent_class;
+};
+
+GType gossip_chat_manager_get_type (void) G_GNUC_CONST;
+GossipChatManager *gossip_chat_manager_new (void);
+GossipPrivateChat *gossip_chat_manager_get_chat (GossipChatManager *manager,
+ GossipContact *contact);
+GList * gossip_chat_manager_get_chats (GossipChatManager *manager);
+void gossip_chat_manager_remove_events (GossipChatManager *manager,
+ GossipContact *contact);
+void gossip_chat_manager_show_chat (GossipChatManager *manager,
+ GossipContact *contact);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CHAT_MANAGER_H__ */
+
diff --git a/libempathy-gtk/gossip-chat-manager.loT b/libempathy-gtk/gossip-chat-manager.loT
new file mode 100644
index 000000000..3d0d3ab0d
--- /dev/null
+++ b/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/libempathy-gtk/gossip-chat-view.c b/libempathy-gtk/gossip-chat-view.c
new file mode 100644
index 000000000..cc6482ee8
--- /dev/null
+++ b/libempathy-gtk/gossip-chat-view.c
@@ -0,0 +1,2151 @@
+/* -*- 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);
+
+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 *avatar;
+
+ priv = GET_PRIV (view);
+
+ sender = gossip_message_get_sender (msg);
+ my_contact = gossip_get_own_contact_from_contact (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);
+
+ avatar = gossip_pixbuf_avatar_from_contact_scaled (sender, 32, 32);
+
+ 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_get_own_contact_from_contact (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_get_own_contact_from_contact (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_get_own_contact_from_contact (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_get_own_contact_from_contact (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);
+}
+
+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;
+ 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);
+ }
+ }
+
+ priv->last_block_type = BLOCK_TYPE_SELF;
+
+ /* Reset the last inserted contact, since it was from self. */
+ if (priv->last_contact) {
+ g_object_unref (priv->last_contact);
+ priv->last_contact = NULL;
+ }
+
+ 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/libempathy-gtk/gossip-chat-view.h b/libempathy-gtk/gossip-chat-view.h
new file mode 100644
index 000000000..2a7b11472
--- /dev/null
+++ b/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/libempathy-gtk/gossip-chat-window.c b/libempathy-gtk/gossip-chat-window.c
new file mode 100644
index 000000000..da305e60b
--- /dev/null
+++ b/libempathy-gtk/gossip-chat-window.c
@@ -0,0 +1,1894 @@
+/* -*- 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>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+
+#include <libempathy/gossip-contact.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-message.h>
+#include <libempathy/gossip-conf.h>
+
+#include "gossip-chat-window.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-sound.h"
+#include "gossip-stock.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 GdkPixbuf *chat_window_get_status_pixbuf (GossipChatWindow *window,
+ GossipChat *chat);
+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,
+ 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", GTK_TARGET_SAME_APP, 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 (gtk_tooltips_new ());
+ gtk_object_sink (GTK_OBJECT (priv->tooltips));
+
+ glade = gossip_glade_get_file ("empathy-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_stock (GTK_IMAGE (image),
+ GOSSIP_STOCK_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 GdkPixbuf *
+chat_window_get_status_pixbuf (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ GdkPixbuf *pixbuf;
+
+ priv = GET_PRIV (window);
+
+ if (g_list_find (priv->chats_new_msg, chat)) {
+ pixbuf = gossip_stock_render (GOSSIP_STOCK_MESSAGE,
+ GTK_ICON_SIZE_MENU);
+ }
+ else if (g_list_find (priv->chats_composing, chat)) {
+ pixbuf = gossip_stock_render (GOSSIP_STOCK_TYPING,
+ GTK_ICON_SIZE_MENU);
+ }
+ else {
+ pixbuf = gossip_chat_get_status_pixbuf (chat);
+ }
+
+ return pixbuf;
+}
+
+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)
+{
+ GtkImage *image;
+ GdkPixbuf *pixbuf;
+
+ pixbuf = chat_window_get_status_pixbuf (window, chat);
+ image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
+ gtk_image_set_from_pixbuf (image, pixbuf);
+
+ g_object_unref (pixbuf);
+
+ chat_window_update_title (window, chat);
+ chat_window_update_tooltip (window, chat);
+}
+
+static void
+chat_window_update_title (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ GdkPixbuf *pixbuf = NULL;
+ 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) {
+ pixbuf = gossip_stock_render (GOSSIP_STOCK_MESSAGE,
+ GTK_ICON_SIZE_MENU);
+ } else {
+ pixbuf = NULL;
+ }
+
+ gtk_window_set_icon (GTK_WINDOW (priv->dialog), pixbuf);
+
+ if (pixbuf) {
+ g_object_unref (pixbuf);
+ }
+}
+
+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)) {
+#if 0
+FIXME:
+ GossipGroupChat *group_chat;
+ GossipChatroomManager *manager;
+ GossipChatroom *chatroom;
+ GossipChatroomId id;
+ gboolean saved;
+
+ group_chat = GOSSIP_GROUP_CHAT (priv->current_chat);
+ chatroom = gossip_group_chat_get_chatroom (group_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_app_get_chatroom_manager ();
+ id = gossip_chatroom_get_id (chatroom);
+ saved = gossip_chatroom_manager_find (manager, id) != NULL;
+
+ gtk_widget_set_sensitive (priv->menu_room_add, !saved);
+ 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_chat_get_show_contacts (priv->current_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);
+#endif
+ } else {
+ GossipSubscription subscription;
+ GossipContact *contact;
+
+ /* Show / Hide widgets */
+ gtk_widget_hide (priv->menu_room);
+
+ contact = gossip_chat_get_contact (priv->current_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);
+
+ contact = gossip_chat_get_contact (priv->current_chat);
+
+/*FIXME: 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_chat_set_show_contacts (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)
+{
+/*FIXME
+ 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);
+
+ // FIXME: 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)
+{
+/* FIXME:
+ GossipChatWindowPriv *priv;
+ GossipGroupChat *group_chat;
+ 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;
+ }
+
+ group_chat = GOSSIP_GROUP_CHAT (priv->current_chat);
+ chatroom = gossip_group_chat_get_chatroom (group_chat);
+ gossip_chatroom_set_favourite (chatroom, TRUE);
+
+ manager = gossip_app_get_chatroom_manager ();
+ gossip_chatroom_manager_add (manager, chatroom);
+ gossip_chatroom_manager_store (manager);
+
+ chat_window_update_menu (window);
+*/
+}
+
+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,
+ 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 (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 (!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)
+{
+ if (info == DND_DRAG_TYPE_CONTACT_ID) {
+#if 0
+FIXME:
+ GossipChatManager *manager;
+ GossipContact *contact;
+ GossipChat *chat;
+ GossipChatWindow *old_window;
+ const gchar *id = NULL;
+
+ if (selection) {
+ id = (const gchar*) selection->data;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "DND contact from roster with id:'%s'", id);
+
+ contact = gossip_session_find_contact (gossip_app_get_session (), id);
+ if (!contact) {
+ gossip_debug (DEBUG_DOMAIN, "DND contact from roster not found");
+ return;
+ }
+
+ manager = gossip_app_get_chat_manager ();
+ chat = GOSSIP_CHAT (gossip_chat_manager_get_chat (manager, contact));
+ 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_manager_show_chat (manager, contact);
+
+ /* 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);
+#endif
+ }
+ 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;
+}
diff --git a/libempathy-gtk/gossip-chat-window.h b/libempathy-gtk/gossip-chat-window.h
new file mode 100644
index 000000000..8cf582d8a
--- /dev/null
+++ b/libempathy-gtk/gossip-chat-window.h
@@ -0,0 +1,74 @@
+/* -*- 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>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ */
+
+#ifndef __GOSSIP_CHAT_WINDOW_H__
+#define __GOSSIP_CHAT_WINDOW_H__
+
+#include <glib-object.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);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CHAT_WINDOW_H__ */
diff --git a/libempathy-gtk/gossip-chat.c b/libempathy-gtk/gossip-chat.c
new file mode 100644
index 000000000..616b3abe3
--- /dev/null
+++ b/libempathy-gtk/gossip-chat.c
@@ -0,0 +1,1295 @@
+/* -*- 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>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ */
+
+#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-session.h>
+#include <libempathy/empathy-contact-manager.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-utils.h>
+#include <libempathy/gossip-conf.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 {
+ EmpathyTpChat *tp_chat;
+ GossipChatWindow *window;
+
+ GtkTooltips *tooltips;
+ guint composing_stop_timeout_id;
+ gboolean sensitive;
+ /* 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);
+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);
+
+enum {
+ COMPOSING,
+ NEW_MESSAGE,
+ NAME_CHANGED,
+ STATUS_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint chat_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;
+
+ chat_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);
+
+ chat_signals[NEW_MESSAGE] =
+ g_signal_new ("new-message",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_MESSAGE);
+
+ chat_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);
+
+ chat_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->tooltips = gtk_tooltips_new ();
+
+ priv->default_window_height = -1;
+ priv->vscroll_visible = FALSE;
+ priv->sensitive = TRUE;
+
+ 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 (GOSSIP_CHAT (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);
+}
+
+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);
+
+ chat_composing_remove_timeout (chat);
+ g_object_unref (GOSSIP_CHAT (object)->account);
+
+ if (priv->tp_chat) {
+ g_object_unref (priv->tp_chat);
+ }
+
+ 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 (msg == NULL || msg[0] == '\0') {
+ return;
+ }
+
+ if (g_str_has_prefix (msg, "/clear")) {
+ gossip_chat_view_clear (chat->view);
+ return;
+ }
+
+ /* FIXME: gossip_app_set_not_away ();*/
+
+ own_contact = gossip_chat_get_own_contact (chat);
+ 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_by_name (chat, "new-message", message);
+}
+
+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 enter but not ctrl/shift-enter */
+ if (IS_ENTER (event->keyval) && !(event->state & GDK_SHIFT_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)) {
+ /* 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 {
+ /* FIXME:
+ gossip_session_send_composing (gossip_app_get_session (),
+ priv->contact, TRUE);
+ */
+ }
+
+ 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);
+ /* FIXME:
+ gossip_session_send_composing (gossip_app_get_session (),
+ priv->contact, FALSE);*/
+}
+
+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;
+ /* FIXME:
+ gossip_session_send_composing (gossip_app_get_session (),
+ priv->contact, FALSE);*/
+
+ 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;
+}
+
+GdkPixbuf *
+gossip_chat_get_status_pixbuf (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_status_pixbuf) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_status_pixbuf (chat);
+ }
+
+ return NULL;
+}
+
+GossipContact *
+gossip_chat_get_contact (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_contact) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_contact (chat);
+ }
+
+ return NULL;
+}
+GossipContact *
+gossip_chat_get_own_contact (GossipChat *chat)
+{
+ EmpathyContactManager *manager;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ manager = empathy_session_get_contact_manager ();
+
+ return empathy_contact_manager_get_own (manager, chat->account);
+}
+
+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);
+}
+
+gboolean
+gossip_chat_get_show_contacts (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_show_contacts) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_show_contacts (chat);
+ }
+
+ return FALSE;
+}
+
+void
+gossip_chat_set_show_contacts (GossipChat *chat,
+ gboolean show)
+{
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->set_show_contacts) {
+ GOSSIP_CHAT_GET_CLASS (chat)->set_show_contacts (chat, show);
+ }
+}
+
+void
+gossip_chat_save_geometry (GossipChat *chat,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ //FIXME: gossip_geometry_save_for_chat (chat, x, y, w, h);
+}
+
+void
+gossip_chat_load_geometry (GossipChat *chat,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h)
+{
+ //FIXME: gossip_geometry_load_for_chat (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);
+ }
+
+ priv->tp_chat = g_object_ref (tp_chat);
+
+ g_signal_connect (tp_chat, "message-received",
+ G_CALLBACK (chat_message_received_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;
+ }
+}
+
+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 (GOSSIP_CHAT (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_get_own_contact_from_contact (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/libempathy-gtk/gossip-chat.h b/libempathy-gtk/gossip-chat.h
new file mode 100644
index 000000000..9a0a0c317
--- /dev/null
+++ b/libempathy-gtk/gossip-chat.h
@@ -0,0 +1,139 @@
+/* -*- 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>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ */
+
+#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);
+ GdkPixbuf * (*get_status_pixbuf)(GossipChat *chat);
+ GossipContact * (*get_contact) (GossipChat *chat);
+ GtkWidget * (*get_widget) (GossipChat *chat);
+
+ gboolean (*get_show_contacts)(GossipChat *chat);
+ void (*set_show_contacts)(GossipChat *chat,
+ gboolean show);
+
+ gboolean (*is_group_chat) (GossipChat *chat);
+ void (*save_geometry) (GossipChat *chat,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+ void (*load_geometry) (GossipChat *chat,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h);
+};
+
+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);
+GdkPixbuf * gossip_chat_get_status_pixbuf (GossipChat *chat);
+GossipContact * gossip_chat_get_contact (GossipChat *chat);
+GossipContact * gossip_chat_get_own_contact (GossipChat *chat);
+GtkWidget * gossip_chat_get_widget (GossipChat *chat);
+gboolean gossip_chat_get_show_contacts (GossipChat *chat);
+void gossip_chat_set_show_contacts (GossipChat *chat,
+ gboolean show);
+
+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);
+
+/* 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/libempathy-gtk/gossip-contact-groups.c b/libempathy-gtk/gossip-contact-groups.c
new file mode 100644
index 000000000..8a6afda11
--- /dev/null
+++ b/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/libempathy-gtk/gossip-contact-groups.dtd b/libempathy-gtk/gossip-contact-groups.dtd
new file mode 100644
index 000000000..689220f0e
--- /dev/null
+++ b/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/libempathy-gtk/gossip-contact-groups.h b/libempathy-gtk/gossip-contact-groups.h
new file mode 100644
index 000000000..88bbdc0cb
--- /dev/null
+++ b/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/libempathy-gtk/gossip-contact-list.c b/libempathy-gtk/gossip-contact-list.c
new file mode 100644
index 000000000..6e1573d8f
--- /dev/null
+++ b/libempathy-gtk/gossip-contact-list.c
@@ -0,0 +1,2419 @@
+/* -*- 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: Mikael Hallendal <micke@imendio.com>
+ * Martyn Russell <martyn@imendio.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 <libempathy/empathy-contact-manager.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/empathy-session.h>
+
+#include "gossip-contact-list.h"
+#include "gossip-contact-groups.h"
+#include "gossip-cell-renderer-expander.h"
+#include "gossip-cell-renderer-text.h"
+#include "gossip-stock.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 "ContactListUI"
+
+/* 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).
+ */
+
+/* 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, GossipContactListPriv))
+
+struct _GossipContactListPriv {
+ EmpathyContactManager *manager;
+
+ GHashTable *groups;
+
+ GtkUIManager *ui;
+ GtkTreeRowReference *drag_row;
+
+ gboolean show_offline;
+ gboolean show_avatars;
+ gboolean is_compact;
+ gboolean show_active;
+};
+
+typedef struct {
+ const gchar *name;
+ gboolean found;
+ GtkTreeIter iter;
+} FindGroup;
+
+typedef struct {
+ GossipContact *contact;
+ gboolean found;
+ GList *iters;
+} FindContact;
+
+typedef struct {
+ GossipContactList *list;
+ GtkTreePath *path;
+ guint timeout_id;
+} DragMotionData;
+
+typedef struct {
+ GossipContactList *list;
+ GossipContact *contact;
+ gboolean remove;
+} ShowActiveData;
+
+static void gossip_contact_list_class_init (GossipContactListClass *klass);
+static void gossip_contact_list_init (GossipContactList *list);
+static void contact_list_finalize (GObject *object);
+static void contact_list_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void contact_list_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void contact_list_contact_update (GossipContactList *list,
+ GossipContact *contact);
+static void contact_list_contact_added_cb (EmpathyContactManager *manager,
+ GossipContact *contact,
+ GossipContactList *list);
+static void contact_list_contact_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipContactList *list);
+static void contact_list_contact_groups_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipContactList *list);
+static void contact_list_contact_removed_cb (EmpathyContactManager *manager,
+ GossipContact *contact,
+ GossipContactList *list);
+static void contact_list_contact_set_active (GossipContactList *list,
+ GossipContact *contact,
+ gboolean active,
+ gboolean set_changed);
+static ShowActiveData *
+ contact_list_contact_active_new (GossipContactList *list,
+ GossipContact *contact,
+ gboolean remove);
+static void contact_list_contact_active_free (ShowActiveData *data);
+static gboolean contact_list_contact_active_cb (ShowActiveData *data);
+static gchar * contact_list_get_parent_group (GtkTreeModel *model,
+ GtkTreePath *path,
+ gboolean *path_is_group);
+static void contact_list_get_group (GossipContactList *list,
+ const gchar *name,
+ GtkTreeIter *iter_to_set,
+ gboolean *created);
+static gboolean contact_list_get_group_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ FindGroup *fg);
+static void contact_list_add_contact (GossipContactList *list,
+ GossipContact *contact);
+static void contact_list_remove_contact (GossipContactList *list,
+ GossipContact *contact);
+static void contact_list_create_model (GossipContactList *list);
+static gboolean contact_list_search_equal_func (GtkTreeModel *model,
+ gint column,
+ const gchar *key,
+ GtkTreeIter *iter,
+ gpointer search_data);
+static void contact_list_setup_view (GossipContactList *list);
+static void contact_list_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ gpointer user_data);
+static gboolean contact_list_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time,
+ gpointer data);
+static gboolean contact_list_drag_motion_cb (DragMotionData *data);
+static void contact_list_drag_begin (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data);
+static void contact_list_drag_data_get (GtkWidget *widget,
+ GdkDragContext *contact,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ gpointer user_data);
+static void contact_list_drag_end (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data);
+static void contact_list_cell_set_background (GossipContactList *list,
+ GtkCellRenderer *cell,
+ gboolean is_group,
+ gboolean is_active);
+static void contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list);
+static void contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list);
+static void contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list);
+static void contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list);
+static GtkWidget *contact_list_get_contact_menu (GossipContactList *list,
+ gboolean can_send_file,
+ gboolean can_show_log);
+static gboolean contact_list_button_press_event_cb (GossipContactList *list,
+ GdkEventButton *event,
+ gpointer user_data);
+static void contact_list_row_activated_cb (GossipContactList *list,
+ GtkTreePath *path,
+ GtkTreeViewColumn *col,
+ gpointer user_data);
+static void contact_list_row_expand_or_collapse_cb (GossipContactList *list,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gpointer user_data);
+static gint contact_list_sort_func (GtkTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b,
+ gpointer user_data);
+static GList * contact_list_find_contact (GossipContactList *list,
+ GossipContact *contact);
+static gboolean contact_list_find_contact_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ FindContact *fc);
+static void contact_list_action_cb (GtkAction *action,
+ GossipContactList *list);
+static gboolean contact_list_update_list_mode_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GossipContactList *list);
+enum {
+ CONTACT_CHAT,
+ CONTACT_INFORMATION,
+ CONTACT_EDIT,
+ CONTACT_REMOVE,
+ CONTACT_INVITE,
+ CONTACT_SEND_FILE,
+ CONTACT_LOG,
+ GROUP_RENAME,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+enum {
+ COL_PIXBUF_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_COUNT
+};
+
+enum {
+ PROP_0,
+ PROP_SHOW_OFFLINE,
+ PROP_SHOW_AVATARS,
+ PROP_IS_COMPACT,
+};
+
+static const GtkActionEntry entries[] = {
+ { "ContactMenu", NULL,
+ N_("_Contact"), NULL, NULL,
+ NULL
+ },
+ { "GroupMenu", NULL,
+ N_("_Group"),NULL, NULL,
+ NULL
+ },
+ { "Chat", GOSSIP_STOCK_MESSAGE,
+ N_("_Chat"), NULL, N_("Chat with contact"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "Information", GOSSIP_STOCK_CONTACT_INFORMATION,
+ N_("Infor_mation"), "<control>I", N_("View contact information"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "Rename", NULL,
+ N_("Re_name"), NULL, N_("Rename"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "Edit", GTK_STOCK_EDIT,
+ N_("_Edit"), NULL, N_("Edit the groups and name for this contact"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "Remove", GTK_STOCK_REMOVE,
+ N_("_Remove"), NULL, N_("Remove contact"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "Invite", GOSSIP_STOCK_GROUP_MESSAGE,
+ N_("_Invite to Chat Room"), NULL, N_("Invite to a currently open chat room"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "SendFile", NULL,
+ N_("_Send File..."), NULL, N_("Send a file"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "Log", GTK_STOCK_JUSTIFY_LEFT,
+ N_("_View Previous Conversations"), NULL, N_("View previous conversations with this contact"),
+ G_CALLBACK (contact_list_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)];
+
+G_DEFINE_TYPE (GossipContactList, gossip_contact_list, GTK_TYPE_TREE_VIEW);
+
+static void
+gossip_contact_list_class_init (GossipContactListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = contact_list_finalize;
+ object_class->get_property = contact_list_get_property;
+ object_class->set_property = contact_list_set_property;
+
+ signals[CONTACT_CHAT] =
+ g_signal_new ("contact-chat",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[CONTACT_INFORMATION] =
+ g_signal_new ("contact-information",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[CONTACT_EDIT] =
+ g_signal_new ("contact-edit",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[CONTACT_REMOVE] =
+ g_signal_new ("contact-remove",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[CONTACT_INVITE] =
+ g_signal_new ("contact-invite",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[CONTACT_SEND_FILE] =
+ g_signal_new ("contact-send-file",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[CONTACT_LOG] =
+ g_signal_new ("contact-log",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[GROUP_RENAME] =
+ g_signal_new ("group-rename",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE,
+ 1, G_TYPE_STRING);
+
+
+ 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_type_class_add_private (object_class, sizeof (GossipContactListPriv));
+}
+
+static void
+gossip_contact_list_init (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+ GtkActionGroup *action_group;
+ GList *contacts, *l;
+ GError *error = NULL;
+
+ priv = GET_PRIV (list);
+
+ priv->manager = empathy_session_get_contact_manager ();
+ g_object_ref (priv->manager);
+ priv->is_compact = FALSE;
+ priv->show_active = TRUE;
+ priv->show_avatars = TRUE;
+
+ contact_list_create_model (list);
+ contact_list_setup_view (list);
+ empathy_contact_manager_setup (priv->manager);
+
+ /* 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, list);
+ 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);
+
+ /* Signal connection. */
+ g_signal_connect (priv->manager,
+ "contact-added",
+ G_CALLBACK (contact_list_contact_added_cb),
+ list);
+ g_signal_connect (priv->manager,
+ "contact-removed",
+ G_CALLBACK (contact_list_contact_removed_cb),
+ list);
+
+ /* Connect to tree view signals rather than override. */
+ g_signal_connect (list,
+ "button-press-event",
+ G_CALLBACK (contact_list_button_press_event_cb),
+ NULL);
+ g_signal_connect (list,
+ "row-activated",
+ G_CALLBACK (contact_list_row_activated_cb),
+ NULL);
+ g_signal_connect (list,
+ "row-expanded",
+ G_CALLBACK (contact_list_row_expand_or_collapse_cb),
+ GINT_TO_POINTER (TRUE));
+ g_signal_connect (list,
+ "row-collapsed",
+ G_CALLBACK (contact_list_row_expand_or_collapse_cb),
+ GINT_TO_POINTER (FALSE));
+
+ /* Add contacts already created */
+ contacts = empathy_contact_manager_get_contacts (priv->manager);
+ for (l = contacts; l; l = l->next) {
+ GossipContact *contact;
+
+ contact = l->data;
+
+ contact_list_contact_added_cb (priv->manager, contact, list);
+
+ g_object_unref (contact);
+ }
+}
+
+static void
+contact_list_finalize (GObject *object)
+{
+ GossipContactListPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ /* FIXME: disconnect all signals on the manager and contacts */
+
+ g_object_unref (priv->manager);
+ g_object_unref (priv->ui);
+
+ G_OBJECT_CLASS (gossip_contact_list_parent_class)->finalize (object);
+}
+
+static void
+contact_list_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GossipContactListPriv *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;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+contact_list_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GossipContactListPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_SHOW_OFFLINE:
+ gossip_contact_list_set_show_offline (GOSSIP_CONTACT_LIST (object),
+ g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_AVATARS:
+ gossip_contact_list_set_show_avatars (GOSSIP_CONTACT_LIST (object),
+ g_value_get_boolean (value));
+ break;
+ case PROP_IS_COMPACT:
+ gossip_contact_list_set_is_compact (GOSSIP_CONTACT_LIST (object),
+ g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+contact_list_contact_update (GossipContactList *list,
+ GossipContact *contact)
+{
+ GossipContactListPriv *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_presence;
+ GdkPixbuf *pixbuf_avatar;
+
+ priv = GET_PRIV (list);
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ iters = contact_list_find_contact (list, 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_remove_contact (list, 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_add_contact (list, 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) {
+ GtkTreeIter *iter;
+
+ iter = g_list_nth_data (iters, 0);
+ gtk_tree_model_get (model, iter, COL_IS_ONLINE, &was_online, -1);
+ }
+
+ /* Is this really an update or an online/offline. */
+ if (priv->show_active) {
+ if (was_online != now_online) {
+ gchar *str;
+
+ do_set_active = TRUE;
+ do_set_refresh = TRUE;
+
+ if (was_online) {
+ str = "online -> offline";
+ } else {
+ str = "offline -> online";
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Set active (contact updated %s)", str);
+ } 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_presence = gossip_pixbuf_for_contact (contact);
+ 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 (model), l->data,
+ COL_PIXBUF_STATUS, pixbuf_presence,
+ COL_STATUS, gossip_contact_get_status (contact),
+ COL_IS_ONLINE, now_online,
+ COL_NAME, gossip_contact_get_name (contact),
+ COL_PIXBUF_AVATAR, pixbuf_avatar,
+ -1);
+ }
+
+ if (pixbuf_presence) {
+ g_object_unref (pixbuf_presence);
+ }
+ if (pixbuf_avatar) {
+ g_object_unref (pixbuf_avatar);
+ }
+
+ if (priv->show_active && do_set_active) {
+ contact_list_contact_set_active (list, contact, do_set_active, do_set_refresh);
+
+ if (do_set_active) {
+ data = contact_list_contact_active_new (list, contact, do_remove);
+ g_timeout_add (ACTIVE_USER_SHOW_TIME,
+ (GSourceFunc) contact_list_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_contact_added_cb (EmpathyContactManager *manager,
+ GossipContact *contact,
+ GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+
+ priv = GET_PRIV (list);
+
+ gossip_debug (DEBUG_DOMAIN, "Contact:'%s' added",
+ gossip_contact_get_name (contact));
+
+ /* Connect notifications for contact updates */
+ g_signal_connect (contact, "notify::groups",
+ G_CALLBACK (contact_list_contact_groups_updated_cb),
+ list);
+ g_signal_connect (contact, "notify::presences",
+ G_CALLBACK (contact_list_contact_updated_cb),
+ list);
+ g_signal_connect (contact, "notify::name",
+ G_CALLBACK (contact_list_contact_updated_cb),
+ list);
+ g_signal_connect (contact, "notify::avatar",
+ G_CALLBACK (contact_list_contact_updated_cb),
+ list);
+ g_signal_connect (contact, "notify::type",
+ G_CALLBACK (contact_list_contact_updated_cb),
+ list);
+
+ contact_list_add_contact (list, contact);
+}
+
+static void
+contact_list_contact_groups_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+
+ priv = GET_PRIV (list);
+
+ if (priv->show_offline || gossip_contact_is_online (contact)) {
+ }
+
+
+ gossip_debug (DEBUG_DOMAIN, "Contact:'%s' groups updated",
+ 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_remove_contact (list, contact);
+ contact_list_add_contact (list, contact);
+}
+
+static void
+contact_list_contact_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipContactList *list)
+{
+ contact_list_contact_update (list, contact);
+}
+
+static void
+contact_list_contact_removed_cb (EmpathyContactManager *manager,
+ GossipContact *contact,
+ GossipContactList *list)
+{
+ 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_contact_groups_updated_cb),
+ list);
+ g_signal_handlers_disconnect_by_func (contact,
+ G_CALLBACK (contact_list_contact_updated_cb),
+ list);
+
+ contact_list_remove_contact (list, contact);
+}
+
+static void
+contact_list_contact_set_active (GossipContactList *list,
+ GossipContact *contact,
+ gboolean active,
+ gboolean set_changed)
+{
+ GtkTreeModel *model;
+ GList *iters, *l;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ iters = contact_list_find_contact (list, contact);
+ for (l = iters; l; l = l->next) {
+ GtkTreePath *path;
+ GtkTreeIter *iter;
+
+ iter = l->data;
+
+ gtk_tree_store_set (GTK_TREE_STORE (model), iter,
+ 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, iter);
+ gtk_tree_model_row_changed (model, path, iter);
+ gtk_tree_path_free (path);
+ }
+ }
+
+ g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
+ g_list_free (iters);
+}
+
+static ShowActiveData *
+contact_list_contact_active_new (GossipContactList *list,
+ GossipContact *contact,
+ gboolean remove)
+{
+ ShowActiveData *data;
+
+ g_return_val_if_fail (list != NULL, NULL);
+ g_return_val_if_fail (contact != NULL, NULL);
+
+ 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->list = g_object_ref (list);
+ data->contact = g_object_ref (contact);
+
+ data->remove = remove;
+
+ return data;
+}
+
+static void
+contact_list_contact_active_free (ShowActiveData *data)
+{
+ g_return_if_fail (data != NULL);
+
+ g_object_unref (data->contact);
+ g_object_unref (data->list);
+
+ g_slice_free (ShowActiveData, data);
+}
+
+static gboolean
+contact_list_contact_active_cb (ShowActiveData *data)
+{
+ GossipContactListPriv *priv;
+
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ priv = GET_PRIV (data->list);
+
+ 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_remove_contact (data->list,
+ data->contact);
+ }
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' no longer active",
+ gossip_contact_get_name (data->contact));
+ contact_list_contact_set_active (data->list,
+ data->contact,
+ FALSE,
+ TRUE);
+
+ contact_list_contact_active_free (data);
+
+ return FALSE;
+}
+
+static gchar *
+contact_list_get_parent_group (GtkTreeModel *model,
+ GtkTreePath *path,
+ gboolean *path_is_group)
+{
+ GtkTreeIter parent_iter, iter;
+ gchar *name;
+ gboolean is_group;
+
+ g_return_val_if_fail (model != NULL, NULL);
+ g_return_val_if_fail (path != NULL, NULL);
+ g_return_val_if_fail (path_is_group != NULL, NULL);
+
+ if (!gtk_tree_model_get_iter (model, &iter, path)) {
+ return NULL;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ COL_IS_GROUP, &is_group,
+ -1);
+
+ if (!is_group) {
+ 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,
+ -1);
+
+ if (!is_group) {
+ return NULL;
+ }
+
+ *path_is_group = TRUE;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ COL_NAME, &name,
+ -1);
+
+ return name;
+}
+
+static void
+contact_list_get_group (GossipContactList *list,
+ const gchar *name,
+ GtkTreeIter *iter_to_set,
+ gboolean *created)
+{
+ GtkTreeModel *model;
+ FindGroup fg;
+
+ memset (&fg, 0, sizeof (fg));
+
+ fg.name = name;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc) contact_list_get_group_foreach,
+ &fg);
+
+ if (!fg.found) {
+ if (created) {
+ *created = TRUE;
+ }
+
+ gtk_tree_store_append (GTK_TREE_STORE (model), iter_to_set, NULL);
+ gtk_tree_store_set (GTK_TREE_STORE (model), iter_to_set,
+ COL_PIXBUF_STATUS, NULL,
+ COL_NAME, name,
+ COL_IS_GROUP, TRUE,
+ COL_IS_ACTIVE, FALSE,
+ -1);
+ } else {
+ if (created) {
+ *created = FALSE;
+ }
+
+ *iter_to_set = fg.iter;
+ }
+}
+
+static gboolean
+contact_list_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_add_contact (GossipContactList *list,
+ GossipContact *contact)
+{
+ GossipContactListPriv *priv;
+ GtkTreeIter iter, iter_group;
+ GtkTreeModel *model;
+ GList *l, *groups;
+
+ priv = GET_PRIV (list);
+
+ if (!priv->show_offline && !gossip_contact_is_online (contact)) {
+ return;
+ }
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ /* If no groups just add it at the top level. */
+ groups = gossip_contact_get_groups (contact);
+ if (!groups) {
+ GdkPixbuf *pixbuf_status;
+ GdkPixbuf *pixbuf_avatar;
+ gboolean show_avatar = FALSE;
+
+ pixbuf_status = gossip_pixbuf_for_contact (contact);
+ pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (
+ contact, 32, 32);
+
+ if (priv->show_avatars && !priv->is_compact) {
+ show_avatar = TRUE;
+ }
+
+ gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+ COL_PIXBUF_STATUS, pixbuf_status,
+ COL_PIXBUF_AVATAR, pixbuf_avatar,
+ COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
+ COL_NAME, gossip_contact_get_name (contact),
+ COL_STATUS, gossip_contact_get_status (contact),
+ COL_STATUS_VISIBLE, !priv->is_compact,
+ COL_CONTACT, contact,
+ COL_IS_GROUP, FALSE,
+ COL_IS_ACTIVE, FALSE,
+ COL_IS_ONLINE, gossip_contact_is_online (contact),
+ -1);
+
+ if (pixbuf_avatar) {
+ g_object_unref (pixbuf_avatar);
+ }
+ if (pixbuf_status) {
+ g_object_unref (pixbuf_status);
+ }
+ }
+
+ /* Else add to each group. */
+ for (l = groups; l; l = l->next) {
+ GtkTreePath *path;
+ GdkPixbuf *pixbuf_status;
+ GdkPixbuf *pixbuf_avatar;
+ const gchar *name;
+ gboolean created;
+ gboolean show_avatar = FALSE;
+
+ name = l->data;
+ if (!name) {
+ continue;
+ }
+
+ pixbuf_status = gossip_pixbuf_for_contact (contact);
+ pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (
+ contact, 32, 32);
+
+ contact_list_get_group (list, name, &iter_group, &created);
+
+ if (priv->show_avatars && !priv->is_compact) {
+ show_avatar = TRUE;
+ }
+
+ gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &iter_group);
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+ COL_PIXBUF_STATUS, pixbuf_status,
+ COL_PIXBUF_AVATAR, pixbuf_avatar,
+ COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
+ COL_NAME, gossip_contact_get_name (contact),
+ COL_STATUS, gossip_contact_get_status (contact),
+ COL_STATUS_VISIBLE, !priv->is_compact,
+ COL_CONTACT, contact,
+ COL_IS_GROUP, FALSE,
+ COL_IS_ACTIVE, FALSE,
+ COL_IS_ONLINE, gossip_contact_is_online (contact),
+ -1);
+
+ if (pixbuf_avatar) {
+ g_object_unref (pixbuf_avatar);
+ }
+ if (pixbuf_status) {
+ g_object_unref (pixbuf_status);
+ }
+
+ if (!created) {
+ continue;
+ }
+
+ path = gtk_tree_model_get_path (model, &iter_group);
+ if (!path) {
+ continue;
+ }
+
+ if (gossip_contact_group_get_expanded (name)) {
+ g_signal_handlers_block_by_func (list,
+ contact_list_row_expand_or_collapse_cb,
+ GINT_TO_POINTER (TRUE));
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (list), path, TRUE);
+ g_signal_handlers_unblock_by_func (list,
+ contact_list_row_expand_or_collapse_cb,
+ GINT_TO_POINTER (TRUE));
+ } else {
+ g_signal_handlers_block_by_func (list,
+ contact_list_row_expand_or_collapse_cb,
+ GINT_TO_POINTER (FALSE));
+ gtk_tree_view_collapse_row (GTK_TREE_VIEW (list), path);
+ g_signal_handlers_unblock_by_func (list,
+ contact_list_row_expand_or_collapse_cb,
+ GINT_TO_POINTER (FALSE));
+ }
+
+ gtk_tree_path_free (path);
+ }
+}
+
+static void
+contact_list_remove_contact (GossipContactList *list,
+ GossipContact *contact)
+{
+ GossipContactListPriv *priv;
+ GtkTreeModel *model;
+ GList *iters, *l;
+
+ priv = GET_PRIV (list);
+
+ iters = contact_list_find_contact (list, contact);
+ if (!iters) {
+ return;
+ }
+
+ /* Clean up model */
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ for (l = iters; l; l = l->next) {
+ GtkTreeIter parent;
+
+ if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
+ gtk_tree_model_iter_n_children (model, &parent) <= 1) {
+ gtk_tree_store_remove (GTK_TREE_STORE (model), &parent);
+ } else {
+ gtk_tree_store_remove (GTK_TREE_STORE (model), l->data);
+ }
+ }
+
+ g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
+ g_list_free (iters);
+}
+
+static void
+contact_list_create_model (GossipContactList *list)
+{
+ GtkTreeModel *model;
+
+ model = GTK_TREE_MODEL (
+ gtk_tree_store_new (COL_COUNT,
+ GDK_TYPE_PIXBUF, /* Status pixbuf */
+ 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 */
+
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
+ COL_NAME,
+ contact_list_sort_func,
+ list, NULL);
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
+ COL_NAME,
+ GTK_SORT_ASCENDING);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (list), model);
+
+ g_object_unref (model);
+}
+
+static gboolean
+contact_list_search_equal_func (GtkTreeModel *model,
+ gint column,
+ const gchar *key,
+ GtkTreeIter *iter,
+ gpointer search_data)
+{
+ gchar *name, *name_folded;
+ gchar *key_folded;
+ gboolean ret;
+
+ gtk_tree_model_get (model, iter,
+ COL_NAME, &name,
+ -1);
+
+ name_folded = g_utf8_casefold (name, -1);
+ key_folded = g_utf8_casefold (key, -1);
+
+ if (strstr (name_folded, key_folded)) {
+ ret = FALSE;
+ } else {
+ ret = TRUE;
+ }
+
+ g_free (name);
+ g_free (name_folded);
+ g_free (key_folded);
+
+ return ret;
+}
+
+static void
+contact_list_setup_view (GossipContactList *list)
+{
+ GtkCellRenderer *cell;
+ GtkTreeViewColumn *col;
+ gint i;
+
+ gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (list),
+ contact_list_search_equal_func,
+ list,
+ NULL);
+
+ g_object_set (list,
+ "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_pixbuf_cell_data_func,
+ list, 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_text_cell_data_func,
+ list, 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_avatar_cell_data_func,
+ list, 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_expander_cell_data_func,
+ list, NULL);
+
+ /* Actually add the column now we have added all cell renderers */
+ gtk_tree_view_append_column (GTK_TREE_VIEW (list), 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 (list),
+ GDK_BUTTON1_MASK,
+ drag_types_source,
+ G_N_ELEMENTS (drag_types_source),
+ GDK_ACTION_MOVE);
+
+ gtk_drag_dest_set (GTK_WIDGET (list),
+ GTK_DEST_DEFAULT_ALL,
+ drag_types_dest,
+ G_N_ELEMENTS (drag_types_dest),
+ GDK_ACTION_MOVE | GDK_ACTION_LINK);
+
+ g_signal_connect (GTK_WIDGET (list),
+ "drag-data-received",
+ G_CALLBACK (contact_list_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 (GTK_WIDGET (list),
+ "drag-motion",
+ G_CALLBACK (contact_list_drag_motion),
+ NULL);
+
+ g_signal_connect (GTK_WIDGET (list),
+ "drag-begin",
+ G_CALLBACK (contact_list_drag_begin),
+ NULL);
+ g_signal_connect (GTK_WIDGET (list),
+ "drag-data-get",
+ G_CALLBACK (contact_list_drag_data_get),
+ NULL);
+ g_signal_connect (GTK_WIDGET (list),
+ "drag-end",
+ G_CALLBACK (contact_list_drag_end),
+ NULL);
+}
+
+static void
+contact_list_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ gpointer user_data)
+{
+ GossipContactListPriv *priv;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeViewDropPosition position;
+ GossipContact *contact;
+ GList *groups;
+ const gchar *id;
+ gchar *old_group;
+ gboolean is_row;
+ gboolean drag_success = TRUE;
+ gboolean drag_del = FALSE;
+
+ 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 */
+ contact = empathy_contact_manager_find (priv->manager, id);
+ if (!contact) {
+ gossip_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
+ return;
+ }
+
+ groups = gossip_contact_get_groups (contact);
+
+ is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
+ x,
+ y,
+ &path,
+ &position);
+
+ if (!is_row) {
+ if (g_list_length (groups) != 1) {
+ /* if they have dragged a contact out of a
+ * group then we would set the contact to have
+ * NO groups but only if they were ONE group
+ * to begin with - should we do this
+ * regardless to how many groups they are in
+ * already or not at all?
+ */
+ return;
+ }
+
+ gossip_contact_set_groups (contact, NULL);
+ } else {
+ GList *l, *new_groups;
+ gchar *name;
+ gboolean is_group;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+ name = contact_list_get_parent_group (model, path, &is_group);
+
+ if (groups && name &&
+ g_list_find_custom (groups, name, (GCompareFunc)strcmp)) {
+ g_free (name);
+ return;
+ }
+
+ /* Get source group information. */
+ priv = GET_PRIV (widget);
+ if (!priv->drag_row) {
+ g_free (name);
+ return;
+ }
+
+ path = gtk_tree_row_reference_get_path (priv->drag_row);
+ if (!path) {
+ g_free (name);
+ return;
+ }
+
+ old_group = contact_list_get_parent_group (model, path, &is_group);
+ gtk_tree_path_free (path);
+
+ if (!name && old_group && GDK_ACTION_MOVE) {
+ drag_success = FALSE;
+ }
+
+ if (context->action == GDK_ACTION_MOVE) {
+ drag_del = TRUE;
+ }
+
+ /* Create new groups GList. */
+ for (l = groups, new_groups = NULL; l && drag_success; l = l->next) {
+ gchar *str;
+
+ str = l->data;
+ if (context->action == GDK_ACTION_MOVE &&
+ old_group != NULL &&
+ strcmp (str, old_group) == 0) {
+ continue;
+ }
+
+ if (str == NULL) {
+ continue;
+ }
+
+ new_groups = g_list_append (new_groups, g_strdup (str));
+ }
+
+ if (drag_success) {
+ if (name) {
+ new_groups = g_list_append (new_groups, name);
+ }
+ gossip_contact_set_groups (contact, new_groups);
+ } else {
+ g_free (name);
+ }
+ }
+
+ gtk_drag_finish (context, drag_success, drag_del, GDK_CURRENT_TIME);
+}
+
+static gboolean
+contact_list_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->list = GOSSIP_CONTACT_LIST (widget);
+ dm->path = gtk_tree_path_copy (path);
+
+ dm->timeout_id = g_timeout_add (
+ 1500,
+ (GSourceFunc) contact_list_drag_motion_cb,
+ dm);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+contact_list_drag_motion_cb (DragMotionData *data)
+{
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (data->list),
+ data->path,
+ FALSE);
+
+ data->timeout_id = 0;
+
+ return FALSE;
+}
+
+static void
+contact_list_drag_begin (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data)
+{
+ GossipContactListPriv *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_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ gpointer user_data)
+{
+ GossipContactListPriv *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_get_selected (GOSSIP_CONTACT_LIST (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_drag_end (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data)
+{
+ GossipContactListPriv *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_cell_set_background (GossipContactList *list,
+ GtkCellRenderer *cell,
+ gboolean is_group,
+ gboolean is_active)
+{
+ GdkColor color;
+ GtkStyle *style;
+ gint color_sum_normal, color_sum_selected;
+
+ g_return_if_fail (list != NULL);
+ g_return_if_fail (cell != NULL);
+
+ style = gtk_widget_get_style (GTK_WIDGET (list));
+
+ 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 {
+ color = style->base[GTK_STATE_SELECTED];
+ color_sum_normal = color.red+color.green+color.blue;
+ color = style->base[GTK_STATE_NORMAL];
+ color_sum_selected = color.red+color.green+color.blue;
+ color = style->text_aa[GTK_STATE_INSENSITIVE];
+
+ if(color_sum_normal < color_sum_selected) {
+ /* found a light theme */
+ color.red = (color.red + (style->white).red) / 2;
+ color.green = (color.green + (style->white).green) / 2;
+ color.blue = (color.blue + (style->white).blue) / 2;
+ } else {
+ /* found a dark theme */
+ color.red = (color.red + (style->black).red) / 2;
+ color.green = (color.green + (style->black).green) / 2;
+ color.blue = (color.blue + (style->black).blue) / 2;
+ }
+
+ g_object_set (cell,
+ "cell-background-gdk", &color,
+ NULL);
+ }
+}
+
+static void
+contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list)
+{
+ GdkPixbuf *pixbuf;
+ gboolean is_group;
+ gboolean is_active;
+
+ gtk_tree_model_get (model, iter,
+ COL_IS_GROUP, &is_group,
+ COL_IS_ACTIVE, &is_active,
+ COL_PIXBUF_STATUS, &pixbuf,
+ -1);
+
+ g_object_set (cell,
+ "visible", !is_group,
+ "pixbuf", pixbuf,
+ NULL);
+
+ if (pixbuf) {
+ g_object_unref (pixbuf);
+ }
+
+ contact_list_cell_set_background (list, cell, is_group, is_active);
+}
+
+static void
+contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list)
+{
+ 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_cell_set_background (list, cell, is_group, is_active);
+}
+
+static void
+contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list)
+{
+ 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_cell_set_background (list, cell, is_group, is_active);
+}
+
+static void
+contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list)
+{
+ 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_cell_set_background (list, cell, is_group, is_active);
+}
+
+static GtkWidget *
+contact_list_get_contact_menu (GossipContactList *list,
+ gboolean can_send_file,
+ gboolean can_show_log)
+{
+ GossipContactListPriv *priv;
+ GtkAction *action;
+ GtkWidget *widget;
+
+ priv = GET_PRIV (list);
+
+ /* 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;
+}
+
+GtkWidget *
+gossip_contact_list_get_group_menu (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+ GtkWidget *widget;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
+
+ priv = GET_PRIV (list);
+
+ widget = gtk_ui_manager_get_widget (priv->ui, "/Group");
+
+ return widget;
+}
+
+GtkWidget *
+gossip_contact_list_get_contact_menu (GossipContactList *list,
+ GossipContact *contact)
+{
+ GtkWidget *menu;
+ gboolean can_show_log;
+ gboolean can_send_file;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), 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;
+
+ menu = contact_list_get_contact_menu (list,
+ can_send_file,
+ can_show_log);
+ return menu;
+}
+
+static gboolean
+contact_list_button_press_event_cb (GossipContactList *list,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ GossipContactListPriv *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 (list);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ gtk_widget_grab_focus (GTK_WIDGET (list));
+
+ row_exists = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (list),
+ 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_get_contact_menu (list, contact);
+ g_object_unref (contact);
+ } else {
+ menu = gossip_contact_list_get_group_menu (list);
+ }
+
+ 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_row_activated_cb (GossipContactList *list,
+ GtkTreePath *path,
+ GtkTreeViewColumn *col,
+ gpointer user_data)
+{
+ GossipContact *contact;
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ view = GTK_TREE_VIEW (list);
+ model = gtk_tree_view_get_model (view);
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
+
+ if (contact) {
+ g_signal_emit (list, signals[CONTACT_CHAT], 0, contact);
+ g_object_unref (contact);
+ }
+}
+
+static void
+contact_list_row_expand_or_collapse_cb (GossipContactList *list,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ GtkTreeModel *model;
+ gchar *name;
+ gboolean expanded;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ 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 gint
+contact_list_sort_func (GtkTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b,
+ gpointer user_data)
+{
+ gchar *name_a, *name_b;
+ GossipContact *contact_a, *contact_b;
+ gint ret_val;
+
+ gtk_tree_model_get (model, iter_a,
+ COL_NAME, &name_a,
+ COL_CONTACT, &contact_a,
+ -1);
+ gtk_tree_model_get (model, iter_b,
+ COL_NAME, &name_b,
+ COL_CONTACT, &contact_b,
+ -1);
+
+ /* If contact is NULL it means it's a group. */
+
+ 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_iter_equal_contact (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContact *contact)
+{
+ GossipContact *c;
+ gboolean equal;
+
+ gtk_tree_model_get (model, iter,
+ COL_CONTACT, &c,
+ -1);
+
+ if (!c) {
+ return FALSE;
+ }
+
+ equal = (c == contact);
+ g_object_unref (c);
+
+ return equal;
+}
+
+static GList *
+contact_list_find_contact (GossipContactList *list,
+ GossipContact *contact)
+{
+ GtkTreeModel *model;
+ FindContact fc;
+ GList *l = NULL;
+
+ memset (&fc, 0, sizeof (fc));
+
+ fc.contact = contact;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc) contact_list_find_contact_foreach,
+ &fc);
+
+ if (fc.found) {
+ l = fc.iters;
+ }
+
+ return l;
+}
+
+static gboolean
+contact_list_find_contact_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ FindContact *fc)
+{
+ if (contact_list_iter_equal_contact (model, iter, fc->contact)) {
+ fc->found = TRUE;
+ fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
+ }
+
+ /* We want to find ALL contacts that match, this means if we
+ * have the same contact in 3 groups, all iters should be
+ * returned.
+ */
+ return FALSE;
+}
+
+static void
+contact_list_action_cb (GtkAction *action,
+ GossipContactList *list)
+{
+ 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_get_selected (list);
+ group = gossip_contact_list_get_selected_group (list);
+
+ if (contact && strcmp (name, "Chat") == 0) {
+ g_signal_emit (list, signals[CONTACT_CHAT], 0, contact);
+ }
+ else if (contact && strcmp (name, "Information") == 0) {
+ g_signal_emit (list, signals[CONTACT_INFORMATION], 0, contact);
+ }
+ else if (contact && strcmp (name, "Edit") == 0) {
+ g_signal_emit (list, signals[CONTACT_EDIT], 0, contact);
+ }
+ else if (contact && strcmp (name, "Remove") == 0) {
+ g_signal_emit (list, signals[CONTACT_REMOVE], 0, contact);
+ }
+ else if (contact && strcmp (name, "Invite") == 0) {
+ g_signal_emit (list, signals[CONTACT_INVITE], 0, contact);
+ }
+ else if (contact && strcmp (name, "SendFile") == 0) {
+ g_signal_emit (list, signals[CONTACT_SEND_FILE], 0, contact);
+ }
+ else if (contact && strcmp (name, "Log") == 0) {
+ g_signal_emit (list, signals[CONTACT_LOG], 0, contact);
+ }
+ else if (group && strcmp (name, "Rename") == 0) {
+ g_signal_emit (list, signals[GROUP_RENAME], 0, group);
+ }
+
+ g_free (group);
+ if (contact) {
+ g_object_unref (contact);
+ }
+}
+
+static gboolean
+contact_list_update_list_mode_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+ gboolean show_avatar = FALSE;
+
+ priv = GET_PRIV (list);
+
+ if (priv->show_avatars && !priv->is_compact) {
+ show_avatar = TRUE;
+ }
+
+ gtk_tree_store_set (GTK_TREE_STORE (model), iter,
+ COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
+ COL_STATUS_VISIBLE, !priv->is_compact,
+ -1);
+
+ return FALSE;
+}
+
+GossipContactList *
+gossip_contact_list_new (void)
+{
+ return g_object_new (GOSSIP_TYPE_CONTACT_LIST, NULL);
+}
+
+GossipContact *
+gossip_contact_list_get_selected (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GossipContact *contact;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
+
+ priv = GET_PRIV (list);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
+ 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_get_selected_group (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gboolean is_group;
+ gchar *name;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
+
+ priv = GET_PRIV (list);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
+ 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;
+}
+
+gboolean
+gossip_contact_list_get_show_offline (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), FALSE);
+
+ priv = GET_PRIV (list);
+
+ return priv->show_offline;
+}
+
+void
+gossip_contact_list_set_show_offline (GossipContactList *list,
+ gboolean show_offline)
+{
+ GossipContactListPriv *priv;
+ GList *contacts, *l;
+ gboolean show_active;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
+
+ priv = GET_PRIV (list);
+
+ priv->show_offline = show_offline;
+ show_active = priv->show_active;
+
+ /* Disable temporarily. */
+ priv->show_active = FALSE;
+
+ contacts = empathy_contact_manager_get_contacts (priv->manager);
+ for (l = contacts; l; l = l->next) {
+ GossipContact *contact;
+
+ contact = GOSSIP_CONTACT (l->data);
+
+ contact_list_contact_update (list, contact);
+
+ g_object_unref (contact);
+ }
+ g_list_free (contacts);
+
+ /* Restore to original setting. */
+ priv->show_active = show_active;
+}
+
+gboolean
+gossip_contact_list_get_show_avatars (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
+
+ priv = GET_PRIV (list);
+
+ return priv->show_avatars;
+}
+
+void
+gossip_contact_list_set_show_avatars (GossipContactList *list,
+ gboolean show_avatars)
+{
+ GossipContactListPriv *priv;
+ GtkTreeModel *model;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
+
+ priv = GET_PRIV (list);
+
+ priv->show_avatars = show_avatars;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc)
+ contact_list_update_list_mode_foreach,
+ list);
+}
+
+gboolean
+gossip_contact_list_get_is_compact (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
+
+ priv = GET_PRIV (list);
+
+ return priv->is_compact;
+}
+
+void
+gossip_contact_list_set_is_compact (GossipContactList *list,
+ gboolean is_compact)
+{
+ GossipContactListPriv *priv;
+ GtkTreeModel *model;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
+
+ priv = GET_PRIV (list);
+
+ priv->is_compact = is_compact;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc)
+ contact_list_update_list_mode_foreach,
+ list);
+}
+
diff --git a/libempathy-gtk/gossip-contact-list.h b/libempathy-gtk/gossip-contact-list.h
new file mode 100644
index 000000000..b5947b947
--- /dev/null
+++ b/libempathy-gtk/gossip-contact-list.h
@@ -0,0 +1,72 @@
+/* -*- 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>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_CONTACT_LIST_H__
+#define __GOSSIP_CONTACT_LIST_H__
+
+#include <gtk/gtktreeview.h>
+
+#include <libempathy/gossip-contact.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CONTACT_LIST (gossip_contact_list_get_type ())
+#define GOSSIP_CONTACT_LIST(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CONTACT_LIST, GossipContactList))
+#define GOSSIP_CONTACT_LIST_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GOSSIP_TYPE_CONTACT_LIST, GossipContactListClass))
+#define GOSSIP_IS_CONTACT_LIST(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CONTACT_LIST))
+#define GOSSIP_IS_CONTACT_LIST_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CONTACT_LIST))
+#define GOSSIP_CONTACT_LIST_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CONTACT_LIST, GossipContactListClass))
+
+typedef struct _GossipContactList GossipContactList;
+typedef struct _GossipContactListClass GossipContactListClass;
+typedef struct _GossipContactListPriv GossipContactListPriv;
+
+struct _GossipContactList {
+ GtkTreeView parent;
+};
+
+struct _GossipContactListClass {
+ GtkTreeViewClass parent_class;
+};
+
+GType gossip_contact_list_get_type (void) G_GNUC_CONST;
+GossipContactList *gossip_contact_list_new (void);
+GossipContact * gossip_contact_list_get_selected (GossipContactList *list);
+gchar * gossip_contact_list_get_selected_group (GossipContactList *list);
+gboolean gossip_contact_list_get_show_offline (GossipContactList *list);
+void gossip_contact_list_set_show_offline (GossipContactList *list,
+ gboolean show_offline);
+gboolean gossip_contact_list_get_show_avatars (GossipContactList *list);
+void gossip_contact_list_set_show_avatars (GossipContactList *list,
+ gboolean show_avatars);
+gboolean gossip_contact_list_get_is_compact (GossipContactList *list);
+void gossip_contact_list_set_is_compact (GossipContactList *list,
+ gboolean is_compact);
+GtkWidget * gossip_contact_list_get_contact_menu (GossipContactList *list,
+ GossipContact *contact);
+GtkWidget * gossip_contact_list_get_group_menu (GossipContactList *list);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CONTACT_LIST_H__ */
+
diff --git a/libempathy-gtk/gossip-preferences.c b/libempathy-gtk/gossip-preferences.c
new file mode 100644
index 000000000..2f127a515
--- /dev/null
+++ b/libempathy-gtk/gossip-preferences.c
@@ -0,0 +1,885 @@
+/* -*- 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-stock.h"
+#include "gossip-ui-utils.h"
+#include "gossip-theme-manager.h"
+#include "gossip-spell.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 *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_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_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);
+}
+
+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)) {
+ 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) {
+ gtk_entry_set_text (GTK_ENTRY (widget), value);
+ 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)
+{
+ gint value;
+
+ if (gossip_conf_get_int (conf, key, &value)) {
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (user_data), value);
+ }
+}
+
+static void
+preferences_notify_string_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ gchar *value;
+
+ if (gossip_conf_get_string (conf, key, &value) && value) {
+ gtk_entry_set_text (GTK_ENTRY (user_data), value);
+ g_free (value);
+ }
+}
+
+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);
+/*
+ if (gossip_conf_get_bool (conf, key, &value)) {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (user_data),
+ gconf_value_get_bool (value));
+ }
+*/
+}
+
+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_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_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);
+}
+
+void
+gossip_preferences_show (void)
+{
+ static GossipPreferences *preferences;
+ GladeXML *glade;
+
+ if (preferences) {
+ gtk_window_present (GTK_WINDOW (preferences->dialog));
+ return;
+ }
+
+ preferences = g_new0 (GossipPreferences, 1);
+
+ glade = gossip_glade_get_file (
+ "main.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,
+ "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);
+ }
+
+ gtk_widget_show (preferences->dialog);
+}
diff --git a/libempathy-gtk/gossip-preferences.h b/libempathy-gtk/gossip-preferences.h
new file mode 100644
index 000000000..428d0fc72
--- /dev/null
+++ b/libempathy-gtk/gossip-preferences.h
@@ -0,0 +1,55 @@
+/* -*- 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__
+
+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_HINTS_CLOSE_MAIN_WINDOW GOSSIP_PREFS_PATH "/hints/close_main_window"
+
+void gossip_preferences_show (void);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_PREFERENCES_H__ */
+
+
diff --git a/libempathy-gtk/gossip-private-chat.c b/libempathy-gtk/gossip-private-chat.c
new file mode 100644
index 000000000..41a7f5946
--- /dev/null
+++ b/libempathy-gtk/gossip-private-chat.c
@@ -0,0 +1,495 @@
+/* -*- 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>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+#include <libempathy/gossip-debug.h>
+#include <libempathy/empathy-tp-chat.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 "gossip-stock.h"
+#include "gossip-ui-utils.h"
+
+#define DEBUG_DOMAIN "PrivateChat"
+
+#define COMPOSING_STOP_TIMEOUT 5
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_PRIVATE_CHAT, GossipPrivateChatPriv))
+
+struct _GossipPrivateChatPriv {
+ GossipContact *contact;
+ gchar *name;
+
+ guint scroll_idle_id;
+ 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 GdkPixbuf * private_chat_get_status_pixbuf (GossipChat *chat);
+static GossipContact *private_chat_get_contact (GossipChat *chat);
+static GtkWidget * private_chat_get_widget (GossipChat *chat);
+/*static GdkPixbuf * private_chat_pad_to_size (GdkPixbuf *pixbuf,
+ gint width,
+ gint height,
+ gint extra_padding_right);*/
+
+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_pixbuf = private_chat_get_status_pixbuf;
+ chat_class->get_contact = private_chat_get_contact;
+ chat_class->get_widget = private_chat_get_widget;
+ chat_class->get_show_contacts = NULL;
+ chat_class->set_show_contacts = NULL;
+ chat_class->is_group_chat = NULL;
+
+ 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);
+ }
+
+ if (priv->scroll_idle_id) {
+ g_source_remove (priv->scroll_idle_id);
+ }
+
+ 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 ("empathy-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;
+ GossipContact *contact;
+ const gchar *status;
+
+ g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
+
+ priv = GET_PRIV (chat);
+
+ contact = gossip_chat_get_contact (chat);
+ status = gossip_contact_get_status (contact);
+
+ return g_strdup_printf ("%s\n%s",
+ gossip_contact_get_id (contact),
+ status);
+}
+
+static GdkPixbuf *
+private_chat_get_status_pixbuf (GossipChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+ GossipContact *contact;
+
+ g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
+
+ priv = GET_PRIV (chat);
+
+ contact = gossip_chat_get_contact (chat);
+
+ return gossip_pixbuf_for_contact (contact);
+}
+
+static GossipContact *
+private_chat_get_contact (GossipChat *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;
+}
+
+/* Scroll down after the back-log has been received. */
+static gboolean
+private_chat_scroll_down_idle_func (GossipChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ gossip_chat_scroll_down (chat);
+ g_object_unref (chat);
+
+ priv->scroll_idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+private_chat_setup (GossipPrivateChat *chat,
+ GossipContact *contact,
+ EmpathyTpChat *tp_chat)
+{
+ GossipPrivateChatPriv *priv;
+ //GossipLogManager *log_manager;
+ GossipChatView *view;
+/* GossipContact *sender;
+ GossipMessage *message;
+ GList *messages, *l;
+ gint num_messages, i;*/
+
+ 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::presences",
+ G_CALLBACK (private_chat_contact_presence_updated_cb),
+ chat);
+
+ view = GOSSIP_CHAT (chat)->view;
+
+ /* Turn off scrolling temporarily */
+ gossip_chat_view_scroll (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 (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)
+ private_chat_scroll_down_idle_func,
+ g_object_ref (chat));
+
+ priv->is_online = gossip_contact_is_online (priv->contact);
+}
+
+GossipPrivateChat *
+gossip_private_chat_new (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;
+}
+
+GossipPrivateChat *
+gossip_private_chat_new_with_channel (GossipContact *contact,
+ TpChan *tp_chan)
+{
+ GossipPrivateChat *chat;
+ EmpathyTpChat *tp_chat;
+ McAccount *account;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+ g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
+
+ account = gossip_contact_get_account (contact);
+ 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);
+
+ return chat;
+}
+
+/* Pads a pixbuf to the specified size, by centering it in a larger transparent
+ * pixbuf. Returns a new ref.
+ */
+#if 0
+FIXME:
+static GdkPixbuf *
+private_chat_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;
+}
+#endif
+
diff --git a/libempathy-gtk/gossip-private-chat.h b/libempathy-gtk/gossip-private-chat.h
new file mode 100644
index 000000000..48741696d
--- /dev/null
+++ b/libempathy-gtk/gossip-private-chat.h
@@ -0,0 +1,62 @@
+/* -*- 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>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ */
+
+#ifndef __GOSSIP_PRIVATE_CHAT_H__
+#define __GOSSIP_PRIVATE_CHAT_H__
+
+#include <libempathy/gossip-contact.h>
+#include <libempathy/gossip-message.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 (GossipContact *contact);
+GossipPrivateChat * gossip_private_chat_new_with_channel (GossipContact *contact,
+ TpChan *tp_chan);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_PRIVATE_CHAT_H__ */
diff --git a/libempathy-gtk/gossip-profile-chooser.c b/libempathy-gtk/gossip-profile-chooser.c
new file mode 100644
index 000000000..15f2fbb34
--- /dev/null
+++ b/libempathy-gtk/gossip-profile-chooser.c
@@ -0,0 +1,102 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * 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.
+ */
+
+#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,
+ GDK_TYPE_PIXBUF, /* Icon */
+ 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,
+ "pixbuf", COL_ICON,
+ 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, gossip_pixbuf_from_profile (profile, GTK_ICON_SIZE_BUTTON),
+ 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/libempathy-gtk/gossip-profile-chooser.h b/libempathy-gtk/gossip-profile-chooser.h
new file mode 100644
index 000000000..e608db7e1
--- /dev/null
+++ b/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) 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: 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/libempathy-gtk/gossip-spell.c b/libempathy-gtk/gossip-spell.c
new file mode 100644
index 000000000..db06e9f1d
--- /dev/null
+++ b/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/libempathy-gtk/gossip-spell.h b/libempathy-gtk/gossip-spell.h
new file mode 100644
index 000000000..f2d841b3a
--- /dev/null
+++ b/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/libempathy-gtk/gossip-stock.c b/libempathy-gtk/gossip-stock.c
new file mode 100644
index 000000000..f43949ee4
--- /dev/null
+++ b/libempathy-gtk/gossip-stock.c
@@ -0,0 +1,105 @@
+/* -*- 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 <gtk/gtk.h>
+
+#include <libempathy/gossip-paths.h>
+
+#include "gossip-stock.h"
+
+static GtkIconFactory *icon_factory = NULL;
+static GtkWidget *main_widget = NULL;
+
+static GtkStockItem stock_items[] = {
+ { GOSSIP_STOCK_OFFLINE, NULL },
+ { GOSSIP_STOCK_AVAILABLE, NULL },
+ { GOSSIP_STOCK_BUSY, NULL },
+ { GOSSIP_STOCK_AWAY, NULL },
+ { GOSSIP_STOCK_EXT_AWAY, NULL },
+ { GOSSIP_STOCK_PENDING, NULL },
+ { GOSSIP_STOCK_MESSAGE, NULL },
+ { GOSSIP_STOCK_TYPING, NULL },
+ { GOSSIP_STOCK_CONTACT_INFORMATION, NULL },
+ { GOSSIP_STOCK_GROUP_MESSAGE, NULL }
+};
+
+void
+gossip_stock_init (GtkWidget *widget)
+{
+ GtkIconSet *icon_set;
+ gint i;
+
+ g_assert (icon_factory == NULL);
+
+ main_widget = g_object_ref (widget);
+
+ gtk_stock_add (stock_items, G_N_ELEMENTS (stock_items));
+
+ icon_factory = gtk_icon_factory_new ();
+ gtk_icon_factory_add_default (icon_factory);
+ g_object_unref (icon_factory);
+
+ for (i = 0; i < G_N_ELEMENTS (stock_items); i++) {
+ gchar *path, *filename;
+ GdkPixbuf *pixbuf;
+
+ filename = g_strdup_printf ("%s.png", stock_items[i].stock_id);
+ path = gossip_paths_get_image_path (filename);
+ pixbuf = gdk_pixbuf_new_from_file (path, NULL);
+ g_free (path);
+ g_free (filename);
+
+ icon_set = gtk_icon_set_new_from_pixbuf (pixbuf);
+
+ gtk_icon_factory_add (icon_factory,
+ stock_items[i].stock_id,
+ icon_set);
+
+ gtk_icon_set_unref (icon_set);
+
+ g_object_unref (pixbuf);
+ }
+}
+
+void
+gossip_stock_finalize (void)
+{
+ g_assert (icon_factory != NULL);
+
+ gtk_icon_factory_remove_default (icon_factory);
+ g_object_unref (main_widget);
+
+ main_widget = NULL;
+ icon_factory = NULL;
+}
+
+GdkPixbuf *
+gossip_stock_render (const gchar *stock,
+ GtkIconSize size)
+{
+ return gtk_widget_render_icon (main_widget, stock, size, NULL);
+}
+
diff --git a/libempathy-gtk/gossip-stock.h b/libempathy-gtk/gossip-stock.h
new file mode 100644
index 000000000..412aceef1
--- /dev/null
+++ b/libempathy-gtk/gossip-stock.h
@@ -0,0 +1,60 @@
+/* -*- 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_STOCK_H__
+#define __GOSSIP_STOCK_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_STOCK_OFFLINE "gossip-offline"
+#define GOSSIP_STOCK_AVAILABLE "gossip-available"
+#define GOSSIP_STOCK_BUSY "gossip-busy"
+#define GOSSIP_STOCK_AWAY "gossip-away"
+#define GOSSIP_STOCK_EXT_AWAY "gossip-extended-away"
+#define GOSSIP_STOCK_PENDING "gossip-pending"
+
+#define GOSSIP_STOCK_MESSAGE "gossip-message"
+#define GOSSIP_STOCK_TYPING "gossip-typing"
+
+
+#define GOSSIP_STOCK_CONTACT_INFORMATION "vcard_16"
+
+#define GOSSIP_STOCK_AIM "gossip-aim"
+#define GOSSIP_STOCK_ICQ "gossip-icq"
+#define GOSSIP_STOCK_MSN "gossip-msn"
+#define GOSSIP_STOCK_YAHOO "gossip-yahoo"
+
+#define GOSSIP_STOCK_GROUP_MESSAGE "gossip-group-message"
+
+void gossip_stock_init (GtkWidget *widget);
+void gossip_stock_finalize (void);
+GdkPixbuf * gossip_stock_render (const gchar *stock,
+ GtkIconSize size);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_STOCK_ICONS_H__ */
diff --git a/libempathy-gtk/gossip-theme-manager.c b/libempathy-gtk/gossip-theme-manager.c
new file mode 100644
index 000000000..6d5905e5a
--- /dev/null
+++ b/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/libempathy-gtk/gossip-theme-manager.h b/libempathy-gtk/gossip-theme-manager.h
new file mode 100644
index 000000000..bb7e3cf70
--- /dev/null
+++ b/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/libempathy-gtk/gossip-ui-utils.c b/libempathy-gtk/gossip-ui-utils.c
new file mode 100644
index 000000000..e902012f1
--- /dev/null
+++ b/libempathy-gtk/gossip-ui-utils.c
@@ -0,0 +1,1360 @@
+/* -*- 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>
+ *
+ * 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 "gossip-stock.h"
+
+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_smiley (GossipSmiley type,
+ GtkIconSize icon_size)
+{
+ GtkIconTheme *theme;
+ GdkPixbuf *pixbuf = NULL;
+ GError *error = NULL;
+ gint w, h;
+ gint size;
+ const gchar *icon_id;
+
+ theme = gtk_icon_theme_get_default ();
+
+ if (!gtk_icon_size_lookup (icon_size, &w, &h)) {
+ size = 16;
+ } else {
+ size = (w + h) / 2;
+ }
+
+ 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;
+ }
+
+ pixbuf = gtk_icon_theme_load_icon (theme,
+ icon_id, /* icon name */
+ size, /* size */
+ 0, /* flags */
+ &error);
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+gossip_pixbuf_from_profile (McProfile *profile,
+ GtkIconSize icon_size)
+{
+ GtkIconTheme *theme;
+ gint size = 48;
+ gint w, h;
+ const gchar *icon_id = NULL;
+ GError *error = NULL;
+
+ theme = gtk_icon_theme_get_default ();
+
+ if (gtk_icon_size_lookup (icon_size, &w, &h)) {
+ size = (w + h) / 2;
+ }
+
+ icon_id = mc_profile_get_icon_name (profile);
+
+ theme = gtk_icon_theme_get_default ();
+ return gtk_icon_theme_load_icon (theme,
+ icon_id, /* Icon name */
+ size, /* Size */
+ 0, /* Flags */
+ &error);
+}
+
+GdkPixbuf *
+gossip_pixbuf_from_account (McAccount *account,
+ GtkIconSize icon_size)
+{
+ McProfile *profile;
+
+ profile = mc_account_get_profile (account);
+
+ return gossip_pixbuf_from_profile (profile, icon_size);
+}
+
+GdkPixbuf *
+gossip_pixbuf_for_presence_state (GossipPresenceState state)
+{
+ const gchar *stock = NULL;
+
+ switch (state) {
+ case GOSSIP_PRESENCE_STATE_AVAILABLE:
+ stock = GOSSIP_STOCK_AVAILABLE;
+ break;
+ case GOSSIP_PRESENCE_STATE_BUSY:
+ stock = GOSSIP_STOCK_BUSY;
+ break;
+ case GOSSIP_PRESENCE_STATE_AWAY:
+ stock = GOSSIP_STOCK_AWAY;
+ break;
+ case GOSSIP_PRESENCE_STATE_EXT_AWAY:
+ stock = GOSSIP_STOCK_EXT_AWAY;
+ break;
+ case GOSSIP_PRESENCE_STATE_HIDDEN:
+ case GOSSIP_PRESENCE_STATE_UNAVAILABLE:
+ stock = GOSSIP_STOCK_OFFLINE;
+ break;
+ }
+
+ return gossip_stock_render (stock, GTK_ICON_SIZE_MENU);
+}
+
+GdkPixbuf *
+gossip_pixbuf_for_presence (GossipPresence *presence)
+{
+ GossipPresenceState state;
+
+ g_return_val_if_fail (GOSSIP_IS_PRESENCE (presence),
+ gossip_pixbuf_offline ());
+
+ state = gossip_presence_get_state (presence);
+
+ return gossip_pixbuf_for_presence_state (state);
+}
+
+GdkPixbuf *
+gossip_pixbuf_for_contact (GossipContact *contact)
+{
+ GossipPresence *presence;
+ GossipSubscription subscription;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact),
+ gossip_pixbuf_offline ());
+
+ presence = gossip_contact_get_active_presence (contact);
+
+ if (presence) {
+ return gossip_pixbuf_for_presence (presence);
+ }
+
+ subscription = gossip_contact_get_subscription (contact);
+
+ if (subscription != GOSSIP_SUBSCRIPTION_BOTH &&
+ subscription != GOSSIP_SUBSCRIPTION_TO) {
+ return gossip_stock_render (GOSSIP_STOCK_PENDING,
+ GTK_ICON_SIZE_MENU);
+ }
+
+ return gossip_pixbuf_offline ();
+}
+
+GdkPixbuf *
+gossip_pixbuf_offline (void)
+{
+ return gossip_stock_render (GOSSIP_STOCK_OFFLINE,
+ GTK_ICON_SIZE_MENU);
+}
+
+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/libempathy-gtk/gossip-ui-utils.h b/libempathy-gtk/gossip-ui-utils.h
new file mode 100644
index 000000000..581587a73
--- /dev/null
+++ b/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
+ *
+ * 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>
+ *
+ * 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_smiley (GossipSmiley type,
+ GtkIconSize icon_size);
+GdkPixbuf * gossip_pixbuf_from_profile (McProfile *account,
+ GtkIconSize icon_size);
+GdkPixbuf * gossip_pixbuf_from_account (McAccount *account,
+ GtkIconSize icon_size);
+GdkPixbuf * gossip_pixbuf_for_presence_state (GossipPresenceState state);
+GdkPixbuf * gossip_pixbuf_for_presence (GossipPresence *presence);
+GdkPixbuf * gossip_pixbuf_for_contact (GossipContact *contact);
+GdkPixbuf * gossip_pixbuf_offline (void);
+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__ */