]> git.0d.be Git - empathy.git/commitdiff
[darcs-to-svn @ initial import]
authorXavier Claessens <xclaesse@src.gnome.org>
Wed, 25 Apr 2007 21:59:20 +0000 (21:59 +0000)
committerXavier Claessens <xclaesse@src.gnome.org>
Wed, 25 Apr 2007 21:59:20 +0000 (21:59 +0000)
svn path=/trunk/; revision=2

115 files changed:
AUTHORS [new file with mode: 0644]
CONTRIBUTORS [new file with mode: 0644]
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
MAINTAINERS [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
TODO [new file with mode: 0644]
accounts/Makefile.am [new file with mode: 0644]
accounts/empathy-accounts-main.c [new file with mode: 0644]
acinclude.m4 [new file with mode: 0644]
autogen.sh [new file with mode: 0644]
chat/Makefile.am [new file with mode: 0644]
chat/empathy-chat-main.c [new file with mode: 0644]
chat/empathy-chat.chandler [new file with mode: 0644]
chat/org.gnome.Empathy.Chat.service.in [new file with mode: 0644]
configure.ac [new file with mode: 0644]
contact-list/Makefile.am [new file with mode: 0644]
contact-list/empathy-contact-list-main.c [new file with mode: 0644]
launcher/Makefile.am [new file with mode: 0644]
launcher/empathy-launcher.c [new file with mode: 0644]
libempathy-gtk/Makefile.am [new file with mode: 0644]
libempathy-gtk/empathy-accounts.glade [new file with mode: 0644]
libempathy-gtk/empathy-chat.glade [new file with mode: 0644]
libempathy-gtk/empathy.schemas.in [new file with mode: 0644]
libempathy-gtk/gossip-account-widget-generic.c [new file with mode: 0644]
libempathy-gtk/gossip-account-widget-generic.h [new file with mode: 0644]
libempathy-gtk/gossip-accounts-dialog.c [new file with mode: 0644]
libempathy-gtk/gossip-accounts-dialog.h [new file with mode: 0644]
libempathy-gtk/gossip-cell-renderer-expander.c [new file with mode: 0644]
libempathy-gtk/gossip-cell-renderer-expander.h [new file with mode: 0644]
libempathy-gtk/gossip-cell-renderer-text.c [new file with mode: 0644]
libempathy-gtk/gossip-cell-renderer-text.h [new file with mode: 0644]
libempathy-gtk/gossip-chat-manager.c [new file with mode: 0644]
libempathy-gtk/gossip-chat-manager.h [new file with mode: 0644]
libempathy-gtk/gossip-chat-manager.loT [new file with mode: 0644]
libempathy-gtk/gossip-chat-view.c [new file with mode: 0644]
libempathy-gtk/gossip-chat-view.h [new file with mode: 0644]
libempathy-gtk/gossip-chat-window.c [new file with mode: 0644]
libempathy-gtk/gossip-chat-window.h [new file with mode: 0644]
libempathy-gtk/gossip-chat.c [new file with mode: 0644]
libempathy-gtk/gossip-chat.h [new file with mode: 0644]
libempathy-gtk/gossip-contact-groups.c [new file with mode: 0644]
libempathy-gtk/gossip-contact-groups.dtd [new file with mode: 0644]
libempathy-gtk/gossip-contact-groups.h [new file with mode: 0644]
libempathy-gtk/gossip-contact-list.c [new file with mode: 0644]
libempathy-gtk/gossip-contact-list.h [new file with mode: 0644]
libempathy-gtk/gossip-preferences.c [new file with mode: 0644]
libempathy-gtk/gossip-preferences.h [new file with mode: 0644]
libempathy-gtk/gossip-private-chat.c [new file with mode: 0644]
libempathy-gtk/gossip-private-chat.h [new file with mode: 0644]
libempathy-gtk/gossip-profile-chooser.c [new file with mode: 0644]
libempathy-gtk/gossip-profile-chooser.h [new file with mode: 0644]
libempathy-gtk/gossip-spell.c [new file with mode: 0644]
libempathy-gtk/gossip-spell.h [new file with mode: 0644]
libempathy-gtk/gossip-stock.c [new file with mode: 0644]
libempathy-gtk/gossip-stock.h [new file with mode: 0644]
libempathy-gtk/gossip-theme-manager.c [new file with mode: 0644]
libempathy-gtk/gossip-theme-manager.h [new file with mode: 0644]
libempathy-gtk/gossip-ui-utils.c [new file with mode: 0644]
libempathy-gtk/gossip-ui-utils.h [new file with mode: 0644]
libempathy/Makefile.am [new file with mode: 0644]
libempathy/empathy-chandler.c [new file with mode: 0644]
libempathy/empathy-chandler.h [new file with mode: 0644]
libempathy/empathy-chandler.xml [new file with mode: 0644]
libempathy/empathy-contact-list.c [new file with mode: 0644]
libempathy/empathy-contact-list.h [new file with mode: 0644]
libempathy/empathy-contact-manager.c [new file with mode: 0644]
libempathy/empathy-contact-manager.h [new file with mode: 0644]
libempathy/empathy-marshal-main.c [new file with mode: 0644]
libempathy/empathy-marshal.list [new file with mode: 0644]
libempathy/empathy-session.c [new file with mode: 0644]
libempathy/empathy-session.h [new file with mode: 0644]
libempathy/empathy-tp-chat.c [new file with mode: 0644]
libempathy/empathy-tp-chat.h [new file with mode: 0644]
libempathy/gossip-avatar.c [new file with mode: 0644]
libempathy/gossip-avatar.h [new file with mode: 0644]
libempathy/gossip-conf.c [new file with mode: 0644]
libempathy/gossip-conf.h [new file with mode: 0644]
libempathy/gossip-contact.c [new file with mode: 0644]
libempathy/gossip-contact.h [new file with mode: 0644]
libempathy/gossip-debug.c [new file with mode: 0644]
libempathy/gossip-debug.h [new file with mode: 0644]
libempathy/gossip-message.c [new file with mode: 0644]
libempathy/gossip-message.h [new file with mode: 0644]
libempathy/gossip-paths.c [new file with mode: 0644]
libempathy/gossip-paths.h [new file with mode: 0644]
libempathy/gossip-presence.c [new file with mode: 0644]
libempathy/gossip-presence.h [new file with mode: 0644]
libempathy/gossip-telepathy-group.c [new file with mode: 0644]
libempathy/gossip-telepathy-group.h [new file with mode: 0644]
libempathy/gossip-time.c [new file with mode: 0644]
libempathy/gossip-time.h [new file with mode: 0644]
libempathy/gossip-utils.c [new file with mode: 0644]
libempathy/gossip-utils.h [new file with mode: 0644]
pixmaps/Makefile.am [new file with mode: 0644]
pixmaps/gossip-available.png [new file with mode: 0644]
pixmaps/gossip-away.png [new file with mode: 0644]
pixmaps/gossip-busy.png [new file with mode: 0644]
pixmaps/gossip-extended-away.png [new file with mode: 0644]
pixmaps/gossip-group-message.png [new file with mode: 0644]
pixmaps/gossip-message.png [new file with mode: 0644]
pixmaps/gossip-offline.png [new file with mode: 0644]
pixmaps/gossip-pending.png [new file with mode: 0644]
pixmaps/gossip-typing.png [new file with mode: 0644]
pixmaps/vcard_16.png [new file with mode: 0644]
pixmaps/vcard_48.png [new file with mode: 0644]
po/ChangeLog [new file with mode: 0644]
po/Makefile.in.in [new file with mode: 0644]
po/POTFILES.in [new file with mode: 0644]
profiles/Makefile.am [new file with mode: 0644]
profiles/gtalk.profile [new file with mode: 0644]
profiles/jabber.profile [new file with mode: 0644]
profiles/msn.profile [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..2847eaa
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,4 @@
+Mikael Hallendal <micke@imendio.com>
+Richard Hult <richard@imendio.com>
+Martyn Russell <martyn@gnome.org>
+Xavier Claessens <xclaesse@gmail.com>
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
new file mode 100644 (file)
index 0000000..a9a8cd1
--- /dev/null
@@ -0,0 +1,17 @@
+Andreas Lööw
+Aurelien Naldi
+Bastien Nocera
+Christoffer Olsen
+Frederic Crozat
+Geert-Jan Van den Bogaerde
+Johan Hammar
+Jonatan Magnusson
+Jordi Mallach
+Kim Andersen
+Martyn Russell
+Mike Gratton
+Ross Burton
+Sjoerd Simons
+Thomas Reynolds
+Vincent Untz
+Xavier Claessens
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..d60c31a
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..86b8105
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,4 @@
+2006-03-16  Xavier Claessens  <xclaesse@gmail.com>
+
+       * Initial version
+
diff --git a/MAINTAINERS b/MAINTAINERS
new file mode 100644 (file)
index 0000000..422a3ed
--- /dev/null
@@ -0,0 +1 @@
+Xavier Claessens <xclaesse@gmail.com>
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..bf4cebf
--- /dev/null
@@ -0,0 +1,24 @@
+SUBDIRS = po profiles pixmaps libempathy libempathy-gtk launcher accounts contact-list chat
+
+DISTCHECK_CONFIGURE_FLAGS =    \
+       --disable-scrollkeeper
+
+INTLTOOL =                     \
+       intltool-extract.in     \
+       intltool-merge.in       \
+       intltool-update.in
+
+EXTRA_DIST =                   \
+        ChangeLog               \
+        README                  \
+       CONTRIBUTORS            \
+       $(INTLTOOL)
+
+DISTCLEANFILES =               \
+       intltool-extract        \
+       intltool-merge          \
+       intltool-update
+
+# Workaround broken scrollkeeper that doesn't remove its files on
+# uninstall.
+distuninstallcheck_listfiles = find . -type f -print | grep -v '^\./var/scrollkeeper'
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..ef16e2c
--- /dev/null
+++ b/README
@@ -0,0 +1,15 @@
+How to use empathy ?
+
+1) Install
+$ ./autogen
+$ make && make install
+
+2) Setup and enable some accounts
+$ empathy-accounts
+
+3) Get your contact list
+$ empathy-contact-list
+This will start MC and connect all enabled accounts. If you start a private chat
+or someone is saying something to you, empathy-chat will be started
+automagicaly and display a chat UI for your conversation.
+
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..9fe1f1c
--- /dev/null
+++ b/TODO
@@ -0,0 +1,20 @@
+Things you can do if you want to help:
+
+ - Find a good name, empathy is already used and gaim had legal problems with it.
+ - Rename all files and functions name to use the new name once we get a cool one.
+ - Porting gossip-account-widget-*.{c,h} from gossip project (Guillaume is already working on IRC widget).
+ - Make a EmpathyChatroom object inherited from EmpathyChat and adding support for topic, invite, members list, etc.
+ - Porting various UI widgets from gossip to libempathy-gtk for contact info, adding contact, personal info, etc.
+ - GtkWidget-ify gossip widgets imported in libempathy-gtk. Actually most window/dialog do not inherit from GtkWindow/GtkDialog...
+ - Set up the translation system and import po files from gossip? Or re-translate everything?
+ - Fix setting subscription for contacts in EmpathyContactList.
+ - Write a MC plugin to filter channels before dispatching them. For example we need a GtkStatusIcon that blink when an event arrives (text/voip/ft channel) and tells the MC to dispatch the channel only when the user clicked the icon. Like in gossip.
+ - Testing and Bugfixing.
+
+SoC projects:
+ - Adding VoIP support based on the patch proposed for gossip.
+ - Adding FileTransfer support.
+If you want to contribute you can ask for information at
+ - #telepathy on freenode
+ - Telepathy's mailing list.
diff --git a/accounts/Makefile.am b/accounts/Makefile.am
new file mode 100644 (file)
index 0000000..589cae3
--- /dev/null
@@ -0,0 +1,19 @@
+AM_CPPFLAGS =                                          \
+       -I$(top_srcdir)                                 \
+       -DPREFIX="\"$(prefix)"\"                        \
+       -DSYSCONFDIR=\""$(sysconfdir)"\"                \
+       -DDATADIR=\""$(datadir)"\"                      \
+       -DLIBDIR=\""$(libdir)"\"                        \
+       $(EMPATHY_CFLAGS)                               \
+       $(WARN_CFLAGS)
+
+bin_PROGRAMS = empathy-accounts
+
+empathy_accounts_SOURCES =                                                     \
+       empathy-accounts-main.c
+
+empathy_accounts_LDADD = \
+       $(top_builddir)/libempathy/libempathy.la                                \
+       $(top_builddir)/libempathy-gtk/libempathy-gtk.la                        \
+       $(EMPATHY_LIBS)
+
diff --git a/accounts/empathy-accounts-main.c b/accounts/empathy-accounts-main.c
new file mode 100644 (file)
index 0000000..ec30b83
--- /dev/null
@@ -0,0 +1,59 @@
+/* -*- 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 <string.h>
+#include <stdlib.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include <libempathy/empathy-session.h>
+#include <libempathy-gtk/gossip-accounts-dialog.h>
+
+static void
+destroy_cb (GtkWidget *dialog,
+           gpointer   user_data)
+{
+       empathy_session_finalize ();
+       gtk_main_quit ();
+}
+
+int
+main (int argc, char *argv[])
+{
+       GtkWidget *dialog;
+
+       gtk_init (&argc, &argv);
+
+       dialog = gossip_accounts_dialog_show ();
+
+       gtk_widget_show (dialog);
+       g_signal_connect (dialog, "destroy",
+                         G_CALLBACK (destroy_cb),
+                         NULL);
+
+       gtk_main ();
+
+       return EXIT_SUCCESS;
+}
+
diff --git a/acinclude.m4 b/acinclude.m4
new file mode 100644 (file)
index 0000000..3120d86
--- /dev/null
@@ -0,0 +1,65 @@
+dnl Turn on the additional warnings last, so -Werror doesn't affect other tests.
+
+AC_DEFUN([IDT_COMPILE_WARNINGS],[
+   if test -f $srcdir/autogen.sh; then
+       default_compile_warnings="error"
+    else
+       default_compile_warnings="no"
+    fi
+
+    AC_ARG_WITH(compile-warnings,
+                AS_HELP_STRING([--with-compile-warnings=@<:@no/yes/error@:>@],
+                               [Compiler warnings]),
+                [enable_compile_warnings="$withval"],
+                [enable_compile_warnings="$default_compile_warnings"])
+
+    warnCFLAGS=
+    if test "x$GCC" != xyes; then
+       enable_compile_warnings=no
+    fi
+
+    warning_flags=
+    realsave_CFLAGS="$CFLAGS"
+
+    case "$enable_compile_warnings" in
+    no)
+       warning_flags=
+       ;;
+    yes)
+       warning_flags="-Wall -Wunused -Wmissing-prototypes -Wmissing-declarations"
+       ;;
+    maximum|error)
+       warning_flags="-Wall -Wunused -Wchar-subscripts -Wmissing-declarations -Wmissing-prototypes -Wnested-externs -Wpointer-arith"
+       CFLAGS="$warning_flags $CFLAGS"
+       for option in -Wno-sign-compare -Wno-pointer-sign; do
+               SAVE_CFLAGS="$CFLAGS"
+               CFLAGS="$CFLAGS $option"
+               AC_MSG_CHECKING([whether gcc understands $option])
+               AC_TRY_COMPILE([], [],
+                       has_option=yes,
+                       has_option=no,)
+               CFLAGS="$SAVE_CFLAGS"
+               AC_MSG_RESULT($has_option)
+               if test $has_option = yes; then
+                 warning_flags="$warning_flags $option"
+               fi
+               unset has_option
+               unset SAVE_CFLAGS
+       done
+       unset option
+       if test "$enable_compile_warnings" = "error" ; then
+           warning_flags="$warning_flags -Werror"
+       fi
+       ;;
+    *)
+       AC_MSG_ERROR(Unknown argument '$enable_compile_warnings' to --enable-compile-warnings)
+       ;;
+    esac
+    CFLAGS="$realsave_CFLAGS"
+    AC_MSG_CHECKING(what warning flags to pass to the C compiler)
+    AC_MSG_RESULT($warning_flags)
+
+    WARN_CFLAGS="$warning_flags"
+    AC_SUBST(WARN_CFLAGS)
+])
+
diff --git a/autogen.sh b/autogen.sh
new file mode 100644 (file)
index 0000000..1b93b75
--- /dev/null
@@ -0,0 +1,174 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+: ${AUTOCONF=autoconf}
+: ${AUTOHEADER=autoheader}
+: ${AUTOMAKE=automake-1.9}
+: ${ACLOCAL=aclocal-1.9}
+: ${LIBTOOLIZE=libtoolize}
+: ${INTLTOOLIZE=intltoolize}
+: ${LIBTOOL=libtool}
+: ${GNOME_DOC_PREPARE=gnome-doc-prepare}
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+PROJECT="empathy"
+TEST_TYPE=-f
+CONFIGURE=configure.ac
+
+DIE=0
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+       echo
+       echo "You must have autoconf installed to compile $PROJECT."
+       echo "Download the appropriate package for your distribution,"
+       echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/"
+       DIE=1
+}
+
+(grep "^AC_PROG_INTLTOOL" $srcdir/$CONFIGURE >/dev/null) && {
+  ($INTLTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+    echo
+    echo "You must have \`intltoolize' installed to compile $PROJECT."
+    echo "Get ftp://ftp.gnome.org/pub/GNOME/stable/sources/intltool/intltool-0.35.tar.gz"
+    echo "(or a newer version if it is available)"
+    DIE=1
+  }
+}
+
+(grep "^GNOME_DOC_INIT" $srcdir/$CONFIGURE >/dev/null) && {
+  ($GNOME_DOC_PREPARE --version) < /dev/null > /dev/null 2>&1 || {
+    echo
+    echo "You must have \`gnome-doc-prepare' installed to compile $PROJECT."
+    DIE=1
+  }
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+       echo
+       echo "You must have automake 1.9 installed to compile $PROJECT."
+       echo "Get ftp://ftp.gnu.org/pub/gnu/automake/automake-1.9.6.tar.gz"
+       echo "(or a newer version of 1.9.x if it is available)"
+       DIE=1
+}
+
+(grep "^AM_PROG_LIBTOOL" $CONFIGURE >/dev/null) && {
+  ($LIBTOOL --version) < /dev/null > /dev/null 2>&1 || {
+    echo
+    echo "**Error**: You must have \`libtool' installed to compile $PROJECT."
+    echo "Get ftp://ftp.gnu.org/pub/gnu/libtool/libtool-1.5.22.tar.gz"
+    echo "(or a newer version if it is available)"
+    DIE=1
+  }
+}
+
+if grep "^AM_[A-Z0-9_]\{1,\}_GETTEXT" "$CONFIGURE" >/dev/null; then
+  if grep "sed.*POTFILES" "$CONFIGURE" >/dev/null; then
+    GETTEXTIZE=""
+  else
+    if grep "^AM_GLIB_GNU_GETTEXT" "$CONFIGURE" >/dev/null; then
+      GETTEXTIZE="glib-gettextize"
+      GETTEXTIZE_URL="ftp://ftp.gtk.org/pub/gtk/v2.0/glib-2.0.0.tar.gz"
+    else
+      GETTEXTIZE="gettextize"
+      GETTEXTIZE_URL="ftp://alpha.gnu.org/gnu/gettext-0.10.35.tar.gz"
+    fi
+                                                                                                          
+    $GETTEXTIZE --version < /dev/null > /dev/null 2>&1
+    if test $? -ne 0; then
+      echo
+      echo "**Error**: You must have \`$GETTEXTIZE' installed to compile $PKG_NAME."
+      echo "Get $GETTEXTIZE_URL"
+      echo "(or a newer version if it is available)"
+      DIE=1
+    fi
+  fi
+fi
+
+if test "$DIE" -eq 1; then
+       exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+       echo "You must run this script in the top-level $PROJECT directory"
+       exit 1
+}
+
+#if test -z "$*"; then
+#      echo "I am going to run ./configure with no arguments - if you wish "
+#        echo "to pass any to it, please specify them on the $0 command line."
+#fi
+
+case $CC in
+*xlc | *xlc\ * | *lcc | *lcc\ *) am_opt=--include-deps;;
+esac
+
+for coin in .
+do 
+  dr=`dirname $coin`
+  if test -f $dr/NO-AUTO-GEN; then
+    echo skipping $dr -- flagged as no auto-gen
+  else
+    echo processing $dr
+    macrodirs= #`sed -n -e 's,AM_ACLOCAL_INCLUDE(\(.*\)),\1,gp' < $coin`
+    ( cd $dr
+      aclocalinclude="$ACLOCAL_FLAGS"
+      for k in $macrodirs; do
+       if test -d $k; then
+          aclocalinclude="$aclocalinclude -I $k"
+       ##else 
+       ##  echo "**Warning**: No such directory \`$k'.  Ignored."
+        fi
+      done
+      if grep "^AM_GLIB_GNU_GETTEXT" $CONFIGURE >/dev/null; then
+       if grep "sed.*POTFILES" $CONFIGURE >/dev/null; then
+         : do nothing -- we still have an old unmodified $CONFIGURE
+       else
+         echo "Creating $dr/aclocal.m4 ..."
+         test -r $dr/aclocal.m4 || touch $dr/aclocal.m4
+         echo "Running glib-gettextize...  Ignore non-fatal messages."
+         echo "no" | glib-gettextize --force --copy
+         echo "Making $dr/aclocal.m4 writable ..."
+         test -r $dr/aclocal.m4 && chmod u+w $dr/aclocal.m4
+        fi
+      fi
+      if grep "^IT_PROG_INTLTOOL" $CONFIGURE >/dev/null; then
+        echo "Running intltoolize..."
+       intltoolize --copy --force --automake
+      fi
+      if grep "^GNOME_DOC_INIT" $CONFIGURE >/dev/null; then
+        echo "Running $GNOME_DOC_PREPARE..."
+       $GNOME_DOC_PREPARE --force --copy || exit 1
+      fi
+      if grep "^AM_PROG_LIBTOOL" $CONFIGURE >/dev/null; then
+       echo "Running $LIBTOOLIZE..."
+       $LIBTOOLIZE --force --copy
+      fi
+      echo "Running $ACLOCAL $aclocalinclude ..."
+      $ACLOCAL $aclocalinclude
+      if grep "^AM_CONFIG_HEADER" $CONFIGURE >/dev/null; then
+       echo "Running $AUTOHEADER..."
+       $AUTOHEADER
+      fi
+      echo "Running $AUTOMAKE --gnu $am_opt ..."
+      $AUTOMAKE --add-missing --gnu $am_opt
+      echo "Running $AUTOCONF ..."
+      $AUTOCONF
+    )
+  fi
+done
+
+conf_flags="--enable-maintainer-mode"
+
+cd "$ORIGDIR"
+
+if test x$NOCONFIGURE = x; then
+  echo Running $srcdir/configure $conf_flags "$@" ...
+  $srcdir/configure $conf_flags "$@" \
+  && echo Now type \`make\' to compile $PROJECT  || exit 1
+else
+  echo Skipping configure process.
+fi
diff --git a/chat/Makefile.am b/chat/Makefile.am
new file mode 100644 (file)
index 0000000..d8ee78a
--- /dev/null
@@ -0,0 +1,40 @@
+AM_CPPFLAGS =                                                  \
+       -I$(top_srcdir)                                         \
+       -DPREFIX="\"$(prefix)"\"                                \
+       -DSYSCONFDIR=\""$(sysconfdir)"\"                        \
+       -DDATADIR=\""$(datadir)"\"                              \
+       -DLIBDIR=\""$(libdir)"\"                                \
+       $(EMPATHY_CFLAGS)                                       \
+       $(WARN_CFLAGS)
+
+bin_PROGRAMS = empathy-chat
+
+empathy_chat_SOURCES =                                 \
+       empathy-chat-main.c
+
+empathy_chat_LDADD =                                           \
+       $(top_builddir)/libempathy/libempathy.la                \
+       $(top_builddir)/libempathy-gtk/libempathy-gtk.la        \
+       $(EMPATHY_LIBS)
+
+# Dbus service file
+servicedir = $(datadir)/dbus-1/services
+service_in_files = org.gnome.Empathy.Chat.service.in
+service_DATA = $(service_in_files:.service.in=.service)
+
+# Rule to make the service file with bindir expanded
+$(service_DATA): $(service_in_files) Makefile
+       @sed -e "s|\@bindir\@|$(bindir)|" $< > $@
+
+chandlerdir = $(datadir)/telepathy/managers
+chandler_DATA = empathy-chat.chandler
+
+
+EXTRA_DIST =                                   \
+       org.gnome.Empathy.Chat.service.in       \
+       $(chandler_DATA)
+
+BUILT_SOURCES =                                \
+       org.gnome.Empathy.Chat.service
+       
+CLEANFILES = $(BUILT_SOURCES)
diff --git a/chat/empathy-chat-main.c b/chat/empathy-chat-main.c
new file mode 100644 (file)
index 0000000..f279449
--- /dev/null
@@ -0,0 +1,93 @@
+/* -*- 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 <stdlib.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include <libtelepathy/tp-conn.h>
+#include <libtelepathy/tp-chan.h>
+#include <libmissioncontrol/mc-account.h>
+
+#include <libempathy/gossip-contact.h>
+#include <libempathy/empathy-chandler.h>
+#include <libempathy/empathy-session.h>
+#include <libempathy/empathy-contact-manager.h>
+#include <libempathy/empathy-contact-list.h>
+#include <libempathy-gtk/gossip-private-chat.h>
+#include <libempathy-gtk/gossip-stock.h>
+
+#define BUS_NAME "org.gnome.Empathy.Chat"
+#define OBJECT_PATH "/org/freedesktop/Telepathy/ChannelHandler"
+
+static void
+new_channel_cb (EmpathyChandler *chandler,
+               TpConn          *tp_conn,
+               TpChan          *tp_chan,
+               gpointer         user_data)
+{
+       if (tp_chan->handle_type == TP_HANDLE_TYPE_CONTACT) {
+               MissionControl        *mc;
+               McAccount             *account;
+               EmpathyContactManager *manager;
+               EmpathyContactList    *list;
+               GossipContact         *contact;
+               GossipPrivateChat     *chat;
+
+               /* We have a private chat channel */
+               mc = empathy_session_get_mission_control ();
+               account = mission_control_get_account_for_connection (mc, tp_conn, NULL);
+               manager = empathy_session_get_contact_manager ();
+               list = empathy_contact_manager_get_list (manager, account);
+               contact = empathy_contact_list_get_from_handle (list, tp_chan->handle);
+
+               chat = gossip_private_chat_new_with_channel (contact, tp_chan);
+               gossip_chat_present (GOSSIP_CHAT (chat));
+
+               g_object_unref (account);
+               g_object_unref (contact);
+               g_object_unref (chat);
+       }
+}
+
+int
+main (int argc, char *argv[])
+{
+       EmpathyChandler *chandler;
+
+       gtk_init (&argc, &argv);
+       /* FIXME: This is a horrible hack */
+       gossip_stock_init (gtk_window_new (GTK_WINDOW_TOPLEVEL));
+
+       chandler = empathy_chandler_new (BUS_NAME, OBJECT_PATH);
+
+       g_signal_connect (chandler, "new-channel",
+                         G_CALLBACK (new_channel_cb),
+                         NULL);
+
+       gtk_main ();
+
+       return EXIT_SUCCESS;
+}
+
diff --git a/chat/empathy-chat.chandler b/chat/empathy-chat.chandler
new file mode 100644 (file)
index 0000000..4cfe75b
--- /dev/null
@@ -0,0 +1,5 @@
+[ChannelHandler]
+BusName = org.gnome.Empathy.Chat
+ObjectPath = /org/freedesktop/Telepathy/ChannelHandler
+ChannelType = org.freedesktop.Telepathy.Channel.Type.Text
+
diff --git a/chat/org.gnome.Empathy.Chat.service.in b/chat/org.gnome.Empathy.Chat.service.in
new file mode 100644 (file)
index 0000000..6a794e4
--- /dev/null
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.gnome.Empathy.Chat
+Exec=@bindir@/empathy-chat
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..42a1621
--- /dev/null
@@ -0,0 +1,75 @@
+AC_INIT(Empathy, 0.1)
+AC_PREREQ(2.59)
+AC_COPYRIGHT([Copyright (C) 2003-2007 Imendio AB])
+
+AM_CONFIG_HEADER(config.h)
+AM_INIT_AUTOMAKE(1.9 dist-bzip2 no-define)
+
+AM_MAINTAINER_MODE
+
+AC_ISC_POSIX
+AC_PROG_CC
+AC_HEADER_STDC
+
+AM_PROG_LIBTOOL
+AM_PATH_GLIB_2_0
+AC_PATH_XTRA
+
+AC_PATH_PROG(DBUS_BINDING_TOOL, dbus-binding-tool)
+AC_PATH_PROG(GCONFTOOL, gconftool-2)
+AM_GCONF_SOURCE_2
+
+IT_PROG_INTLTOOL([0.35.0])
+
+GLIB_REQUIRED=2.12.0
+GTK_REQUIRED=2.10.0
+GCONF_REQUIRED=1.2.0
+LIBGLADE_REQUIRED=2.0.0
+TELEPATHY_REQUIRED=0.0.51
+MISSION_CONTROL_REQUIRED=4.20
+
+GLIB_GENMARSHAL=`$PKG_CONFIG glib-2.0 --variable=glib_genmarshal`
+AC_SUBST(GLIB_GENMARSHAL)
+
+IDT_COMPILE_WARNINGS
+
+dnl -----------------------------------------------------------
+dnl Pkg-Config dependency checks
+dnl -----------------------------------------------------------
+
+PKG_CHECK_MODULES(EMPATHY,
+[
+   glib-2.0 >= $GLIB_REQUIRED
+   gobject-2.0
+   gtk+-2.0 >= $GTK_REQUIRED
+   gconf-2.0 >= $GCONF_REQUIRED
+   libglade-2.0 >= $LIBGLADE_REQUIRED
+   libgnomeui-2.0
+   libtelepathy >= $TELEPATHY_REQUIRED
+   libmissioncontrol >= $MISSION_CONTROL_REQUIRED
+])
+
+dnl -----------------------------------------------------------
+dnl Language Support
+dnl -----------------------------------------------------------
+GETTEXT_PACKAGE=empathy
+AC_SUBST(GETTEXT_PACKAGE)
+AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE",[Gettext package name])
+
+AM_GLIB_GNU_GETTEXT
+
+
+dnl -----------------------------------------------------------
+AC_OUTPUT([
+  Makefile
+  po/Makefile.in
+  profiles/Makefile
+  pixmaps/Makefile
+  libempathy/Makefile
+  libempathy-gtk/Makefile
+  launcher/Makefile
+  accounts/Makefile
+  contact-list/Makefile
+  chat/Makefile
+])
+
diff --git a/contact-list/Makefile.am b/contact-list/Makefile.am
new file mode 100644 (file)
index 0000000..de67863
--- /dev/null
@@ -0,0 +1,20 @@
+AM_CPPFLAGS =                                          \
+       -I$(top_srcdir)                                 \
+       -DPREFIX="\"$(prefix)"\"                        \
+       -DSYSCONFDIR=\""$(sysconfdir)"\"                \
+       -DDATADIR=\""$(datadir)"\"                      \
+       -DLIBDIR=\""$(libdir)"\"                        \
+       $(EMPATHY_CFLAGS)                               \
+       $(WARN_CFLAGS)
+
+bin_PROGRAMS = empathy-contact-list
+
+empathy_contact_list_SOURCES =                                                 \
+       empathy-contact-list-main.c
+
+empathy_contact_list_LDADD = \
+       $(top_builddir)/libempathy/libempathy.la                                \
+       $(top_builddir)/libempathy-gtk/libempathy-gtk.la                        \
+       $(EMPATHY_LIBS)
+
+
diff --git a/contact-list/empathy-contact-list-main.c b/contact-list/empathy-contact-list-main.c
new file mode 100644 (file)
index 0000000..2f43284
--- /dev/null
@@ -0,0 +1,94 @@
+/* -*- 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 <stdlib.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include <libempathy/gossip-contact.h>
+#include <libempathy/empathy-session.h>
+#include <libempathy-gtk/gossip-contact-list.h>
+#include <libempathy-gtk/gossip-private-chat.h>
+#include <libempathy-gtk/gossip-stock.h>
+
+static void
+destroy_cb (GtkWidget *window,
+           gpointer   user_data)
+{
+       gossip_stock_finalize ();
+       empathy_session_finalize ();
+       gtk_main_quit ();
+}
+
+static void
+contact_chat_cb (GtkWidget     *list,
+                GossipContact *contact,
+                gpointer       user_data)
+{
+       mission_control_request_channel (empathy_session_get_mission_control (),
+                                        gossip_contact_get_account (contact),
+                                        TP_IFACE_CHANNEL_TYPE_TEXT,
+                                        gossip_contact_get_handle (contact),
+                                        TP_HANDLE_TYPE_CONTACT,
+                                        NULL, NULL);
+}
+
+int
+main (int argc, char *argv[])
+{
+       GtkWidget *window;
+       GtkWidget *list;
+       GtkWidget *sw;
+
+       gtk_init (&argc, &argv);
+
+       empathy_session_connect ();
+
+       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+       gossip_stock_init (window);
+
+       list = GTK_WIDGET (gossip_contact_list_new ());
+       sw = gtk_scrolled_window_new (NULL, NULL);      
+       gtk_container_add (GTK_CONTAINER (window), sw);
+       gtk_container_add (GTK_CONTAINER (sw), list);
+
+       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+                                       GTK_POLICY_AUTOMATIC,
+                                       GTK_POLICY_AUTOMATIC);
+       gtk_widget_set_size_request (sw, 200, 400);
+
+       g_signal_connect (window, "destroy",
+                         G_CALLBACK (destroy_cb),
+                         NULL);
+       g_signal_connect (list, "contact-chat",
+                         G_CALLBACK (contact_chat_cb),
+                         NULL);
+
+       gtk_widget_show_all (window);
+
+       gtk_main ();
+
+       return EXIT_SUCCESS;
+}
+
diff --git a/launcher/Makefile.am b/launcher/Makefile.am
new file mode 100644 (file)
index 0000000..b066253
--- /dev/null
@@ -0,0 +1,12 @@
+INCLUDES =                                             \
+       -I$(top_srcdir)                                 \
+       $(EMPATHY_CFLAGS)                               \
+       $(WARN_CFLAGS)
+
+bin_PROGRAMS = empathy-launcher
+empathy_launcher_SOURCES = empathy-launcher.c
+empathy_launcher_LDADD =                                                       \
+       $(top_builddir)/libempathy/libempathy.la                                \
+       $(EMPATHY_LIBS)
+
+
diff --git a/launcher/empathy-launcher.c b/launcher/empathy-launcher.c
new file mode 100644 (file)
index 0000000..44b390f
--- /dev/null
@@ -0,0 +1,47 @@
+/* -*- 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 <stdlib.h>
+
+#include <glib.h>
+
+#include <libempathy/empathy-session.h>
+#include <libempathy/gossip-debug.h>
+
+#define DEBUG_DOMAIN "Launcher"
+
+int
+main (int argc, char *argv[])
+{
+       GMainLoop *main_loop;
+
+       g_type_init ();
+
+       empathy_session_connect ();
+
+       main_loop = g_main_loop_new (NULL, FALSE);
+       g_main_loop_run (main_loop);
+
+       return 0;
+}
+
diff --git a/libempathy-gtk/Makefile.am b/libempathy-gtk/Makefile.am
new file mode 100644 (file)
index 0000000..1627156
--- /dev/null
@@ -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 (file)
index 0000000..a7bb391
--- /dev/null
@@ -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 (file)
index 0000000..e8bd47c
--- /dev/null
@@ -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 (file)
index 0000000..243e6ce
--- /dev/null
@@ -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 (file)
index 0000000..7f78463
--- /dev/null
@@ -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 (file)
index 0000000..1a3db63
--- /dev/null
@@ -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 (file)
index 0000000..43d9747
--- /dev/null
@@ -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 (file)
index 0000000..b6b0593
--- /dev/null
@@ -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 (file)
index 0000000..e116ace
--- /dev/null
@@ -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 (file)
index 0000000..7df4746
--- /dev/null
@@ -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 (file)
index 0000000..2b54eed
--- /dev/null
@@ -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 (file)
index 0000000..9b5a413
--- /dev/null
@@ -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 (file)
index 0000000..86cd0ea
--- /dev/null
@@ -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 (file)
index 0000000..7500b59
--- /dev/null
@@ -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 (file)
index 0000000..3d0d3ab
--- /dev/null
@@ -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 (file)
index 0000000..cc6482e
--- /dev/null
@@ -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 (file)
index 0000000..2a7b114
--- /dev/null
@@ -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 (file)
index 0000000..da305e6
--- /dev/null
@@ -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 (file)
index 0000000..8cf582d
--- /dev/null
@@ -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 (file)
index 0000000..616b3ab
--- /dev/null
@@ -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 (file)
index 0000000..9a0a0c3
--- /dev/null
@@ -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 (file)
index 0000000..8a6afda
--- /dev/null
@@ -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 (file)
index 0000000..689220f
--- /dev/null
@@ -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 (file)
index 0000000..88bbdc0
--- /dev/null
@@ -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 (file)
index 0000000..6e1573d
--- /dev/null
@@ -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 (file)
index 0000000..b5947b9
--- /dev/null
@@ -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 (file)
index 0000000..2f127a5
--- /dev/null
@@ -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 (file)
index 0000000..428d0fc
--- /dev/null
@@ -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 (file)
index 0000000..41a7f59
--- /dev/null
@@ -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 (file)
index 0000000..4874169
--- /dev/null
@@ -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 (file)
index 0000000..15f2fbb
--- /dev/null
@@ -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 (file)
index 0000000..e608db7
--- /dev/null
@@ -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 (file)
index 0000000..db06e9f
--- /dev/null
@@ -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 (file)
index 0000000..f2d841b
--- /dev/null
@@ -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 (file)
index 0000000..f43949e
--- /dev/null
@@ -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 (file)
index 0000000..412acee
--- /dev/null
@@ -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 (file)
index 0000000..6d5905e
--- /dev/null
@@ -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 (file)
index 0000000..bb7e3cf
--- /dev/null
@@ -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 (file)
index 0000000..e902012
--- /dev/null
@@ -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 (file)
index 0000000..581587a
--- /dev/null
@@ -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__ */
diff --git a/libempathy/Makefile.am b/libempathy/Makefile.am
new file mode 100644 (file)
index 0000000..30d40c5
--- /dev/null
@@ -0,0 +1,54 @@
+AM_CPPFLAGS =                                           \
+       -I.                                             \
+       -I$(top_srcdir)                                 \
+       -DDATADIR=\""$(datadir)"\"                      \
+       -DLOCALEDIR=\""$(datadir)/locale"\"             \
+       $(EMPATHY_CFLAGS)                               \
+       $(WARN_CFLAGS)
+
+BUILT_SOURCES =                                        \
+       empathy-marshal.h                               \
+       empathy-marshal.c                               \
+       empathy-chandler-glue.h
+
+noinst_LTLIBRARIES = libempathy.la
+
+libempathy_la_SOURCES =                                                        \
+       gossip-conf.c                   gossip-conf.h                           \
+       gossip-contact.c                gossip-contact.h                        \
+       gossip-avatar.c                 gossip-avatar.h                         \
+       gossip-time.c                   gossip-time.h                           \
+       gossip-presence.c               gossip-presence.h                       \
+       gossip-telepathy-group.c        gossip-telepathy-group.h                \
+       gossip-paths.c                  gossip-paths.h                          \
+       gossip-debug.c                  gossip-debug.h                          \
+       gossip-utils.c                  gossip-utils.h                          \
+       gossip-message.c                gossip-message.h                        \
+       empathy-session.c               empathy-session.h                       \
+       empathy-contact-list.c          empathy-contact-list.h                  \
+       empathy-contact-manager.c       empathy-contact-manager.h               \
+       empathy-tp-chat.c               empathy-tp-chat.h                       \
+       empathy-chandler.c              empathy-chandler.h                      \
+       empathy-marshal-main.c
+
+libempathy_la_LIBADD =                                 \
+       $(EMPATHY_LIBS)
+
+libempathy_includedir = $(includedir)/empathy/
+
+%-marshal.h: %-marshal.list Makefile.am
+       $(GLIB_GENMARSHAL) --header --prefix=$(subst -,_,$*)_marshal $< > $*-marshal.h
+
+%-marshal.c: %-marshal.list Makefile.am
+       $(GLIB_GENMARSHAL) --body --prefix=$(subst -,_,$*)_marshal $< > $*-marshal.c
+
+%-marshal-main.c: %-marshal.c %-marshal.h
+
+empathy-chandler-glue.h: empathy-chandler.xml
+       $(LIBTOOL) --mode=execute $(DBUS_BINDING_TOOL) --prefix=empathy_chandler --mode=glib-server --output=$@ $<
+
+EXTRA_DIST =                   \
+       empathy-marshal.list    \
+       empathy-chandler.xml
+
+CLEANFILES = $(BUILT_SOURCES)
diff --git a/libempathy/empathy-chandler.c b/libempathy/empathy-chandler.c
new file mode 100644 (file)
index 0000000..adc415a
--- /dev/null
@@ -0,0 +1,150 @@
+/* -*- 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 <dbus/dbus-glib.h>
+
+#include <libtelepathy/tp-helpers.h>
+#include <libtelepathy/tp-conn.h>
+#include <libtelepathy/tp-chan.h>
+
+#include "empathy-chandler.h"
+#include "gossip-debug.h"
+#include "empathy-marshal.h"
+
+#define DEBUG_DOMAIN "EmpathyChandler"
+
+static gboolean empathy_chandler_handle_channel (EmpathyChandler *chandler,
+                                                const gchar     *bus_name,
+                                                const gchar     *connection,
+                                                const gchar     *channel_type,
+                                                const gchar     *channel,
+                                                guint            handle_type,
+                                                guint            handle,
+                                                GError         **error);
+
+#include "empathy-chandler-glue.h"
+
+enum {
+       NEW_CHANNEL,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (EmpathyChandler, empathy_chandler, G_TYPE_OBJECT)
+
+static void
+empathy_chandler_class_init (EmpathyChandlerClass *klass)
+{
+       signals[NEW_CHANNEL] =
+               g_signal_new ("new-channel",
+                             G_OBJECT_CLASS_TYPE (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             empathy_marshal_VOID__OBJECT_OBJECT,
+                             G_TYPE_NONE,
+                             2, TELEPATHY_CONN_TYPE, TELEPATHY_CHAN_TYPE);
+}
+
+static void
+empathy_chandler_init (EmpathyChandler *chandler)
+{
+}
+
+EmpathyChandler *
+empathy_chandler_new (const gchar *bus_name,
+                     const gchar *object_path)
+{
+       static gboolean  initialized = FALSE;
+       EmpathyChandler *chandler;
+       DBusGProxy      *proxy;
+       guint            result;
+       GError          *error = NULL;
+
+       if (!initialized) {
+               dbus_g_object_type_install_info (EMPATHY_TYPE_CHANDLER,
+                                                &dbus_glib_empathy_chandler_object_info);
+               initialized = TRUE;
+       }
+
+       proxy = dbus_g_proxy_new_for_name (tp_get_bus (),
+                                          DBUS_SERVICE_DBUS,
+                                          DBUS_PATH_DBUS,
+                                          DBUS_INTERFACE_DBUS);
+
+       if (!dbus_g_proxy_call (proxy, "RequestName", &error,
+                               G_TYPE_STRING, bus_name,
+                               G_TYPE_UINT, DBUS_NAME_FLAG_DO_NOT_QUEUE,
+                               G_TYPE_INVALID,
+                               G_TYPE_UINT, &result,
+                               G_TYPE_INVALID)) {
+               gossip_debug (DEBUG_DOMAIN,
+                             "Failed to request name: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+
+               return NULL;
+       }
+       g_object_unref (proxy);
+
+       chandler = g_object_new (EMPATHY_TYPE_CHANDLER, NULL);
+       dbus_g_connection_register_g_object (tp_get_bus (),
+                                            object_path,
+                                            G_OBJECT (chandler));
+
+       return chandler;
+}
+
+static gboolean
+empathy_chandler_handle_channel (EmpathyChandler  *chandler,
+                                const gchar      *bus_name,
+                                const gchar      *connection,
+                                const gchar      *channel_type,
+                                const gchar      *channel,
+                                guint             handle_type,
+                                guint             handle,
+                                GError          **error)
+{
+       TpChan *tp_chan;
+       TpConn *tp_conn;
+
+       tp_conn = tp_conn_new (tp_get_bus (),
+                              bus_name,
+                              connection);
+
+       tp_chan = tp_chan_new (tp_get_bus(),
+                              bus_name,
+                              channel,
+                              channel_type,
+                              handle_type,
+                              handle);
+
+       g_signal_emit (chandler, signals[NEW_CHANNEL], 0, tp_conn, tp_chan);
+
+       g_object_unref (tp_chan);
+       g_object_unref (tp_conn);
+
+       return TRUE;
+}
+
diff --git a/libempathy/empathy-chandler.h b/libempathy/empathy-chandler.h
new file mode 100644 (file)
index 0000000..ca27161
--- /dev/null
@@ -0,0 +1,53 @@
+/* -*- 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.
+ *
+ */
+
+#ifndef __EMPATHY_CHANDLER_H__
+#define __EMPATHY_CHANDLER_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_CHANDLER         (empathy_chandler_get_type ())
+#define EMPATHY_CHANDLER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_CHANDLER, EmpathyChandler))
+#define EMPATHY_CHANDLER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), EMPATHY_TYPE_CHANDLER, EmpathyChandlerClass))
+#define EMPATHY_IS_CHANDLER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_CHANDLER))
+#define EMPATHY_IS_CHANDLER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_CHANDLER))
+#define EMPATHY_CHANDLER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_CHANDLER, EmpathyChandlerClass))
+
+typedef struct _EmpathyChandler      EmpathyChandler;
+typedef struct _EmpathyChandlerClass EmpathyChandlerClass;
+
+struct _EmpathyChandler {
+       GObject parent;
+};
+
+struct _EmpathyChandlerClass {
+       GObjectClass parent_class;
+};
+
+GType            empathy_chandler_get_type (void) G_GNUC_CONST;
+EmpathyChandler *empathy_chandler_new      (const gchar *bus_name,
+                                           const gchar *object_path);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_CHANDLER_H__ */
diff --git a/libempathy/empathy-chandler.xml b/libempathy/empathy-chandler.xml
new file mode 100644 (file)
index 0000000..0fb264e
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<node name="/">
+  <interface name="org.freedesktop.Telepathy.ChannelHandler">
+    <method name="HandleChannel">
+      <arg direction="in" type="s" name="bus_name" />
+      <arg direction="in" type="o" name="connection" />
+      <arg direction="in" type="s" name="channel_type" />
+      <arg direction="in" type="o" name="channel" />
+      <arg direction="in" type="u" name="handle_type" />
+      <arg direction="in" type="u" name="handle" />
+    </method>
+  </interface>
+</node>
diff --git a/libempathy/empathy-contact-list.c b/libempathy/empathy-contact-list.c
new file mode 100644 (file)
index 0000000..2cc486d
--- /dev/null
@@ -0,0 +1,1793 @@
+/* -*- 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 <string.h>
+
+#include <libtelepathy/tp-conn.h>
+#include <libtelepathy/tp-chan.h>
+#include <libtelepathy/tp-helpers.h>
+#include <libtelepathy/tp-chan-type-contact-list-gen.h>
+#include <libtelepathy/tp-conn-iface-aliasing-gen.h>
+#include <libtelepathy/tp-conn-iface-presence-gen.h>
+#include <libtelepathy/tp-conn-iface-avatars-gen.h>
+
+#include "empathy-contact-list.h"
+#include "empathy-session.h"
+#include "gossip-debug.h"
+#include "gossip-telepathy-group.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
+                      EMPATHY_TYPE_CONTACT_LIST, EmpathyContactListPriv))
+
+#define DEBUG_DOMAIN "ContactList"
+#define MAX_AVATAR_REQUESTS 10
+
+struct _EmpathyContactListPriv {
+       TpConn               *tp_conn;
+       McAccount            *account;
+       GossipContact        *own_contact;
+
+       GossipTelepathyGroup *known;
+       GossipTelepathyGroup *publish;
+       GossipTelepathyGroup *subscribe;
+
+       GHashTable           *groups;
+       GHashTable           *contacts;
+
+       DBusGProxy           *aliasing_iface;
+       DBusGProxy           *avatars_iface;
+       DBusGProxy           *presence_iface;
+
+       GList                *avatar_requests_queue;
+};
+
+typedef enum {
+       CONTACT_LIST_TYPE_KNOWN,
+       CONTACT_LIST_TYPE_PUBLISH,
+       CONTACT_LIST_TYPE_SUBSCRIBE,
+       CONTACT_LIST_TYPE_UNKNOWN,
+       CONTACT_LIST_TYPE_COUNT
+} ContactListType;
+
+typedef struct {
+       guint  handle;
+       GList *new_groups;
+} ContactListData;
+
+typedef struct {
+       EmpathyContactList *list;
+       guint                 handle;
+} ContactListAvatarRequestData;
+
+typedef struct {
+       EmpathyContactList *list;
+       guint                *handles;
+} ContactListAliasesRequestData;
+
+static void                   empathy_contact_list_class_init       (EmpathyContactListClass       *klass);
+static void                   empathy_contact_list_init             (EmpathyContactList            *list);
+static void                   contact_list_finalize                 (GObject                       *object);
+static void                   contact_list_finalize_proxies         (EmpathyContactList            *list);
+static void                   contact_list_contact_removed_foreach  (guint                          handle,
+                                                                    GossipContact                 *contact,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_destroy_cb               (DBusGProxy                    *proxy,
+                                                                    EmpathyContactList            *list);
+static gboolean               contact_list_find_foreach             (guint                          handle,
+                                                                    GossipContact                 *contact,
+                                                                    gchar                         *id);
+static void                   contact_list_newchannel_cb            (DBusGProxy                    *proxy,
+                                                                    const gchar                   *object_path,
+                                                                    const gchar                   *channel_type,
+                                                                    TelepathyHandleType            handle_type,
+                                                                    guint                          channel_handle,
+                                                                    gboolean                       suppress_handle,
+                                                                    EmpathyContactList            *list);
+static ContactListType        contact_list_get_type                 (EmpathyContactList            *list,
+                                                                    TpChan                        *list_chan);
+static void                   contact_list_contact_added_cb         (GossipTelepathyGroup          *group,
+                                                                    GArray                        *handles,
+                                                                    guint                          actor_handle,
+                                                                    guint                          reason,
+                                                                    const gchar                   *message,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_contact_removed_cb       (GossipTelepathyGroup          *group,
+                                                                    GArray                        *handles,
+                                                                    guint                          actor_handle,
+                                                                    guint                          reason,
+                                                                    const gchar                   *message,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_local_pending_cb         (GossipTelepathyGroup          *group,
+                                                                    GArray                        *handles,
+                                                                    guint                          actor_handle,
+                                                                    guint                          reason,
+                                                                    const gchar                   *message,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_groups_updated_cb        (GossipContact                 *contact,
+                                                                    GParamSpec                    *param,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_subscription_updated_cb  (GossipContact                 *contact,
+                                                                    GParamSpec                    *param,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_name_updated_cb          (GossipContact                 *contact,
+                                                                    GParamSpec                    *param,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_update_groups_foreach    (gchar                         *object_path,
+                                                                    GossipTelepathyGroup          *group,
+                                                                    ContactListData               *data);
+static GossipTelepathyGroup * contact_list_get_group                (EmpathyContactList            *list,
+                                                                    const gchar                   *name);
+static gboolean               contact_list_find_group               (gchar                         *key,
+                                                                    GossipTelepathyGroup          *group,
+                                                                    gchar                         *group_name);
+static void                   contact_list_get_groups_foreach       (gchar                         *key,
+                                                                    GossipTelepathyGroup          *group,
+                                                                    GList                        **groups);
+static void                   contact_list_group_channel_closed_cb  (TpChan                        *channel,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_group_members_added_cb   (GossipTelepathyGroup          *group,
+                                                                    GArray                        *members,
+                                                                    guint                          actor_handle,
+                                                                    guint                          reason,
+                                                                    const gchar                   *message,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_group_members_removed_cb (GossipTelepathyGroup          *group,
+                                                                    GArray                        *members,
+                                                                    guint                          actor_handle,
+                                                                    guint                          reason,
+                                                                    const gchar                   *message,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_get_contacts_foreach     (guint                          handle,
+                                                                    GossipContact                 *contact,
+                                                                    GList                        **contacts);
+static void                   contact_list_get_info                 (EmpathyContactList            *list,
+                                                                    GArray                        *handles);
+static void                   contact_list_request_avatar           (EmpathyContactList            *list,
+                                                                    guint                          handle);
+static void                   contact_list_start_avatar_requests    (EmpathyContactList            *list);
+static void                   contact_list_avatar_update_cb         (DBusGProxy                    *proxy,
+                                                                    guint                          handle,
+                                                                    gchar                         *new_token,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_request_avatar_cb        (DBusGProxy                    *proxy,
+                                                                    GArray                        *avatar_data,
+                                                                    gchar                         *mime_type,
+                                                                    GError                        *error,
+                                                                    ContactListAvatarRequestData  *data);
+static void                   contact_list_aliases_update_cb        (DBusGProxy                    *proxy,
+                                                                    GPtrArray                     *handlers,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_request_aliases_cb       (DBusGProxy                    *proxy,
+                                                                    gchar                        **contact_names,
+                                                                    GError                        *error,
+                                                                    ContactListAliasesRequestData *data);
+static void                   contact_list_presence_update_cb       (DBusGProxy                    *proxy,
+                                                                    GHashTable                    *handle_table,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_parse_presence_foreach   (guint                          handle,
+                                                                    GValueArray                   *presence_struct,
+                                                                    EmpathyContactList            *list);
+static void                   contact_list_presences_table_foreach  (const gchar                   *state_str,
+                                                                    GHashTable                    *presences_table,
+                                                                    GossipPresence               **presence);
+static GossipPresenceState    contact_list_presence_state_from_str  (const gchar                   *str);
+
+enum {
+       CONTACT_ADDED,
+       CONTACT_REMOVED,
+       DESTROY,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+static guint n_avatar_requests = 0;
+
+G_DEFINE_TYPE (EmpathyContactList, empathy_contact_list, G_TYPE_OBJECT);
+
+static void
+empathy_contact_list_class_init (EmpathyContactListClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = contact_list_finalize;
+
+       signals[CONTACT_ADDED] =
+               g_signal_new ("contact-added",
+                             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_REMOVED] =
+               g_signal_new ("contact-removed",
+                             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[DESTROY] =
+               g_signal_new ("destroy",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__VOID,
+                             G_TYPE_NONE,
+                             0);
+
+       g_type_class_add_private (object_class, sizeof (EmpathyContactListPriv));
+}
+
+static void
+empathy_contact_list_init (EmpathyContactList *list)
+{
+       EmpathyContactListPriv *priv;
+
+       priv = GET_PRIV (list);
+
+       priv->groups = g_hash_table_new_full (g_str_hash,
+                                             g_str_equal,
+                                             (GDestroyNotify) g_free,
+                                             (GDestroyNotify) g_object_unref);
+       priv->contacts = g_hash_table_new_full (g_direct_hash,
+                                               g_direct_equal,
+                                               NULL,
+                                               (GDestroyNotify) g_object_unref);
+}
+
+
+static void
+contact_list_finalize (GObject *object)
+{
+       EmpathyContactListPriv *priv;
+       EmpathyContactList     *list;
+
+       list = EMPATHY_CONTACT_LIST (object);
+       priv = GET_PRIV (list);
+
+       gossip_debug (DEBUG_DOMAIN, "finalize: %p", object);
+
+       if (priv->tp_conn) {
+               contact_list_finalize_proxies (list);
+               g_object_unref (priv->tp_conn);
+       }
+
+       if (priv->known) {
+               g_object_unref (priv->known);
+       }
+
+       if (priv->subscribe) {
+               g_object_unref (priv->subscribe);
+       }
+
+       if (priv->publish) {
+               g_object_unref (priv->publish);
+       }
+
+       g_object_unref (priv->account);
+       g_object_unref (priv->own_contact);
+       g_hash_table_destroy (priv->groups);
+       g_hash_table_destroy (priv->contacts);
+
+       G_OBJECT_CLASS (empathy_contact_list_parent_class)->finalize (object);
+}
+
+EmpathyContactList *
+empathy_contact_list_new (McAccount *account)
+{
+       EmpathyContactListPriv *priv;
+       EmpathyContactList     *list;
+       MissionControl         *mc;
+       TpConn                 *tp_conn;
+       guint                   handle;
+       GError                 *error = NULL;
+
+       g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+
+       mc = empathy_session_get_mission_control ();
+
+       if (mission_control_get_connection_status (mc, account, NULL) != 0) {
+               /* The account is not connected, nothing to do. */
+               return NULL;
+       }
+
+       tp_conn = mission_control_get_connection (mc, account, NULL);
+       g_return_val_if_fail (tp_conn != NULL, NULL);
+
+       list = g_object_new (EMPATHY_TYPE_CONTACT_LIST, NULL);
+       priv = GET_PRIV (list);
+
+       priv->tp_conn = tp_conn;
+       priv->account = g_object_ref (account);
+
+       g_signal_connect (priv->tp_conn, "destroy",
+                         G_CALLBACK (contact_list_destroy_cb),
+                         list);
+
+       priv->aliasing_iface = tp_conn_get_interface (priv->tp_conn,
+                                                     TELEPATHY_CONN_IFACE_ALIASING_QUARK);
+       priv->avatars_iface = tp_conn_get_interface (priv->tp_conn,
+                                                    TELEPATHY_CONN_IFACE_AVATARS_QUARK);
+       priv->presence_iface = tp_conn_get_interface (priv->tp_conn,
+                                                     TELEPATHY_CONN_IFACE_PRESENCE_QUARK);
+
+       if (priv->aliasing_iface) {
+               dbus_g_proxy_connect_signal (priv->aliasing_iface,
+                                            "AliasesChanged",
+                                            G_CALLBACK (contact_list_aliases_update_cb),
+                                            list, NULL);
+       }
+
+       if (priv->avatars_iface) {
+               dbus_g_proxy_connect_signal (priv->avatars_iface,
+                                            "AvatarUpdated",
+                                            G_CALLBACK (contact_list_avatar_update_cb),
+                                            list, NULL);
+       }
+
+       if (priv->presence_iface) {
+               dbus_g_proxy_connect_signal (priv->presence_iface,
+                                            "PresenceUpdate",
+                                            G_CALLBACK (contact_list_presence_update_cb),
+                                            list, NULL);
+       }
+
+       /* Get our own handle and contact */
+       if (!tp_conn_get_self_handle (DBUS_G_PROXY (priv->tp_conn),
+                                     &handle, &error)) {
+               gossip_debug (DEBUG_DOMAIN, "GetSelfHandle Error: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+       } else {
+               priv->own_contact = empathy_contact_list_get_from_handle (list, handle);
+       }
+
+       return list;
+}
+
+void
+empathy_contact_list_setup (EmpathyContactList *list)
+{
+       EmpathyContactListPriv *priv;
+       GPtrArray                *channels;
+       GError                   *error = NULL;
+       guint                     i;
+
+       g_return_if_fail (EMPATHY_IS_CONTACT_LIST (list));
+
+       priv = GET_PRIV (list);
+
+       dbus_g_proxy_connect_signal (DBUS_G_PROXY (priv->tp_conn), "NewChannel",
+                                    G_CALLBACK (contact_list_newchannel_cb),
+                                    list, NULL);
+
+       /* Get existing channels */
+       if (!tp_conn_list_channels (DBUS_G_PROXY (priv->tp_conn),
+                                   &channels,
+                                   &error)) {
+               gossip_debug (DEBUG_DOMAIN,
+                             "Failed to get list of open channels: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+               return;
+       }
+
+       for (i = 0; channels->len > i; i++) {
+               GValueArray         *chan_struct;
+               const gchar         *object_path;
+               const gchar         *chan_iface;
+               TelepathyHandleType  handle_type;
+               guint                handle;
+
+               chan_struct = g_ptr_array_index (channels, i);
+               object_path = g_value_get_boxed (g_value_array_get_nth (chan_struct, 0));
+               chan_iface = g_value_get_string (g_value_array_get_nth (chan_struct, 1));
+               handle_type = g_value_get_uint (g_value_array_get_nth (chan_struct, 2));
+               handle = g_value_get_uint (g_value_array_get_nth (chan_struct, 3));
+
+               contact_list_newchannel_cb (DBUS_G_PROXY (priv->tp_conn),
+                                           object_path, chan_iface,
+                                           handle_type, handle,
+                                           FALSE, list);
+
+               g_value_array_free (chan_struct);
+       }
+
+       g_ptr_array_free (channels, TRUE);
+}
+
+McAccount *
+empathy_contact_list_get_account (EmpathyContactList *list)
+{
+       EmpathyContactListPriv *priv;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+
+       priv = GET_PRIV (list);
+
+       return priv->account;
+}
+
+GossipContact *
+empathy_contact_list_get_own (EmpathyContactList *list)
+{
+       EmpathyContactListPriv *priv;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+
+       priv = GET_PRIV (list);
+       
+       return priv->own_contact;
+}
+
+GossipContact *
+empathy_contact_list_find (EmpathyContactList *list,
+                          const gchar        *id)
+{
+       EmpathyContactListPriv *priv;
+       GossipContact            *contact;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+
+       priv = GET_PRIV (list);
+
+       contact = g_hash_table_find (priv->contacts,
+                                    (GHRFunc) contact_list_find_foreach,
+                                    (gchar*) id);
+
+       return NULL;
+}
+
+void
+empathy_contact_list_add (EmpathyContactList *list,
+                         guint               handle,
+                         const gchar        *message)
+{
+       EmpathyContactListPriv *priv;
+
+       g_return_if_fail (EMPATHY_IS_CONTACT_LIST (list));
+
+       priv = GET_PRIV (list);
+
+       gossip_telepathy_group_add_member (priv->subscribe, handle, message);
+}
+
+void
+empathy_contact_list_remove (EmpathyContactList *list,
+                            guint               handle)
+{
+       EmpathyContactListPriv *priv;
+
+       g_return_if_fail (EMPATHY_IS_CONTACT_LIST (list));
+
+       priv = GET_PRIV (list);
+
+       gossip_telepathy_group_remove_member (priv->subscribe, handle, "");
+       gossip_telepathy_group_remove_member (priv->publish, handle, "");
+       gossip_telepathy_group_remove_member (priv->known, handle, "");
+}
+
+GossipContact *
+empathy_contact_list_get_from_id (EmpathyContactList *list,
+                                 const gchar        *id)
+{
+       EmpathyContactListPriv *priv;
+       GossipContact          *contact;
+       const gchar            *contact_ids[] = {id, NULL};
+       GArray                 *handles;
+       guint                   handle;
+       GError                 *error = NULL;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+       g_return_val_if_fail (id != NULL, NULL);
+       
+       priv = GET_PRIV (list);
+
+       contact = empathy_contact_list_find (list, id);
+       if (contact) {
+               return contact;
+       }
+
+       /* The id is unknown, requests a new handle */
+       if (!tp_conn_request_handles (DBUS_G_PROXY (priv->tp_conn),
+                                     TP_HANDLE_TYPE_CONTACT,
+                                     contact_ids,
+                                     &handles, &error)) {
+               gossip_debug (DEBUG_DOMAIN, 
+                             "RequestHandle for %s failed: %s", id,
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+               return 0;
+       }
+
+       handle = g_array_index(handles, guint, 0);
+       g_array_free (handles, TRUE);
+
+       return empathy_contact_list_get_from_handle (list, handle);
+}
+
+GossipContact *
+empathy_contact_list_get_from_handle (EmpathyContactList *list,
+                                     guint               handle)
+{
+       GossipContact *contact;
+       GArray        *handles;
+       GList         *contacts;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+
+       handles = g_array_new (FALSE, FALSE, sizeof (guint));
+       g_array_append_val (handles, handle);
+
+       contacts = empathy_contact_list_get_from_handles (list, handles);
+       g_array_free (handles, TRUE);
+
+       if (!contacts) {
+               return NULL;
+       }
+
+       contact = contacts->data;
+       g_list_free (contacts);
+
+       return contact;
+}
+
+GList *
+empathy_contact_list_get_from_handles (EmpathyContactList *list,
+                                      GArray             *handles)
+{
+       EmpathyContactListPriv  *priv;
+       gchar                  **handles_names;
+       gchar                  **id;
+       GArray                  *new_handles;
+       GList                   *contacts = NULL;
+       guint                    i;
+       GError                  *error = NULL;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+       g_return_val_if_fail (handles != NULL, NULL);
+
+       priv = GET_PRIV (list);
+
+       /* Search all handles we already have */
+       new_handles = g_array_new (FALSE, FALSE, sizeof (guint));
+       for (i = 0; i < handles->len; i++) {
+               GossipContact *contact;
+               guint          handle;
+
+               handle = g_array_index (handles, guint, i);
+               contact = g_hash_table_lookup (priv->contacts,
+                                              GUINT_TO_POINTER (handle));
+
+               if (contact) {
+                       contacts = g_list_prepend (contacts,
+                                                  g_object_ref (contact));
+               } else {
+                       g_array_append_val (new_handles, handle);
+               }
+       }
+
+       if (new_handles->len == 0) {
+               return contacts;
+       }
+
+       /* Holds all handles we don't have yet.
+        * FIXME: We should release them at some point. */
+       if (!tp_conn_hold_handles (DBUS_G_PROXY (priv->tp_conn),
+                                  TP_HANDLE_TYPE_CONTACT,
+                                  new_handles, &error)) {
+               gossip_debug (DEBUG_DOMAIN, 
+                             "HoldHandles Error: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+               g_array_free (new_handles, TRUE);
+               return contacts;
+       }
+
+       /* Get the IDs of all new handles */
+       if (!tp_conn_inspect_handles (DBUS_G_PROXY (priv->tp_conn),
+                                     TP_HANDLE_TYPE_CONTACT,
+                                     new_handles,
+                                     &handles_names,
+                                     &error)) {
+               gossip_debug (DEBUG_DOMAIN, 
+                             "InspectHandle Error: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+               g_array_free (new_handles, TRUE);
+               return contacts;
+       }
+
+       /* Create contact objects */
+       for (i = 0, id = handles_names; *id && i < new_handles->len; id++, i++) {
+               GossipContact *contact;
+               guint          handle;
+
+               handle = g_array_index (new_handles, guint, i);
+               contact = g_object_new (GOSSIP_TYPE_CONTACT,
+                                       "account", priv->account,
+                                       "id", *id,
+                                       "handle", handle,
+                                       NULL);
+
+               g_signal_connect (contact, "notify::groups",
+                                 G_CALLBACK (contact_list_groups_updated_cb),
+                                 list);
+               g_signal_connect (contact, "notify::subscription",
+                                 G_CALLBACK (contact_list_subscription_updated_cb),
+                                 list);
+               g_signal_connect (contact, "notify::name",
+                                 G_CALLBACK (contact_list_name_updated_cb),
+                                 list);
+
+               gossip_debug (DEBUG_DOMAIN, "new contact created: %s (%d)",
+                             *id, handle);
+
+               g_hash_table_insert (priv->contacts,
+                                    GUINT_TO_POINTER (handle),
+                                    contact);
+
+               contacts = g_list_prepend (contacts, g_object_ref (contact));
+       }
+
+       contact_list_get_info (list, new_handles);
+
+       g_array_free (new_handles, TRUE);
+       g_strfreev (handles_names);
+
+       return contacts;
+}
+
+void
+empathy_contact_list_rename_group (EmpathyContactList *list,
+                                  const gchar        *old_group,
+                                  const gchar        *new_group)
+{
+       EmpathyContactListPriv *priv;
+       GossipTelepathyGroup   *group;
+       GArray                 *members;
+
+       g_return_if_fail (EMPATHY_IS_CONTACT_LIST (list));
+       g_return_if_fail (old_group != NULL);
+       g_return_if_fail (new_group != NULL);
+
+       priv = GET_PRIV (list);
+
+       group = g_hash_table_find (priv->groups,
+                                  (GHRFunc) contact_list_find_group,
+                                  (gchar*) old_group);
+       if (!group) {
+               /* The group doesn't exists on this account */
+               return;
+       }
+
+       gossip_debug (DEBUG_DOMAIN, "rename group %s to %s", group, new_group);
+
+       /* Remove all members from the old group */
+       members = gossip_telepathy_group_get_members (group);
+       gossip_telepathy_group_remove_members (group, members, "");
+       contact_list_group_members_removed_cb (group, members, 
+                                              0, 
+                                              TP_CHANNEL_GROUP_CHANGE_REASON_NONE, 
+                                              NULL, list);
+       g_hash_table_remove (priv->groups,
+                            gossip_telepathy_group_get_object_path (group));
+
+       /* Add all members to the new group */
+       group = contact_list_get_group (list, new_group);
+       if (group) {
+               gossip_telepathy_group_add_members (group, members, "");
+       }
+}
+
+GList *
+empathy_contact_list_get_groups (EmpathyContactList *list)
+{
+       EmpathyContactListPriv *priv;
+       GList                    *groups = NULL;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+
+       priv = GET_PRIV (list);
+
+       g_hash_table_foreach (priv->groups,
+                             (GHFunc) contact_list_get_groups_foreach,
+                             &groups);
+
+       groups = g_list_sort (groups, (GCompareFunc) strcmp);
+
+       return groups;
+}
+
+GList *
+empathy_contact_list_get_contacts (EmpathyContactList *list)
+{
+       EmpathyContactListPriv *priv;
+       GList                    *contacts = NULL;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list), NULL);
+
+       priv = GET_PRIV (list);
+
+       /* FIXME: we should only return contacts that are in the contact list */
+       g_hash_table_foreach (priv->contacts,
+                             (GHFunc) contact_list_get_contacts_foreach,
+                             &contacts);
+
+       return contacts;
+}
+
+static void
+contact_list_finalize_proxies (EmpathyContactList *list)
+{
+       EmpathyContactListPriv *priv;
+
+       priv = GET_PRIV (list);
+
+       g_signal_handlers_disconnect_by_func (priv->tp_conn,
+                                             contact_list_destroy_cb,
+                                             list);
+       dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (priv->tp_conn), "NewChannel",
+                                       G_CALLBACK (contact_list_newchannel_cb),
+                                       list);
+
+       if (priv->aliasing_iface) {
+               dbus_g_proxy_disconnect_signal (priv->aliasing_iface,
+                                               "AliasesChanged",
+                                               G_CALLBACK (contact_list_aliases_update_cb),
+                                               list);
+       }
+
+       if (priv->avatars_iface) {
+               dbus_g_proxy_disconnect_signal (priv->avatars_iface,
+                                               "AvatarUpdated",
+                                               G_CALLBACK (contact_list_avatar_update_cb),
+                                               list);
+       }
+
+       if (priv->presence_iface) {
+               dbus_g_proxy_disconnect_signal (priv->presence_iface,
+                                               "PresenceUpdate",
+                                               G_CALLBACK (contact_list_presence_update_cb),
+                                               list);
+       }
+}
+
+static void
+contact_list_destroy_cb (DBusGProxy           *proxy,
+                        EmpathyContactList *list)
+{
+       EmpathyContactListPriv *priv;
+
+       priv = GET_PRIV (list);
+
+       gossip_debug (DEBUG_DOMAIN, "Connection destroyed. "
+                                   "Account disconnected or CM crashed.");
+
+       /* DBus proxies should NOT be used anymore */
+       g_object_unref (priv->tp_conn);
+       priv->tp_conn = NULL;
+       priv->aliasing_iface = NULL;
+       priv->avatars_iface = NULL;
+       priv->presence_iface = NULL;
+
+       /* Remove all contacts */
+       g_hash_table_foreach (priv->contacts,
+                             (GHFunc) contact_list_contact_removed_foreach,
+                             list);
+       g_hash_table_remove_all (priv->contacts);
+
+       /* Tell the world to not use us anymore */
+       g_signal_emit (list, signals[DESTROY], 0);
+}
+
+static void
+contact_list_contact_removed_foreach (guint                 handle,
+                                     GossipContact        *contact,
+                                     EmpathyContactList *list)
+{
+       g_signal_handlers_disconnect_by_func (contact,
+                                             contact_list_groups_updated_cb,
+                                             list);
+       g_signal_handlers_disconnect_by_func (contact,
+                                             contact_list_subscription_updated_cb,
+                                             list);
+       g_signal_handlers_disconnect_by_func (contact,
+                                             contact_list_name_updated_cb,
+                                             list);
+
+       g_signal_emit (list, signals[CONTACT_REMOVED], 0, contact);
+}
+
+static void
+contact_list_block_contact (EmpathyContactList *list,
+                           GossipContact        *contact)
+{
+       g_signal_handlers_block_by_func (contact,
+                                        contact_list_groups_updated_cb,
+                                        list);
+       g_signal_handlers_block_by_func (contact,
+                                        contact_list_subscription_updated_cb,
+                                        list);
+       g_signal_handlers_block_by_func (contact,
+                                        contact_list_name_updated_cb,
+                                        list);
+}
+
+static void
+contact_list_unblock_contact (EmpathyContactList *list,
+                             GossipContact        *contact)
+{
+       g_signal_handlers_unblock_by_func (contact,
+                                          contact_list_groups_updated_cb,
+                                          list);
+       g_signal_handlers_unblock_by_func (contact,
+                                          contact_list_subscription_updated_cb,
+                                          list);
+       g_signal_handlers_unblock_by_func (contact,
+                                          contact_list_name_updated_cb,
+                                          list);
+}
+
+static gboolean
+contact_list_find_foreach (guint          handle,
+                          GossipContact *contact,
+                          gchar         *id)
+{
+       if (strcmp (gossip_contact_get_id (contact), id) == 0) {
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void
+contact_list_newchannel_cb (DBusGProxy          *proxy,
+                           const gchar         *object_path,
+                           const gchar         *channel_type,
+                           TelepathyHandleType  handle_type,
+                           guint                channel_handle,
+                           gboolean             suppress_handle,
+                           EmpathyContactList  *list)
+{
+       EmpathyContactListPriv *priv;
+       GossipTelepathyGroup   *group;
+       TpChan                 *new_chan;
+       const gchar            *bus_name;
+       GArray                 *members;
+
+       priv = GET_PRIV (list);
+
+       if (strcmp (channel_type, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST) != 0 ||
+           suppress_handle) {
+               return;
+       }
+
+       bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (priv->tp_conn));
+       new_chan = tp_chan_new (tp_get_bus (),
+                               bus_name,
+                               object_path,
+                               channel_type, handle_type, channel_handle);
+
+       if (handle_type == TP_HANDLE_TYPE_LIST) {
+               ContactListType list_type;
+
+               list_type = contact_list_get_type (list, new_chan);
+               if (list_type == CONTACT_LIST_TYPE_UNKNOWN) {
+                       gossip_debug (DEBUG_DOMAIN, "Unknown contact list channel");
+                       g_object_unref (new_chan);
+                       return;
+               }
+
+               gossip_debug (DEBUG_DOMAIN, "New contact list channel of type: %d",
+                             list_type);
+
+               group = gossip_telepathy_group_new (new_chan, priv->tp_conn);
+
+               switch (list_type) {
+               case CONTACT_LIST_TYPE_KNOWN:
+                       if (priv->known) {
+                               g_object_unref (priv->known);
+                       }
+                       priv->known = group;
+                       break;
+               case CONTACT_LIST_TYPE_PUBLISH:
+                       if (priv->publish) {
+                               g_object_unref (priv->publish);
+                       }
+                       priv->publish = group;
+                       break;
+               case CONTACT_LIST_TYPE_SUBSCRIBE:
+                       if (priv->subscribe) {
+                               g_object_unref (priv->subscribe);
+                       }
+                       priv->subscribe = group;
+                       break;
+               default:
+                       g_assert_not_reached ();
+               }
+
+               /* Connect and setup the new contact-list group */
+               if (list_type == CONTACT_LIST_TYPE_KNOWN ||
+                   list_type == CONTACT_LIST_TYPE_SUBSCRIBE) {
+                       g_signal_connect (group, "members-added",
+                                         G_CALLBACK (contact_list_contact_added_cb),
+                                         list);
+                       g_signal_connect (group, "members-removed",
+                                         G_CALLBACK (contact_list_contact_removed_cb),
+                                         list);
+
+                       members = gossip_telepathy_group_get_members (group);
+                       contact_list_contact_added_cb (group, members, 0,
+                                                      TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
+                                                      NULL, list);
+                       g_array_free (members, TRUE);
+               }
+               if (list_type == CONTACT_LIST_TYPE_PUBLISH) {
+                       GPtrArray *info;
+                       GArray    *pending; 
+                       guint      i;
+
+                       g_signal_connect (group, "local-pending",
+                                         G_CALLBACK (contact_list_local_pending_cb),
+                                         list);
+
+                       info = gossip_telepathy_group_get_local_pending_members_with_info (group);
+
+                       if (!info) {
+                               /* This happens with butterfly because
+                                * GetLocalPendingMembersWithInfo is not 
+                                * implemented */
+                               g_object_unref (new_chan);
+                               return;
+                       }
+
+                       pending = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1);
+                       for (i = 0; info->len > i; i++) {
+                               GValueArray *pending_struct;
+                               guint        member;
+                               guint        invitor;
+                               guint        reason;
+                               const gchar *message;
+
+                               pending_struct = g_ptr_array_index (info, i);
+                               member = g_value_get_uint (g_value_array_get_nth (pending_struct, 0));
+                               invitor = g_value_get_uint (g_value_array_get_nth (pending_struct, 1));
+                               reason = g_value_get_uint (g_value_array_get_nth (pending_struct, 2));
+                               message = g_value_get_string (g_value_array_get_nth (pending_struct, 3));
+
+                               g_array_insert_val (pending, 0, member);
+
+                               contact_list_local_pending_cb (group, pending,
+                                                              invitor,
+                                                              reason,
+                                                              message, list);
+
+                               g_value_array_free (pending_struct);
+                       }
+
+                       g_ptr_array_free (info, TRUE);
+                       g_array_free (pending, TRUE);
+               }
+       }
+       else if (handle_type == TP_HANDLE_TYPE_GROUP) {
+               const gchar *object_path;
+
+               object_path = dbus_g_proxy_get_path (DBUS_G_PROXY (new_chan));
+               if (g_hash_table_lookup (priv->groups, object_path)) {
+                       g_object_unref (new_chan);
+                       return;
+               }
+
+               group = gossip_telepathy_group_new (new_chan, priv->tp_conn);
+
+               gossip_debug (DEBUG_DOMAIN, "New server-side group channel: %s",
+                             gossip_telepathy_group_get_name (group));
+
+               dbus_g_proxy_connect_signal (DBUS_G_PROXY (new_chan), "Closed",
+                                            G_CALLBACK
+                                            (contact_list_group_channel_closed_cb),
+                                            list, NULL);
+
+               g_hash_table_insert (priv->groups, g_strdup (object_path), group);
+               g_signal_connect (group, "members-added",
+                                 G_CALLBACK (contact_list_group_members_added_cb),
+                                 list);
+               g_signal_connect (group, "members-removed",
+                                 G_CALLBACK (contact_list_group_members_removed_cb),
+                                 list);
+
+               members = gossip_telepathy_group_get_members (group);
+               contact_list_group_members_added_cb (group, members, 0,
+                                                    TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
+                                                    NULL, list);
+               g_array_free (members, TRUE);
+       }
+
+       g_object_unref (new_chan);
+}
+
+static ContactListType
+contact_list_get_type (EmpathyContactList *list,
+                      TpChan             *list_chan)
+{
+       EmpathyContactListPriv  *priv;
+       GArray                  *handles;
+       gchar                  **handle_name;
+       ContactListType          list_type;
+       GError                  *error = NULL;
+
+       priv = GET_PRIV (list);
+
+       handles = g_array_new (FALSE, FALSE, sizeof (guint));
+       g_array_append_val (handles, list_chan->handle);
+
+       if (!tp_conn_inspect_handles (DBUS_G_PROXY (priv->tp_conn),
+                                     TP_HANDLE_TYPE_LIST,
+                                     handles,
+                                     &handle_name,
+                                     &error)) {
+               gossip_debug (DEBUG_DOMAIN, 
+                             "InspectHandle Error: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+               g_array_free (handles, TRUE);
+               return CONTACT_LIST_TYPE_UNKNOWN;
+       }
+
+       if (strcmp (*handle_name, "subscribe") == 0) {
+               list_type = CONTACT_LIST_TYPE_SUBSCRIBE;
+       } else if (strcmp (*handle_name, "publish") == 0) {
+               list_type = CONTACT_LIST_TYPE_PUBLISH;
+       } else if (strcmp (*handle_name, "known") == 0) {
+               list_type = CONTACT_LIST_TYPE_KNOWN;
+       } else {
+               list_type = CONTACT_LIST_TYPE_UNKNOWN;
+       }
+
+       g_strfreev (handle_name);
+       g_array_free (handles, TRUE);
+
+       return list_type;
+}
+
+static void
+contact_list_contact_added_cb (GossipTelepathyGroup *group,
+                              GArray               *handles,
+                              guint                 actor_handle,
+                              guint                 reason,
+                              const gchar          *message,
+                              EmpathyContactList   *list)
+{
+       EmpathyContactListPriv *priv;
+       GList                  *added_list, *l;
+
+       priv = GET_PRIV (list);
+
+       added_list = empathy_contact_list_get_from_handles (list, handles);
+
+       for (l = added_list; l; l = l->next) {
+               GossipContact *contact;
+
+               contact = GOSSIP_CONTACT (l->data);
+               contact_list_block_contact (list, contact);
+               gossip_contact_set_subscription (contact, GOSSIP_SUBSCRIPTION_BOTH);
+               contact_list_unblock_contact (list, contact);
+
+               g_signal_emit (list, signals[CONTACT_ADDED], 0, contact);
+
+               g_object_unref (contact);
+       }
+
+       g_list_free (added_list);
+}
+
+static void
+contact_list_contact_removed_cb (GossipTelepathyGroup *group,
+                                GArray               *handles,
+                                guint                 actor_handle,
+                                guint                 reason,
+                                const gchar          *message,
+                                EmpathyContactList   *list)
+{
+       EmpathyContactListPriv *priv;
+       GList                  *removed_list, *l;
+
+       priv = GET_PRIV (list);
+
+       removed_list = empathy_contact_list_get_from_handles (list, handles);
+
+       for (l = removed_list; l; l = l->next) {
+               GossipContact *contact;
+               guint          handle;
+
+               contact = GOSSIP_CONTACT (l->data);
+
+               handle = gossip_contact_get_handle (contact);
+               g_hash_table_remove (priv->contacts, GUINT_TO_POINTER (handle));
+
+               g_signal_emit (list, signals[CONTACT_REMOVED], 0, contact);
+
+               g_object_unref (contact);
+       }
+
+       g_list_free (removed_list);
+}
+
+static void
+contact_list_local_pending_cb (GossipTelepathyGroup *group,
+                              GArray               *handles,
+                              guint                 actor_handle,
+                              guint                 reason,
+                              const gchar          *message,
+                              EmpathyContactList   *list)
+{
+       EmpathyContactListPriv *priv;
+       GList                  *pending_list, *l;
+
+       priv = GET_PRIV (list);
+
+       pending_list = empathy_contact_list_get_from_handles (list, handles);
+
+       for (l = pending_list; l; l = l->next) {
+               GossipContact *contact;
+
+               contact = GOSSIP_CONTACT (l->data);
+
+               /* FIXME: Is that the correct way ? */
+               contact_list_block_contact (list, contact);
+               gossip_contact_set_subscription (contact, GOSSIP_SUBSCRIPTION_FROM);
+               contact_list_unblock_contact (list, contact);
+               g_signal_emit (list, signals[CONTACT_ADDED], 0, contact);
+
+               g_object_unref (contact);
+       }
+
+       g_list_free (pending_list);
+}
+
+static void
+contact_list_groups_updated_cb (GossipContact      *contact,
+                               GParamSpec         *param,
+                               EmpathyContactList *list)
+{
+       EmpathyContactListPriv *priv;
+       ContactListData         data;
+       GList                  *groups, *l;
+
+       priv = GET_PRIV (list);
+
+       /* Make sure all groups are created */
+       groups = gossip_contact_get_groups (contact);
+       for (l = groups; l; l = l->next) {
+               contact_list_get_group (list, l->data);
+       }
+
+       data.handle = gossip_contact_get_handle (contact);
+       data.new_groups = groups;
+
+       g_hash_table_foreach (priv->groups,
+                             (GHFunc) contact_list_update_groups_foreach,
+                             &data);
+}
+
+static void
+contact_list_subscription_updated_cb (GossipContact      *contact,
+                                     GParamSpec         *param,
+                                     EmpathyContactList *list)
+{
+       EmpathyContactListPriv *priv;
+       GossipSubscription      subscription;
+       guint                   handle;
+
+       priv = GET_PRIV (list);
+
+       subscription = gossip_contact_get_subscription (contact);
+       handle = gossip_contact_get_handle (contact);
+
+       /* FIXME: what to do here, I'm a bit lost... */
+       if (subscription) {
+               gossip_telepathy_group_add_member (priv->publish, handle, "");
+       } else {
+               gossip_telepathy_group_remove_member (priv->publish, handle, "");
+       }
+}
+
+static void
+contact_list_name_updated_cb (GossipContact      *contact,
+                             GParamSpec         *param,
+                             EmpathyContactList *list)
+{
+       EmpathyContactListPriv *priv;
+       GHashTable             *new_alias;
+       const gchar            *new_name;
+       guint                   handle;
+       GError                 *error = NULL;
+
+       priv = GET_PRIV (list);
+       
+       handle = gossip_contact_get_handle (contact);
+       new_name = gossip_contact_get_name (contact);
+
+       gossip_debug (DEBUG_DOMAIN, "renaming handle %d to %s",
+                     handle, new_name);
+
+       new_alias = g_hash_table_new_full (g_direct_hash,
+                                          g_direct_equal,
+                                          NULL,
+                                          g_free);
+
+       g_hash_table_insert (new_alias,
+                            GUINT_TO_POINTER (handle),
+                            g_strdup (new_name));
+
+       if (!tp_conn_iface_aliasing_set_aliases (priv->aliasing_iface,
+                                                new_alias,
+                                                &error)) {
+               gossip_debug (DEBUG_DOMAIN, 
+                             "Couldn't rename contact: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+       }
+
+       g_hash_table_destroy (new_alias);
+}
+
+static void
+contact_list_update_groups_foreach (gchar                *object_path,
+                                   GossipTelepathyGroup *group,
+                                   ContactListData      *data)
+{
+       gboolean     is_member;
+       gboolean     found = FALSE;
+       const gchar *group_name;
+       GList       *l;
+
+       is_member = gossip_telepathy_group_is_member (group, data->handle);
+       group_name = gossip_telepathy_group_get_name (group);
+
+       for (l = data->new_groups; l; l = l->next) {
+               if (strcmp (group_name, l->data) == 0) {
+                       found = TRUE;
+                       break;
+               }
+       }
+
+       if (is_member && !found) {
+               /* We are no longer member of this group */
+               gossip_debug (DEBUG_DOMAIN, "Contact %d removed from group '%s'",
+                             data->handle, group_name);
+               gossip_telepathy_group_remove_member (group, data->handle, "");
+       }
+
+       if (!is_member && found) {
+               /* We are now member of this group */
+               gossip_debug (DEBUG_DOMAIN, "Contact %d added to group '%s'",
+                             data->handle, group_name);
+               gossip_telepathy_group_add_member (group, data->handle, "");
+       }
+}
+
+static GossipTelepathyGroup *
+contact_list_get_group (EmpathyContactList *list,
+                       const gchar        *name)
+{
+       EmpathyContactListPriv *priv;
+       GossipTelepathyGroup   *group;
+       TpChan                 *group_channel;
+       GArray                 *handles;
+       guint                   group_handle;
+       char                   *group_object_path;
+       const char             *names[2] = {name, NULL};
+       GError                 *error = NULL;
+
+       priv = GET_PRIV (list);
+
+       group = g_hash_table_find (priv->groups,
+                                  (GHRFunc) contact_list_find_group,
+                                  (gchar*) name);
+       if (group) {
+               return group;
+       }
+
+       gossip_debug (DEBUG_DOMAIN, "creating new group: %s", name);
+
+       if (!tp_conn_request_handles (DBUS_G_PROXY (priv->tp_conn),
+                                     TP_HANDLE_TYPE_GROUP,
+                                     names,
+                                     &handles,
+                                     &error)) {
+               gossip_debug (DEBUG_DOMAIN,
+                             "Couldn't request the creation of a new handle for group: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+               return NULL;
+       }
+       group_handle = g_array_index (handles, guint, 0);
+       g_array_free (handles, TRUE);
+
+       if (!tp_conn_request_channel (DBUS_G_PROXY (priv->tp_conn),
+                                     TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
+                                     TP_HANDLE_TYPE_GROUP,
+                                     group_handle,
+                                     FALSE,
+                                     &group_object_path,
+                                     &error)) {
+               gossip_debug (DEBUG_DOMAIN,
+                             "Couldn't request the creation of a new group channel: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+               return NULL;
+       }
+
+       group_channel = tp_chan_new (tp_get_bus (),
+                                    dbus_g_proxy_get_bus_name (DBUS_G_PROXY (priv->tp_conn)),
+                                    group_object_path,
+                                    TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
+                                    TP_HANDLE_TYPE_GROUP,
+                                    group_handle);
+
+       dbus_g_proxy_connect_signal (DBUS_G_PROXY (group_channel),
+                                    "Closed",
+                                    G_CALLBACK
+                                    (contact_list_group_channel_closed_cb),
+                                    list,
+                                    NULL);
+
+       group = gossip_telepathy_group_new (group_channel, priv->tp_conn);
+       g_hash_table_insert (priv->groups, group_object_path, group);
+       g_signal_connect (group, "members-added",
+                         G_CALLBACK (contact_list_group_members_added_cb),
+                         list);
+       g_signal_connect (group, "members-removed",
+                         G_CALLBACK (contact_list_group_members_removed_cb),
+                         list);
+
+       return group;
+}
+
+static gboolean
+contact_list_find_group (gchar                 *key,
+                        GossipTelepathyGroup  *group,
+                        gchar                 *group_name)
+{
+       if (strcmp (group_name, gossip_telepathy_group_get_name (group)) == 0) {
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void
+contact_list_get_groups_foreach (gchar                 *key,
+                                GossipTelepathyGroup  *group,
+                                GList                **groups)
+{
+       const gchar *name;
+
+       name = gossip_telepathy_group_get_name (group);
+       *groups = g_list_append (*groups, g_strdup (name));
+}
+
+static void
+contact_list_group_channel_closed_cb (TpChan             *channel,
+                                     EmpathyContactList *list)
+{
+       EmpathyContactListPriv *priv;
+
+       priv = GET_PRIV (list);
+
+       g_hash_table_remove (priv->groups,
+                            dbus_g_proxy_get_path (DBUS_G_PROXY (channel)));
+}
+
+static void
+contact_list_group_members_added_cb (GossipTelepathyGroup *group,
+                                    GArray               *members,
+                                    guint                 actor_handle,
+                                    guint                 reason,
+                                    const gchar          *message,
+                                    EmpathyContactList   *list)
+{
+       EmpathyContactListPriv *priv;
+       GList                  *added_list, *l;
+       const gchar            *group_name;
+
+       priv = GET_PRIV (list);
+
+       group_name = gossip_telepathy_group_get_name (group);
+       added_list = empathy_contact_list_get_from_handles (list, members);
+
+       for (l = added_list; l; l = l->next) {
+               GossipContact *contact;
+               GList         *contact_groups;
+
+               contact = GOSSIP_CONTACT (l->data);
+               contact_groups = gossip_contact_get_groups (contact);
+
+               if (!g_list_find_custom (contact_groups,
+                                        group_name,
+                                        (GCompareFunc) strcmp)) {
+                       gossip_debug (DEBUG_DOMAIN, "Contact %s added to group '%s'",
+                                     gossip_contact_get_name (contact),
+                                     group_name);
+                       contact_groups = g_list_append (contact_groups,
+                                                       g_strdup (group_name));
+                       contact_list_block_contact (list, contact);
+                       gossip_contact_set_groups (contact, contact_groups);
+                       contact_list_unblock_contact (list, contact);
+               }
+
+               g_object_unref (contact);
+       }
+
+       g_list_free (added_list);
+}
+
+static void
+contact_list_group_members_removed_cb (GossipTelepathyGroup *group,
+                                      GArray               *members,
+                                      guint                 actor_handle,
+                                      guint                 reason,
+                                      const gchar          *message,
+                                      EmpathyContactList   *list)
+{
+       EmpathyContactListPriv *priv;
+       GList                  *removed_list, *l;
+       const gchar            *group_name;
+
+       priv = GET_PRIV (list);
+
+       group_name = gossip_telepathy_group_get_name (group);
+       removed_list = empathy_contact_list_get_from_handles (list, members);
+
+       for (l = removed_list; l; l = l->next) {
+               GossipContact *contact;
+               GList         *contact_groups;
+               GList         *to_remove;
+
+               /* FIXME: Does it leak ? */
+               contact = GOSSIP_CONTACT (l->data);
+               contact_groups = gossip_contact_get_groups (contact);
+               contact_groups = g_list_copy (contact_groups);
+
+               to_remove = g_list_find_custom (contact_groups,
+                                               group_name,
+                                               (GCompareFunc) strcmp);
+               if (to_remove) {
+                       gossip_debug (DEBUG_DOMAIN, "Contact %d removed from group '%s'",
+                                     gossip_contact_get_handle (contact),
+                                     group_name);
+                       contact_groups = g_list_remove_link (contact_groups,
+                                                            to_remove);
+                       contact_list_block_contact (list, contact);
+                       gossip_contact_set_groups (contact, contact_groups);
+                       contact_list_unblock_contact (list, contact);
+               }
+
+               g_list_free (contact_groups);
+
+               g_object_unref (contact);
+       }
+
+       g_list_free (removed_list);
+}
+
+static void
+contact_list_get_contacts_foreach (guint           handle,
+                                  GossipContact  *contact,
+                                  GList         **contacts)
+{
+       *contacts = g_list_append (*contacts, g_object_ref (contact));
+}
+
+static void
+contact_list_get_info (EmpathyContactList *list,
+                      GArray             *handles)
+{
+       EmpathyContactListPriv *priv;
+       GError                 *error = NULL;
+
+       priv = GET_PRIV (list);
+
+       if (priv->presence_iface) {
+               /* FIXME: We should use GetPresence instead */
+               if (!tp_conn_iface_presence_request_presence (priv->presence_iface,
+                                                             handles, &error)) {
+                       gossip_debug (DEBUG_DOMAIN, 
+                                     "Could not request presences: %s",
+                                     error ? error->message : "No error given");
+                       g_clear_error (&error);
+               }
+       }
+
+       if (priv->aliasing_iface) {
+               ContactListAliasesRequestData *data;
+
+               data = g_slice_new (ContactListAliasesRequestData);
+               data->list = list;
+               data->handles = g_memdup (handles->data, handles->len * sizeof (guint));
+
+               tp_conn_iface_aliasing_request_aliases_async (priv->aliasing_iface,
+                                                             handles,
+                                                             (tp_conn_iface_aliasing_request_aliases_reply)
+                                                             contact_list_request_aliases_cb,
+                                                             data);
+       }
+
+       if (priv->avatars_iface) {
+               guint i;
+
+               for (i = 0; i < handles->len; i++) {
+                       guint handle;
+
+                       handle = g_array_index (handles, gint, i);
+                       contact_list_request_avatar (list, handle);
+               }
+       }
+}
+
+static void
+contact_list_request_avatar (EmpathyContactList *list,
+                            guint               handle)
+{
+       EmpathyContactListPriv *priv;
+
+       priv = GET_PRIV (list);
+       
+       /* We queue avatar requests to not send too many dbus async
+        * calls at once. If we don't we reach the dbus's limit of
+        * pending calls */
+       priv->avatar_requests_queue = g_list_append (priv->avatar_requests_queue,
+                                                    GUINT_TO_POINTER (handle));
+       contact_list_start_avatar_requests (list);
+}
+
+static void
+contact_list_start_avatar_requests (EmpathyContactList *list)
+{
+       EmpathyContactListPriv       *priv;
+       ContactListAvatarRequestData *data;
+
+       priv = GET_PRIV (list);
+
+       while (n_avatar_requests <  MAX_AVATAR_REQUESTS &&
+              priv->avatar_requests_queue) {
+               data = g_slice_new (ContactListAvatarRequestData);
+               data->list = list;
+               data->handle = GPOINTER_TO_UINT (priv->avatar_requests_queue->data);
+
+               n_avatar_requests++;
+               priv->avatar_requests_queue = g_list_remove (priv->avatar_requests_queue,
+                                                            priv->avatar_requests_queue->data);
+
+               tp_conn_iface_avatars_request_avatar_async (priv->avatars_iface,
+                                                           data->handle,
+                                                           (tp_conn_iface_avatars_request_avatar_reply)
+                                                           contact_list_request_avatar_cb,
+                                                           data);
+       }
+}
+
+static void
+contact_list_avatar_update_cb (DBusGProxy         *proxy,
+                              guint               handle,
+                              gchar              *new_token,
+                              EmpathyContactList *list)
+{
+       gossip_debug (DEBUG_DOMAIN, "Changing avatar for %d to %s",
+                     handle, new_token);
+
+       contact_list_request_avatar (list, handle);
+}
+
+static void
+contact_list_request_avatar_cb (DBusGProxy                   *proxy,
+                               GArray                       *avatar_data,
+                               gchar                        *mime_type,
+                               GError                       *error,
+                               ContactListAvatarRequestData *data)
+{
+       GossipContact *contact;
+
+       contact = empathy_contact_list_get_from_handle (data->list, data->handle);
+
+       if (error) {
+               gossip_debug (DEBUG_DOMAIN, "Error requesting avatar for %s: %s",
+                             gossip_contact_get_name (contact),
+                             error ? error->message : "No error given");
+       } else {
+               GossipAvatar *avatar;
+
+               avatar = gossip_avatar_new (avatar_data->data,
+                                           avatar_data->len,
+                                           mime_type);
+               contact_list_block_contact (data->list, contact);
+               gossip_contact_set_avatar (contact, avatar);
+               contact_list_unblock_contact (data->list, contact);
+               gossip_avatar_unref (avatar);
+       }
+
+       n_avatar_requests--;
+       contact_list_start_avatar_requests (data->list);
+
+       g_slice_free (ContactListAvatarRequestData, data);
+}
+
+static void
+contact_list_aliases_update_cb (DBusGProxy         *proxy,
+                               GPtrArray          *renamed_handlers,
+                               EmpathyContactList *list)
+{
+       gint i;
+
+       for (i = 0; renamed_handlers->len > i; i++) {
+               guint          handle;
+               const gchar   *alias;
+               GValueArray   *renamed_struct;
+               GossipContact *contact;
+
+               renamed_struct = g_ptr_array_index (renamed_handlers, i);
+               handle = g_value_get_uint(g_value_array_get_nth (renamed_struct, 0));
+               alias = g_value_get_string(g_value_array_get_nth (renamed_struct, 1));
+
+               if (alias && *alias == '\0') {
+                       alias = NULL;
+               }
+
+               contact = empathy_contact_list_get_from_handle (list, handle);
+               contact_list_block_contact (list, contact);
+               gossip_contact_set_name (contact, alias);
+               contact_list_unblock_contact (list, contact);
+
+               gossip_debug (DEBUG_DOMAIN, "contact %d renamed to %s (update cb)",
+                             handle, alias);
+       }
+}
+
+static void
+contact_list_request_aliases_cb (DBusGProxy                     *proxy,
+                                gchar                         **contact_names,
+                                GError                         *error,
+                                ContactListAliasesRequestData  *data)
+{
+       guint   i = 0;
+       gchar **name;
+
+       for (name = contact_names; *name && !error; name++) {
+               GossipContact *contact;
+
+               contact = empathy_contact_list_get_from_handle (data->list,
+                                                               data->handles[i]);
+               contact_list_block_contact (data->list, contact);
+               gossip_contact_set_name (contact, *name);
+               contact_list_unblock_contact (data->list, contact);
+
+               gossip_debug (DEBUG_DOMAIN, "contact %d renamed to %s (request cb)",
+                             data->handles[i], *name);
+
+               i++;
+       }
+
+       g_free (data->handles);
+       g_slice_free (ContactListAliasesRequestData, data);
+}
+
+static void
+contact_list_presence_update_cb (DBusGProxy         *proxy,
+                                GHashTable         *handle_table,
+                                EmpathyContactList *list)
+{
+       g_hash_table_foreach (handle_table,
+                             (GHFunc) contact_list_parse_presence_foreach,
+                             list);
+}
+
+static void
+contact_list_parse_presence_foreach (guint               handle,
+                                    GValueArray        *presence_struct,
+                                    EmpathyContactList *list)
+{
+       GHashTable     *presences_table;
+       GossipContact  *contact;
+       GossipPresence *presence = NULL;
+
+       contact = empathy_contact_list_get_from_handle (list, handle);
+       presences_table = g_value_get_boxed (g_value_array_get_nth (presence_struct, 1));
+
+       g_hash_table_foreach (presences_table,
+                             (GHFunc) contact_list_presences_table_foreach,
+                             &presence);
+
+       gossip_debug (DEBUG_DOMAIN, "Presence changed for %s (%d)",
+                     gossip_contact_get_name (contact),
+                     handle);
+
+       contact_list_block_contact (list, contact);
+       if (presence) {
+               gossip_contact_add_presence (contact, presence);
+               g_object_unref (presence);
+       } else {
+               g_object_set (contact, "presences", NULL, NULL);
+       }
+       contact_list_unblock_contact (list, contact);
+}
+
+static void
+contact_list_presences_table_foreach (const gchar     *state_str,
+                                     GHashTable      *presences_table,
+                                     GossipPresence **presence)
+{
+       GossipPresenceState  state;
+       const GValue        *message;
+
+       if (*presence) {
+               g_object_unref (*presence);
+               *presence = NULL;
+       }
+
+       state = contact_list_presence_state_from_str (state_str);
+       if (state == GOSSIP_PRESENCE_STATE_UNAVAILABLE) {
+               return;
+       }
+
+       *presence = gossip_presence_new ();
+       gossip_presence_set_state (*presence, state);
+
+       message = g_hash_table_lookup (presences_table, "message");
+       if (message != NULL) {
+               gossip_presence_set_status (*presence,
+                                           g_value_get_string (message));
+       }
+
+       gossip_presence_set_resource (*presence, "");
+}
+
+static GossipPresenceState
+contact_list_presence_state_from_str (const gchar *str)
+{
+       if (strcmp (str, "available") == 0) {
+               return GOSSIP_PRESENCE_STATE_AVAILABLE;
+       } else if ((strcmp (str, "dnd") == 0) || (strcmp (str, "busy") == 0)) {
+               return GOSSIP_PRESENCE_STATE_BUSY;
+       } else if ((strcmp (str, "away") == 0) || (strcmp (str, "brb") == 0)) {
+               return GOSSIP_PRESENCE_STATE_AWAY;
+       } else if (strcmp (str, "xa") == 0) {
+               return GOSSIP_PRESENCE_STATE_EXT_AWAY;
+       } else if (strcmp (str, "hidden") == 0) {
+               return GOSSIP_PRESENCE_STATE_HIDDEN;
+       } else if (strcmp (str, "offline") == 0) {
+               return GOSSIP_PRESENCE_STATE_UNAVAILABLE;
+       } else if (strcmp (str, "chat") == 0) {
+               /* We don't support chat, so treat it like available. */
+               return GOSSIP_PRESENCE_STATE_AVAILABLE;
+       }
+
+       return GOSSIP_PRESENCE_STATE_AVAILABLE;
+}
+
diff --git a/libempathy/empathy-contact-list.h b/libempathy/empathy-contact-list.h
new file mode 100644 (file)
index 0000000..db8b36e
--- /dev/null
@@ -0,0 +1,76 @@
+/* -*- 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.
+ */
+
+#ifndef __EMPATHY_CONTACT_LIST_H__
+#define __EMPATHY_CONTACT_LIST_H__
+
+#include <glib.h>
+#include <libmissioncontrol/mc-account.h>
+
+#include "gossip-contact.h"
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_CONTACT_LIST         (empathy_contact_list_get_type ())
+#define EMPATHY_CONTACT_LIST(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_CONTACT_LIST, EmpathyContactList))
+#define EMPATHY_CONTACT_LIST_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_CONTACT_LIST, EmpathyContactListClass))
+#define EMPATHY_IS_CONTACT_LIST(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_CONTACT_LIST))
+#define EMPATHY_IS_CONTACT_LIST_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_CONTACT_LIST))
+#define EMPATHY_CONTACT_LIST_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_CONTACT_LIST, EmpathyContactListClass))
+
+typedef struct _EmpathyContactList      EmpathyContactList;
+typedef struct _EmpathyContactListClass EmpathyContactListClass;
+typedef struct _EmpathyContactListPriv  EmpathyContactListPriv;
+
+struct _EmpathyContactList {
+       GObject      parent;
+};
+
+struct _EmpathyContactListClass {
+       GObjectClass parent_class;
+};
+
+GType                empathy_contact_list_get_type         (void) G_GNUC_CONST;
+EmpathyContactList * empathy_contact_list_new              (McAccount          *account);
+void                 empathy_contact_list_setup            (EmpathyContactList *list);
+McAccount *          empathy_contact_list_get_account      (EmpathyContactList *list);
+GossipContact *      empathy_contact_list_get_own          (EmpathyContactList *list);
+GossipContact *      empathy_contact_list_find             (EmpathyContactList *list,
+                                                           const gchar        *id);
+void                 empathy_contact_list_add              (EmpathyContactList *list,
+                                                           guint               handle,
+                                                           const gchar        *message);
+void                 empathy_contact_list_remove           (EmpathyContactList *list,
+                                                           guint               handle);
+GossipContact *      empathy_contact_list_get_from_id      (EmpathyContactList *list,
+                                                           const gchar        *id);
+GossipContact *      empathy_contact_list_get_from_handle  (EmpathyContactList *list,
+                                                           guint               handle);
+GList *              empathy_contact_list_get_from_handles (EmpathyContactList *list,
+                                                           GArray             *handles);
+void                 empathy_contact_list_rename_group     (EmpathyContactList *list,
+                                                           const gchar        *old_group,
+                                                           const gchar        *new_group);
+GList *              empathy_contact_list_get_groups       (EmpathyContactList *list);
+GList *              empathy_contact_list_get_contacts     (EmpathyContactList *list);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_CONTACT_LIST_H__ */
diff --git a/libempathy/empathy-contact-manager.c b/libempathy/empathy-contact-manager.c
new file mode 100644 (file)
index 0000000..15fb82e
--- /dev/null
@@ -0,0 +1,550 @@
+/* -*- 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 <string.h>
+
+#include <libtelepathy/tp-constants.h>
+
+#include "empathy-contact-manager.h"
+#include "empathy-session.h"
+#include "gossip-utils.h"
+#include "gossip-debug.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
+                      EMPATHY_TYPE_CONTACT_MANAGER, EmpathyContactManagerPriv))
+
+#define DEBUG_DOMAIN "ContactManager"
+
+struct _EmpathyContactManagerPriv {
+       GHashTable *lists;
+       gboolean    setup;
+};
+
+typedef struct {
+       const gchar *old_group;
+       const gchar *new_group;
+} ContactManagerRenameGroupData;
+
+typedef struct {
+       GossipContact *contact;
+       const gchar   *id;
+} ContactManagerFindData;
+
+static void     empathy_contact_manager_class_init   (EmpathyContactManagerClass      *klass);
+static void     empathy_contact_manager_init         (EmpathyContactManager           *manager);
+static void     contact_manager_finalize             (GObject                         *object);
+static void     contact_manager_setup_foreach        (McAccount                       *account,
+                                                     EmpathyContactList              *list,
+                                                     EmpathyContactManager           *manager);
+static gboolean contact_manager_find_foreach         (McAccount                       *account,
+                                                     EmpathyContactList              *list,
+                                                     ContactManagerFindData          *data);
+static void     contact_manager_add_account          (EmpathyContactManager           *manager,
+                                                     McAccount                       *account);
+static void     contact_manager_added_cb             (EmpathyContactList              *list,
+                                                     GossipContact                   *contact,
+                                                     EmpathyContactManager           *manager);
+static void     contact_manager_removed_cb           (EmpathyContactList              *list,
+                                                     GossipContact                   *contact,
+                                                     EmpathyContactManager           *manager);
+static void     contact_manager_destroy_cb           (EmpathyContactList              *list,
+                                                     EmpathyContactManager           *manager);
+static void     contact_manager_rename_group_foreach (McAccount                       *account,
+                                                     EmpathyContactList              *list,
+                                                     ContactManagerRenameGroupData   *data);
+static void     contact_manager_get_groups_foreach   (McAccount                       *account,
+                                                     EmpathyContactList              *list,
+                                                     GList                          **all_groups);
+static void     contact_manager_get_contacts_foreach (McAccount                       *account,
+                                                     EmpathyContactList              *list,
+                                                     GList                          **contacts);
+static void     contact_manager_status_changed_cb    (MissionControl                  *mc,
+                                                     TelepathyConnectionStatus        status,
+                                                     McPresence                       presence,
+                                                     TelepathyConnectionStatusReason  reason,
+                                                     const gchar                     *unique_name,
+                                                     EmpathyContactManager           *manager);
+
+enum {
+       CONTACT_ADDED,
+       CONTACT_REMOVED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (EmpathyContactManager, empathy_contact_manager, G_TYPE_OBJECT);
+
+static void
+empathy_contact_manager_class_init (EmpathyContactManagerClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = contact_manager_finalize;
+
+       signals[CONTACT_ADDED] =
+               g_signal_new ("contact-added",
+                             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_REMOVED] =
+               g_signal_new ("contact-removed",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__OBJECT,
+                             G_TYPE_NONE,
+                             1, GOSSIP_TYPE_CONTACT);
+
+       g_type_class_add_private (object_class, sizeof (EmpathyContactManagerPriv));
+}
+
+static void
+empathy_contact_manager_init (EmpathyContactManager *manager)
+{
+       EmpathyContactManagerPriv *priv;
+       MissionControl            *mc;
+       GSList                    *accounts, *l;
+
+       priv = GET_PRIV (manager);
+
+       priv->lists = g_hash_table_new_full (gossip_account_hash,
+                                            gossip_account_equal,
+                                            (GDestroyNotify) g_object_unref,
+                                            (GDestroyNotify) g_object_unref);
+
+       mc = empathy_session_get_mission_control ();
+
+       dbus_g_proxy_connect_signal (DBUS_G_PROXY (mc), "AccountStatusChanged",
+                                    G_CALLBACK (contact_manager_status_changed_cb),
+                                    manager, NULL);
+
+       /* Get ContactList for existing connections */
+       accounts = mission_control_get_online_connections (mc, NULL);
+       for (l = accounts; l; l = l->next) {
+               McAccount *account;
+
+               account = l->data;
+               contact_manager_add_account (manager, account);
+               
+               g_object_unref (account);
+       }
+       g_slist_free (accounts);
+}
+
+static void
+contact_manager_finalize (GObject *object)
+{
+       EmpathyContactManagerPriv *priv;
+
+       priv = GET_PRIV (object);
+
+       g_hash_table_destroy (priv->lists);
+}
+
+EmpathyContactManager *
+empathy_contact_manager_new (void)
+{
+       return g_object_new (EMPATHY_TYPE_CONTACT_MANAGER, NULL);
+}
+
+void
+empathy_contact_manager_setup (EmpathyContactManager *manager)
+{
+       EmpathyContactManagerPriv *priv;
+
+       g_return_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager));
+
+       priv = GET_PRIV (manager);
+
+       if (priv->setup) {
+               /* Already done */
+               return;
+       }
+
+       g_hash_table_foreach (priv->lists,
+                             (GHFunc) contact_manager_setup_foreach,
+                             manager);
+
+       priv->setup = TRUE;
+}
+
+EmpathyContactList *
+empathy_contact_manager_get_list (EmpathyContactManager *manager,
+                                 McAccount             *account)
+{
+       EmpathyContactManagerPriv *priv;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager), NULL);
+       g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+
+       priv = GET_PRIV (manager);
+
+       return g_hash_table_lookup (priv->lists, account);
+}
+
+GossipContact *
+empathy_contact_manager_get_own (EmpathyContactManager *manager,
+                                McAccount             *account)
+{
+       EmpathyContactManagerPriv *priv;
+       EmpathyContactList        *list;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager), NULL);
+       g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+
+       priv = GET_PRIV (manager);
+
+       list = g_hash_table_lookup (priv->lists, account);
+       
+       if (!list) {
+               return NULL;
+       }
+
+       return empathy_contact_list_get_own (list);
+}
+
+GossipContact *
+empathy_contact_manager_create (EmpathyContactManager *manager,
+                               McAccount             *account,
+                               const gchar           *id)
+{
+       EmpathyContactManagerPriv *priv;
+       EmpathyContactList        *list;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager), NULL);
+       g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+       g_return_val_if_fail (id != NULL, NULL);
+
+       priv = GET_PRIV (manager);
+
+       list = g_hash_table_lookup (priv->lists, account);
+       
+       if (!list) {
+               return NULL;
+       }
+
+       return empathy_contact_list_get_from_id (list, id);
+}
+
+GossipContact *
+empathy_contact_manager_find (EmpathyContactManager *manager,
+                             const gchar           *id)
+{
+       EmpathyContactManagerPriv *priv;
+       ContactManagerFindData     data;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager), NULL);
+       g_return_val_if_fail (id != NULL, NULL);
+
+       priv = GET_PRIV (manager);
+
+       data.contact = NULL;
+       data.id = id;
+
+       g_hash_table_find (priv->lists,
+                          (GHRFunc) contact_manager_find_foreach,
+                          &data);
+
+       return data.contact;
+}
+
+void
+empathy_contact_manager_add (EmpathyContactManager *manager,
+                            GossipContact         *contact,
+                            const gchar           *message)
+{
+       EmpathyContactManagerPriv *priv;
+       EmpathyContactList        *list;
+       McAccount                 *account;
+       guint                      handle;
+
+       g_return_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager));
+       g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+       priv = GET_PRIV (manager);
+
+       account = gossip_contact_get_account (contact);
+       handle = gossip_contact_get_handle (contact);
+       list = g_hash_table_lookup (priv->lists, account);
+
+       if (list) {
+               empathy_contact_list_add (list, handle, message);
+       }
+}
+
+void
+empathy_contact_manager_remove (EmpathyContactManager *manager,
+                               GossipContact         *contact)
+{
+       EmpathyContactManagerPriv *priv;
+       EmpathyContactList        *list;
+       McAccount                 *account;
+       guint                      handle;
+
+       g_return_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager));
+       g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+       priv = GET_PRIV (manager);
+
+       account = gossip_contact_get_account (contact);
+       handle = gossip_contact_get_handle (contact);
+       list = g_hash_table_lookup (priv->lists, account);
+
+       if (list) {
+               empathy_contact_list_remove (list, handle);
+       }
+}
+
+void
+empathy_contact_manager_rename_group (EmpathyContactManager *manager,
+                                     const gchar           *old_group,
+                                     const gchar           *new_group)
+{
+       EmpathyContactManagerPriv   *priv;
+       ContactManagerRenameGroupData  data;
+
+       g_return_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager));
+       g_return_if_fail (old_group != NULL);
+       g_return_if_fail (new_group != NULL);
+
+       priv = GET_PRIV (manager);
+
+       data.old_group = old_group;
+       data.new_group = new_group;
+
+       g_hash_table_foreach (priv->lists,
+                             (GHFunc) contact_manager_rename_group_foreach,
+                             &data);
+}
+
+GList *
+empathy_contact_manager_get_groups (EmpathyContactManager *manager)
+{
+       EmpathyContactManagerPriv *priv;
+       GList                     *groups = NULL;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager), NULL);
+
+       priv = GET_PRIV (manager);
+
+       g_hash_table_foreach (priv->lists,
+                             (GHFunc) contact_manager_get_groups_foreach,
+                             &groups);
+
+       return groups;
+}
+
+GList *
+empathy_contact_manager_get_contacts (EmpathyContactManager *manager)
+{
+       EmpathyContactManagerPriv *priv;
+       GList                     *contacts = NULL;
+
+       g_return_val_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager), NULL);
+
+       priv = GET_PRIV (manager);
+
+       g_hash_table_foreach (priv->lists,
+                             (GHFunc) contact_manager_get_contacts_foreach,
+                             &contacts);
+
+       return contacts;
+}
+
+static void
+contact_manager_setup_foreach (McAccount             *account,
+                              EmpathyContactList    *list,
+                              EmpathyContactManager *manager)
+{
+       empathy_contact_list_setup (list);
+}
+
+static gboolean
+contact_manager_find_foreach (McAccount              *account,
+                             EmpathyContactList     *list,
+                              ContactManagerFindData *data)
+{
+       data->contact = empathy_contact_list_find (list, data->id);
+       
+       if (data->contact) {
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void
+contact_manager_add_account (EmpathyContactManager *manager,
+                            McAccount             *account)
+{
+       EmpathyContactManagerPriv *priv;
+       EmpathyContactList        *list;
+
+       priv = GET_PRIV (manager);
+
+       if (g_hash_table_lookup (priv->lists, account)) {
+               return;
+       }
+
+       gossip_debug (DEBUG_DOMAIN, "Adding new account: %s",
+                     mc_account_get_display_name (account));
+
+       list = empathy_contact_list_new (account);
+       if (!list) {
+               return;
+       }
+
+       g_hash_table_insert (priv->lists, g_object_ref (account), list);
+
+       /* Connect signals */
+       g_signal_connect (list, "contact-added",
+                         G_CALLBACK (contact_manager_added_cb),
+                         manager);
+       g_signal_connect (list, "contact-removed",
+                         G_CALLBACK (contact_manager_removed_cb),
+                         manager);
+       g_signal_connect (list, "destroy",
+                         G_CALLBACK (contact_manager_destroy_cb),
+                         manager);
+
+       if (priv->setup) {
+               empathy_contact_list_setup (list);
+       }
+}
+
+static void
+contact_manager_added_cb (EmpathyContactList    *list,
+                         GossipContact         *contact,
+                         EmpathyContactManager *manager)
+{
+       g_signal_emit (manager, signals[CONTACT_ADDED], 0, contact);
+}
+
+static void
+contact_manager_removed_cb (EmpathyContactList    *list,
+                           GossipContact         *contact,
+                           EmpathyContactManager *manager)
+{
+       g_signal_emit (manager, signals[CONTACT_REMOVED], 0, contact);
+}
+
+static void
+contact_manager_destroy_cb (EmpathyContactList    *list,
+                           EmpathyContactManager *manager)
+{
+       EmpathyContactManagerPriv *priv;
+       McAccount                 *account;
+
+       priv = GET_PRIV (manager);
+
+       account = empathy_contact_list_get_account (list);
+
+       gossip_debug (DEBUG_DOMAIN, "Removing account: %s",
+                     mc_account_get_display_name (account));
+
+       /* Disconnect signals from the list */
+       g_signal_handlers_disconnect_by_func (list,
+                                             contact_manager_added_cb,
+                                             manager);
+       g_signal_handlers_disconnect_by_func (list,
+                                             contact_manager_removed_cb,
+                                             manager);
+       g_signal_handlers_disconnect_by_func (list,
+                                             contact_manager_destroy_cb,
+                                             manager);
+
+       g_hash_table_remove (priv->lists, account);
+}
+
+static void
+contact_manager_rename_group_foreach (McAccount                     *account,
+                                     EmpathyContactList            *list,
+                                     ContactManagerRenameGroupData *data)
+{
+       empathy_contact_list_rename_group (list,
+                                          data->old_group,
+                                          data->new_group);
+}
+
+static void
+contact_manager_get_groups_foreach (McAccount           *account,
+                                   EmpathyContactList  *list,
+                                   GList              **all_groups)
+{
+       GList *groups, *l;
+
+       groups = empathy_contact_list_get_groups (list);
+       for (l = groups; l; l = l->next) {
+               if (!g_list_find_custom (*all_groups,
+                                        l->data,
+                                        (GCompareFunc) strcmp)) {
+                       *all_groups = g_list_append (*all_groups,
+                                                    g_strdup (l->data));
+               }
+               g_free (l->data);
+       }
+
+       g_list_free (groups);
+}
+
+static void
+contact_manager_get_contacts_foreach (McAccount           *account,
+                                     EmpathyContactList  *list,
+                                     GList              **contacts)
+{
+       GList *l;
+
+       l = empathy_contact_list_get_contacts (list);
+       *contacts = g_list_concat (*contacts, l);
+}
+
+static void
+contact_manager_status_changed_cb (MissionControl                  *mc,
+                                  TelepathyConnectionStatus        status,
+                                  McPresence                       presence,
+                                  TelepathyConnectionStatusReason  reason,
+                                  const gchar                     *unique_name,
+                                  EmpathyContactManager           *manager)
+{
+       EmpathyContactManagerPriv *priv;
+       McAccount                 *account;
+
+       priv = GET_PRIV (manager);
+
+       if (status != TP_CONN_STATUS_CONNECTED) {
+               /* We only care about newly connected accounts */
+               return;
+       }
+
+       account = mc_account_lookup (unique_name);
+       contact_manager_add_account (manager, account);
+
+       g_object_unref (account);
+}
+
diff --git a/libempathy/empathy-contact-manager.h b/libempathy/empathy-contact-manager.h
new file mode 100644 (file)
index 0000000..ca6cb3f
--- /dev/null
@@ -0,0 +1,77 @@
+/* -*- 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.
+ */
+
+#ifndef __EMPATHY_CONTACT_MANAGER_H__
+#define __EMPATHY_CONTACT_MANAGER_H__
+
+#include <glib.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+#include "gossip-contact.h"
+#include "empathy-contact-list.h"
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_CONTACT_MANAGER         (empathy_contact_manager_get_type ())
+#define EMPATHY_CONTACT_MANAGER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_CONTACT_MANAGER, EmpathyContactManager))
+#define EMPATHY_CONTACT_MANAGER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_CONTACT_MANAGER, EmpathyContactManagerClass))
+#define EMPATHY_IS_CONTACT_MANAGER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_CONTACT_MANAGER))
+#define EMPATHY_IS_CONTACT_MANAGER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_CONTACT_MANAGER))
+#define EMPATHY_CONTACT_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_CONTACT_MANAGER, EmpathyContactManagerClass))
+
+typedef struct _EmpathyContactManager      EmpathyContactManager;
+typedef struct _EmpathyContactManagerClass EmpathyContactManagerClass;
+typedef struct _EmpathyContactManagerPriv  EmpathyContactManagerPriv;
+
+struct _EmpathyContactManager {
+       GObject      parent;
+};
+
+struct _EmpathyContactManagerClass {
+       GObjectClass parent_class;
+};
+
+GType                  empathy_contact_manager_get_type     (void) G_GNUC_CONST;
+EmpathyContactManager *empathy_contact_manager_new          (void);
+void                   empathy_contact_manager_setup        (EmpathyContactManager *manager);
+EmpathyContactList *   empathy_contact_manager_get_list     (EmpathyContactManager *manager,
+                                                            McAccount             *account);
+GossipContact *        empathy_contact_manager_get_own      (EmpathyContactManager *manager,
+                                                            McAccount             *account);
+GossipContact *        empathy_contact_manager_find         (EmpathyContactManager *manager,
+                                                            const gchar           *id);
+GossipContact *        empathy_contact_manager_create       (EmpathyContactManager *manager,
+                                                            McAccount             *account,
+                                                            const gchar           *id);
+void                   empathy_contact_manager_add          (EmpathyContactManager *manager,
+                                                            GossipContact         *contact,
+                                                            const gchar           *message);
+void                   empathy_contact_manager_remove       (EmpathyContactManager *manager,
+                                                            GossipContact         *contact);
+void                   empathy_contact_manager_rename_group (EmpathyContactManager *manager,
+                                                            const gchar           *old_group,
+                                                            const gchar           *new_group);
+GList *                empathy_contact_manager_get_groups   (EmpathyContactManager *manager);
+GList *                empathy_contact_manager_get_contacts (EmpathyContactManager *manager);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_CONTACT_MANAGER_H__ */
diff --git a/libempathy/empathy-marshal-main.c b/libempathy/empathy-marshal-main.c
new file mode 100644 (file)
index 0000000..f3ab95f
--- /dev/null
@@ -0,0 +1,2 @@
+#include "empathy-marshal.h"
+#include "empathy-marshal.c"
diff --git a/libempathy/empathy-marshal.list b/libempathy/empathy-marshal.list
new file mode 100644 (file)
index 0000000..6035b94
--- /dev/null
@@ -0,0 +1,3 @@
+VOID:POINTER,UINT,UINT,STRING
+VOID:OBJECT,UINT
+VOID:OBJECT,OBJECT
diff --git a/libempathy/empathy-session.c b/libempathy/empathy-session.c
new file mode 100644 (file)
index 0000000..fefda76
--- /dev/null
@@ -0,0 +1,161 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This library 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 library 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 library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include <glib.h>
+
+#include <libtelepathy/tp-helpers.h>
+
+#include <libmissioncontrol/mc-account-monitor.h>
+
+#include "empathy-session.h"
+#include "gossip-debug.h"
+
+#define DEBUG_DOMAIN "Session"
+
+static void session_start_mission_control (void);
+static void session_error_cb              (MissionControl   *mc,
+                                          GError           *error,
+                                          gpointer          data);
+static void session_account_enabled_cb    (McAccountMonitor *monitor,
+                                          gchar            *unique_name,
+                                          gpointer          user_data);
+static void session_service_ended_cb      (MissionControl   *mc,
+                                          gpointer          user_data);
+
+static MissionControl        *mission_control = NULL;
+static EmpathyContactManager *contact_manager = NULL;
+
+void
+empathy_session_connect (void)
+{
+       MissionControl   *mc;
+       McAccountMonitor *monitor;
+       static gboolean   started = FALSE;
+
+       if (started) {
+               return;
+       }
+
+       mc = empathy_session_get_mission_control ();
+       monitor = mc_account_monitor_new ();
+
+       g_signal_connect (monitor, "account-enabled",
+                         G_CALLBACK (session_account_enabled_cb),
+                         NULL);
+       g_signal_connect (mc, "ServiceEnded",
+                         G_CALLBACK (session_service_ended_cb),
+                         NULL);
+
+       g_object_unref (monitor);
+       session_start_mission_control ();
+
+       started = TRUE;
+}
+
+void
+empathy_session_finalize (void)
+{
+       if (mission_control) {
+               g_object_unref (mission_control);
+               mission_control = NULL;
+       }
+
+       if (contact_manager) {
+               g_object_unref (contact_manager);
+               contact_manager = NULL;
+       }
+}
+
+MissionControl *
+empathy_session_get_mission_control (void)
+{
+       if (!mission_control) {
+               mission_control = mission_control_new (tp_get_bus ());
+       }
+
+       return mission_control;
+}
+
+EmpathyContactManager *
+empathy_session_get_contact_manager (void)
+{
+       if (!contact_manager) {
+               contact_manager = empathy_contact_manager_new ();
+       }
+
+       return contact_manager;
+}
+
+static void
+session_start_mission_control (void)
+{
+       MissionControl *mc;
+       McPresence      presence;
+
+       mc = empathy_session_get_mission_control ();
+       presence = mission_control_get_presence_actual (mc, NULL);
+
+       if (presence != MC_PRESENCE_UNSET &&
+           presence != MC_PRESENCE_OFFLINE) {
+               /* MC is already running and online, nothing to do */
+               return;
+       }
+
+       gossip_debug (DEBUG_DOMAIN, "Starting Mission Control...");
+
+       /* FIXME: Save/Restore status message */
+       mission_control_set_presence (mc, MC_PRESENCE_AVAILABLE,
+                                     NULL,
+                                     (McCallback) session_error_cb,
+                                     NULL);
+
+       mission_control_connect_all_with_default_presence (mc,
+                                                          (McCallback) session_error_cb,
+                                                          NULL);
+}
+
+static void
+session_error_cb (MissionControl *mc,
+                 GError         *error,
+                 gpointer        data)
+{
+       if (error) {
+               gossip_debug (DEBUG_DOMAIN, "Error: %s", error->message);
+       }
+}
+
+static void
+session_account_enabled_cb (McAccountMonitor *monitor,
+                           gchar            *unique_name,
+                           gpointer          user_data)
+{
+       gossip_debug (DEBUG_DOMAIN, "Account enabled: %s", unique_name);
+       session_start_mission_control ();
+}
+
+static void
+session_service_ended_cb (MissionControl *mc,
+                         gpointer        user_data)
+{
+       gossip_debug (DEBUG_DOMAIN, "Mission Control stopped");
+}
+
diff --git a/libempathy/empathy-session.h b/libempathy/empathy-session.h
new file mode 100644 (file)
index 0000000..0d3a2dc
--- /dev/null
@@ -0,0 +1,38 @@
+#include <libtelepathy/tp-helpers.h>/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This library 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 library 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 library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EMPATHY_SESSION_H__
+#define __EMPATHY_SESSION_H__
+
+#include <glib.h>
+
+#include <libmissioncontrol/mission-control.h>
+#include "empathy-contact-manager.h"
+
+G_BEGIN_DECLS
+
+void                   empathy_session_connect             (void);
+void                   empathy_session_finalize            (void);
+MissionControl *       empathy_session_get_mission_control (void);
+EmpathyContactManager *empathy_session_get_contact_manager (void);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_MISSION_CONTROL_H__ */
diff --git a/libempathy/empathy-tp-chat.c b/libempathy/empathy-tp-chat.c
new file mode 100644 (file)
index 0000000..ad00711
--- /dev/null
@@ -0,0 +1,474 @@
+/* -*- 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 <libtelepathy/tp-helpers.h>
+#include <libtelepathy/tp-chan-type-text-gen.h>
+#include <libtelepathy/tp-chan-iface-chat-state-gen.h>
+
+#include "empathy-tp-chat.h"
+#include "empathy-contact-manager.h"
+#include "empathy-contact-list.h"
+#include "empathy-session.h"
+#include "empathy-marshal.h"
+#include "gossip-debug.h"
+#include "gossip-time.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
+                      EMPATHY_TYPE_TP_CHAT, EmpathyTpChatPriv))
+
+#define DEBUG_DOMAIN "TpChat"
+
+struct _EmpathyTpChatPriv {
+       EmpathyContactList *list;
+       TpChan             *tp_chan;
+       DBusGProxy         *text_iface;
+       DBusGProxy         *chat_state_iface;
+};
+
+static void empathy_tp_chat_class_init (EmpathyTpChatClass *klass);
+static void empathy_tp_chat_init       (EmpathyTpChat      *chat);
+static void tp_chat_finalize           (GObject            *object);
+static void tp_chat_destroy_cb         (TpChan             *text_chan,
+                                       EmpathyTpChat      *chat);
+static void tp_chat_received_cb        (DBusGProxy         *text_iface,
+                                       guint               message_id,
+                                       guint               timestamp,
+                                       guint               from_handle,
+                                       guint               message_type,
+                                       guint               message_flags,
+                                       gchar              *message_body,
+                                       EmpathyTpChat      *chat);
+static void tp_chat_sent_cb            (DBusGProxy         *text_iface,
+                                       guint               timestamp,
+                                       guint               message_type,
+                                       gchar              *message_body,
+                                       EmpathyTpChat      *chat);
+static void tp_chat_state_changed_cb   (DBusGProxy         *chat_state_iface,
+                                       guint               handle,
+                                       EmpathyTpChatState  state,
+                                       EmpathyTpChat      *chat);
+static void tp_chat_emit_message       (EmpathyTpChat      *chat,
+                                       guint               type,
+                                       guint               timestamp,
+                                       guint               from_handle,
+                                       const gchar        *message_body);
+
+enum {
+       MESSAGE_RECEIVED,
+       CHAT_STATE_CHANGED,
+       DESTROY,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (EmpathyTpChat, empathy_tp_chat, G_TYPE_OBJECT);
+
+static void
+empathy_tp_chat_class_init (EmpathyTpChatClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tp_chat_finalize;
+
+       signals[MESSAGE_RECEIVED] =
+               g_signal_new ("message-received",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__OBJECT,
+                             G_TYPE_NONE,
+                             1, GOSSIP_TYPE_MESSAGE);
+
+       signals[CHAT_STATE_CHANGED] =
+               g_signal_new ("chat-state-changed",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             empathy_marshal_VOID__OBJECT_UINT,
+                             G_TYPE_NONE,
+                             2, GOSSIP_TYPE_CONTACT, G_TYPE_UINT);
+
+       signals[DESTROY] =
+               g_signal_new ("destroy",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__VOID,
+                             G_TYPE_NONE,
+                             0);
+
+       g_type_class_add_private (object_class, sizeof (EmpathyTpChatPriv));
+}
+
+static void
+empathy_tp_chat_init (EmpathyTpChat *chat)
+{
+}
+
+
+static void
+tp_chat_finalize (GObject *object)
+{
+       EmpathyTpChatPriv *priv;
+       EmpathyTpChat     *chat;
+       GError            *error = NULL;
+
+       chat = EMPATHY_TP_CHAT (object);
+       priv = GET_PRIV (chat);
+
+       if (priv->tp_chan) {
+               gossip_debug (DEBUG_DOMAIN, "Closing channel...");
+
+               g_signal_handlers_disconnect_by_func (priv->tp_chan,
+                                                     tp_chat_destroy_cb,
+                                                     object);
+
+               if (!tp_chan_close (DBUS_G_PROXY (priv->tp_chan), &error)) {
+                       gossip_debug (DEBUG_DOMAIN, 
+                                     "Error closing text channel: %s",
+                                     error ? error->message : "No error given");
+                       g_clear_error (&error);
+               }
+               g_object_unref (priv->tp_chan);
+       }
+
+       if (priv->list) {
+               g_object_unref (priv->list);
+       }
+
+       G_OBJECT_CLASS (empathy_tp_chat_parent_class)->finalize (object);
+}
+
+EmpathyTpChat *
+empathy_tp_chat_new (McAccount *account,
+                    TpChan    *tp_chan)
+{
+       EmpathyTpChatPriv     *priv;
+       EmpathyTpChat         *chat;
+       EmpathyContactManager *manager;
+
+       g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+       g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
+
+       chat = g_object_new (EMPATHY_TYPE_TP_CHAT, NULL);
+       priv = GET_PRIV (chat);
+
+       manager = empathy_session_get_contact_manager ();
+       priv->list = empathy_contact_manager_get_list (manager, account);
+       priv->tp_chan = g_object_ref (tp_chan);
+       g_object_ref (priv->list);
+
+       priv->text_iface = tp_chan_get_interface (tp_chan,
+                                                 TELEPATHY_CHAN_IFACE_TEXT_QUARK);
+       priv->chat_state_iface = tp_chan_get_interface (tp_chan,
+                                                       TELEPATHY_CHAN_IFACE_CHAT_STATE_QUARK);
+
+       g_signal_connect (priv->tp_chan, "destroy",
+                         G_CALLBACK (tp_chat_destroy_cb),
+                         chat);
+       dbus_g_proxy_connect_signal (priv->text_iface, "Received",
+                                    G_CALLBACK (tp_chat_received_cb),
+                                    chat, NULL);
+       dbus_g_proxy_connect_signal (priv->text_iface, "Sent",
+                                    G_CALLBACK (tp_chat_sent_cb),
+                                    chat, NULL);
+
+       if (priv->chat_state_iface != NULL) {
+               dbus_g_proxy_connect_signal (priv->chat_state_iface,
+                                            "ChatStateChanged",
+                                            G_CALLBACK (tp_chat_state_changed_cb),
+                                            chat, NULL);
+       }
+
+       return chat;
+}
+
+EmpathyTpChat *
+empathy_tp_chat_new_with_contact (GossipContact *contact)
+{
+       EmpathyTpChat  *chat;
+       MissionControl *mc;
+       McAccount      *account;
+       TpConn         *tp_conn;
+       TpChan         *text_chan;
+       const gchar    *bus_name;
+       guint           handle;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+       mc = empathy_session_get_mission_control ();
+       account = gossip_contact_get_account (contact);
+
+       if (mission_control_get_connection_status (mc, account, NULL) != 0) {
+               /* The account is not connected, nothing to do. */
+               return NULL;
+       }
+
+       tp_conn = mission_control_get_connection (mc, account, NULL);
+       g_return_val_if_fail (tp_conn != NULL, NULL);
+       bus_name = dbus_g_proxy_get_bus_name (DBUS_G_PROXY (tp_conn));
+       handle = gossip_contact_get_handle (contact);
+
+       text_chan = tp_conn_new_channel (tp_get_bus (),
+                                        tp_conn,
+                                        bus_name,
+                                        TP_IFACE_CHANNEL_TYPE_TEXT,
+                                        TP_HANDLE_TYPE_CONTACT,
+                                        handle,
+                                        TRUE);
+
+       chat = empathy_tp_chat_new (account, text_chan);
+
+       g_object_unref (tp_conn);
+       g_object_unref (text_chan);
+
+       return chat;
+}
+
+void
+empathy_tp_chat_request_pending (EmpathyTpChat *chat)
+{
+       EmpathyTpChatPriv *priv;
+       GPtrArray         *messages_list;
+       guint              i;
+       GError            *error = NULL;
+
+       g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
+
+       priv = GET_PRIV (chat);
+
+       /* If we do this call async, don't forget to ignore Received signal
+        * until we get the answer */
+       if (!tp_chan_type_text_list_pending_messages (priv->text_iface,
+                                                     TRUE,
+                                                     &messages_list,
+                                                     &error)) {
+               gossip_debug (DEBUG_DOMAIN, 
+                             "Error retrieving pending messages: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+               return;
+       }
+
+       for (i = 0; i < messages_list->len; i++) {
+               GValueArray *message_struct;
+               const gchar *message_body;
+               guint        message_id;
+               guint        timestamp;
+               guint        from_handle;
+               guint        message_type;
+               guint        message_flags;
+
+               message_struct = g_ptr_array_index (messages_list, i);
+
+               message_id = g_value_get_uint (g_value_array_get_nth (message_struct, 0));
+               timestamp = g_value_get_uint (g_value_array_get_nth (message_struct, 1));
+               from_handle = g_value_get_uint (g_value_array_get_nth (message_struct, 2));
+               message_type = g_value_get_uint (g_value_array_get_nth (message_struct, 3));
+               message_flags = g_value_get_uint (g_value_array_get_nth (message_struct, 4));
+               message_body = g_value_get_string (g_value_array_get_nth (message_struct, 5));
+
+               gossip_debug (DEBUG_DOMAIN, "Message pending: %s", message_body);
+
+               tp_chat_emit_message (chat,
+                                     message_type,
+                                     timestamp,
+                                     from_handle,
+                                     message_body);
+
+               g_value_array_free (message_struct);
+       }
+
+       g_ptr_array_free (messages_list, TRUE);
+}
+
+void
+empathy_tp_chat_send (EmpathyTpChat *chat,
+                     GossipMessage *message)
+{
+       EmpathyTpChatPriv *priv;
+       const gchar       *message_body;
+       GossipMessageType  message_type;
+       GError            *error = NULL;
+
+       g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
+       g_return_if_fail (GOSSIP_IS_MESSAGE (message));
+
+       priv = GET_PRIV (chat);
+
+       message_body = gossip_message_get_body (message);
+       message_type = gossip_message_get_type (message);
+
+       gossip_debug (DEBUG_DOMAIN, "Sending message: %s", message_body);
+       if (!tp_chan_type_text_send (priv->text_iface,
+                                    message_type,
+                                    message_body,
+                                    &error)) {
+               gossip_debug (DEBUG_DOMAIN, 
+                             "Send Error: %s", 
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+       }
+}
+
+void
+empathy_tp_chat_send_state (EmpathyTpChat      *chat,
+                           EmpathyTpChatState  state)
+{
+       EmpathyTpChatPriv *priv;
+       GError            *error = NULL;
+
+       g_return_if_fail (EMPATHY_IS_TP_CHAT (chat));
+
+       priv = GET_PRIV (chat);
+
+       if (priv->chat_state_iface) {
+               gossip_debug (DEBUG_DOMAIN, "Set state: %d", state);
+               if (!tp_chan_iface_chat_state_set_chat_state (priv->chat_state_iface,
+                                                             state,
+                                                             &error)) {
+                       gossip_debug (DEBUG_DOMAIN,
+                                     "Set Chat State Error: %s",
+                                     error ? error->message : "No error given");
+                       g_clear_error (&error);
+               }
+       }
+}
+
+static void
+tp_chat_destroy_cb (TpChan        *text_chan,
+                   EmpathyTpChat *chat)
+{
+       EmpathyTpChatPriv *priv;
+
+       priv = GET_PRIV (chat);
+
+       gossip_debug (DEBUG_DOMAIN, "Channel destroyed");
+
+       g_object_unref  (priv->tp_chan);
+       priv->tp_chan = NULL;
+       priv->text_iface = NULL;
+       priv->chat_state_iface = NULL;
+
+       g_signal_emit (chat, signals[DESTROY], 0);
+}
+
+static void
+tp_chat_received_cb (DBusGProxy    *text_iface,
+                    guint          message_id,
+                    guint          timestamp,
+                    guint          from_handle,
+                    guint          message_type,
+                    guint          message_flags,
+                    gchar         *message_body,
+                    EmpathyTpChat *chat)
+{
+       EmpathyTpChatPriv *priv;
+       GArray            *message_ids;
+
+       priv = GET_PRIV (chat);
+
+       gossip_debug (DEBUG_DOMAIN, "Message received: %s", message_body);
+
+       tp_chat_emit_message (chat,
+                             message_type,
+                             timestamp,
+                             from_handle,
+                             message_body);
+
+       message_ids = g_array_new (FALSE, FALSE, sizeof (guint));
+       g_array_append_val (message_ids, message_id);
+       tp_chan_type_text_acknowledge_pending_messages (priv->text_iface,
+                                                       message_ids, NULL);
+       g_array_free (message_ids, TRUE);
+}
+
+static void
+tp_chat_sent_cb (DBusGProxy    *text_iface,
+                guint          timestamp,
+                guint          message_type,
+                gchar         *message_body,
+                EmpathyTpChat *chat)
+{
+       gossip_debug (DEBUG_DOMAIN, "Message sent: %s", message_body);
+
+       tp_chat_emit_message (chat,
+                             message_type,
+                             timestamp,
+                             0,
+                             message_body);
+}
+
+static void
+tp_chat_state_changed_cb (DBusGProxy         *chat_state_iface,
+                         guint               handle,
+                         EmpathyTpChatState  state,
+                         EmpathyTpChat      *chat)
+{
+       EmpathyTpChatPriv *priv;
+       GossipContact     *contact;
+
+       priv = GET_PRIV (chat);
+
+       contact = empathy_contact_list_get_from_handle (priv->list, handle);
+
+       g_signal_emit (chat, signals[CHAT_STATE_CHANGED], 0, contact, state);
+
+       g_object_unref (contact);
+}
+
+static void
+tp_chat_emit_message (EmpathyTpChat *chat,
+                     guint          type,
+                     guint          timestamp,
+                     guint          from_handle,
+                     const gchar   *message_body)
+{
+       EmpathyTpChatPriv *priv;
+       GossipMessage     *message;
+       GossipContact     *sender;
+
+       priv = GET_PRIV (chat);
+
+       if (from_handle == 0) {
+               sender = empathy_contact_list_get_own (priv->list);
+               g_object_ref (sender);
+       } else {
+               sender = empathy_contact_list_get_from_handle (priv->list,
+                                                              from_handle);
+       }
+
+       message = gossip_message_new (message_body);
+       gossip_message_set_type (message, type);
+       gossip_message_set_sender (message, sender);
+       gossip_message_set_timestamp (message, (GossipTime) timestamp);
+
+       g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message);
+
+       g_object_unref (message);
+       g_object_unref (sender);
+}
+
diff --git a/libempathy/empathy-tp-chat.h b/libempathy/empathy-tp-chat.h
new file mode 100644 (file)
index 0000000..c64bbd7
--- /dev/null
@@ -0,0 +1,75 @@
+/* -*- 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.
+ */
+
+#ifndef __EMPATHY_TP_CHAT_H__
+#define __EMPATHY_TP_CHAT_H__
+
+#include <glib.h>
+
+#include <libtelepathy/tp-chan.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+#include "gossip-message.h"
+#include "gossip-contact.h"
+
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_TP_CHAT         (empathy_tp_chat_get_type ())
+#define EMPATHY_TP_CHAT(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_TP_CHAT, EmpathyTpChat))
+#define EMPATHY_TP_CHAT_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_TP_CHAT, EmpathyTpChatClass))
+#define EMPATHY_IS_TP_CHAT(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_TP_CHAT))
+#define EMPATHY_IS_TP_CHAT_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_TP_CHAT))
+#define EMPATHY_TP_CHAT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_TP_CHAT, EmpathyTpChatClass))
+
+typedef struct _EmpathyTpChat      EmpathyTpChat;
+typedef struct _EmpathyTpChatClass EmpathyTpChatClass;
+typedef struct _EmpathyTpChatPriv  EmpathyTpChatPriv;
+
+struct _EmpathyTpChat {
+       GObject      parent;
+};
+
+struct _EmpathyTpChatClass {
+       GObjectClass parent_class;
+};
+
+typedef enum {
+    EMPATHY_TP_CHAT_STATE_GONE,
+    EMPATHY_TP_CHAT_STATE_INACTIVE,
+    EMPATHY_TP_CHAT_STATE_ACTIVE,
+    EMPATHY_TP_CHAT_STATE_PAUSED,
+    EMPATHY_TP_CHAT_STATE_COMPOSING
+} EmpathyTpChatState;
+
+GType          empathy_tp_chat_get_type         (void) G_GNUC_CONST;
+EmpathyTpChat *empathy_tp_chat_new              (McAccount          *account,
+                                                TpChan             *tp_chan);
+EmpathyTpChat *empathy_tp_chat_new_with_contact (GossipContact      *contact);
+void           empathy_tp_chat_request_pending  (EmpathyTpChat      *chat);
+void           empathy_tp_chat_send             (EmpathyTpChat      *chat,
+                                                GossipMessage      *message);
+void           empathy_tp_chat_send_state       (EmpathyTpChat      *chat,
+                                                EmpathyTpChatState  state);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_TP_CHAT_H__ */
diff --git a/libempathy/gossip-avatar.c b/libempathy/gossip-avatar.c
new file mode 100644 (file)
index 0000000..5c17a51
--- /dev/null
@@ -0,0 +1,86 @@
+/* -*- 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: Martyn Russell <martyn@imendio.com>
+ *          Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include "config.h"
+
+#include "gossip-avatar.h"
+
+#define DEBUG_DOMAIN "Avatar"
+
+GType
+gossip_avatar_get_gtype (void)
+{
+       static GType type_id = 0;
+
+       if (!type_id) {
+               type_id = g_boxed_type_register_static ("GossipAvatar",
+                                                       (GBoxedCopyFunc) gossip_avatar_ref,
+                                                       (GBoxedFreeFunc) gossip_avatar_unref);
+       }
+
+       return type_id;
+}
+
+GossipAvatar *
+gossip_avatar_new (guchar *data,
+                  gsize   len,
+                  gchar  *format)
+{
+       GossipAvatar *avatar;
+
+       g_return_val_if_fail (data != NULL, NULL);
+       g_return_val_if_fail (len > 0, NULL);
+       g_return_val_if_fail (format != NULL, NULL);
+
+       avatar = g_slice_new0 (GossipAvatar);
+       avatar->data = g_memdup (data, len);
+       avatar->len = len;
+       avatar->format = g_strdup (format);
+       avatar->refcount = 1;
+
+       return avatar;
+}
+
+void
+gossip_avatar_unref (GossipAvatar *avatar)
+{
+       g_return_if_fail (avatar != NULL);
+
+       avatar->refcount--;
+       if (avatar->refcount == 0) {
+               g_free (avatar->data);
+               g_free (avatar->format);
+               g_slice_free (GossipAvatar, avatar);
+       }
+}
+
+GossipAvatar *
+gossip_avatar_ref (GossipAvatar *avatar)
+{
+       g_return_val_if_fail (avatar != NULL, NULL);
+
+       avatar->refcount++;
+
+       return avatar;
+}
+
diff --git a/libempathy/gossip-avatar.h b/libempathy/gossip-avatar.h
new file mode 100644 (file)
index 0000000..44fa9ab
--- /dev/null
@@ -0,0 +1,48 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 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.
+ */
+
+#ifndef __GOSSIP_AVATAR_H__
+#define __GOSSIP_AVATAR_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_AVATAR (gossip_avatar_get_gtype ())
+
+typedef struct _GossipAvatar GossipAvatar;
+
+struct _GossipAvatar {
+       guchar *data;
+       gsize   len;
+       gchar  *format;
+       guint   refcount;
+};
+
+GType          gossip_avatar_get_gtype (void) G_GNUC_CONST;
+GossipAvatar * gossip_avatar_new       (guchar       *avatar,
+                                       gsize         len,
+                                       gchar        *format);
+GossipAvatar * gossip_avatar_ref       (GossipAvatar *avatar);
+void           gossip_avatar_unref     (GossipAvatar *avatar);
+
+G_END_DECLS
+
+#endif /*  __GOSSIP_AVATAR_H__ */
diff --git a/libempathy/gossip-conf.c b/libempathy/gossip-conf.c
new file mode 100644 (file)
index 0000000..9625a70
--- /dev/null
@@ -0,0 +1,382 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 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 <gconf/gconf-client.h>
+
+#include "gossip-conf.h"
+#include "gossip-debug.h"
+
+#define DEBUG_DOMAIN "Config"
+
+#define GOSSIP_CONF_ROOT       "/apps/empathy"
+#define DESKTOP_INTERFACE_ROOT "/desktop/gnome/interface"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONF, GossipConfPriv))
+
+typedef struct {
+       GConfClient *gconf_client;
+} GossipConfPriv;
+
+typedef struct {
+       GossipConf           *conf;
+       GossipConfNotifyFunc  func;
+       gpointer               user_data;
+} GossipConfNotifyData;
+
+static void conf_finalize (GObject *object);
+
+G_DEFINE_TYPE (GossipConf, gossip_conf, G_TYPE_OBJECT);
+
+static GossipConf *global_conf = NULL;
+
+static void
+gossip_conf_class_init (GossipConfClass *class)
+{
+       GObjectClass *object_class;
+
+       object_class = G_OBJECT_CLASS (class);
+
+       object_class->finalize = conf_finalize;
+
+       g_type_class_add_private (object_class, sizeof (GossipConfPriv));
+}
+
+static void
+gossip_conf_init (GossipConf *conf)
+{
+       GossipConfPriv *priv;
+
+       priv = GET_PRIV (conf);
+
+       priv->gconf_client = gconf_client_get_default ();
+
+       gconf_client_add_dir (priv->gconf_client,
+                             GOSSIP_CONF_ROOT,
+                             GCONF_CLIENT_PRELOAD_ONELEVEL,
+                             NULL);
+       gconf_client_add_dir (priv->gconf_client,
+                             DESKTOP_INTERFACE_ROOT,
+                             GCONF_CLIENT_PRELOAD_NONE,
+                             NULL);
+}
+
+static void
+conf_finalize (GObject *object)
+{
+       GossipConfPriv *priv;
+
+       priv = GET_PRIV (object);
+
+       gconf_client_remove_dir (priv->gconf_client,
+                                GOSSIP_CONF_ROOT,
+                                NULL);
+       gconf_client_remove_dir (priv->gconf_client,
+                                DESKTOP_INTERFACE_ROOT,
+                                NULL);
+
+       g_object_unref (priv->gconf_client);
+
+       G_OBJECT_CLASS (gossip_conf_parent_class)->finalize (object);
+}
+
+GossipConf *
+gossip_conf_get (void)
+{
+       if (!global_conf) {
+               global_conf = g_object_new (GOSSIP_TYPE_CONF, NULL);
+       }
+
+       return global_conf;
+}
+
+void
+gossip_conf_shutdown (void)
+{
+       if (global_conf) {
+               g_object_unref (global_conf);
+               global_conf = NULL;
+       }
+}
+
+gboolean
+gossip_conf_set_int (GossipConf  *conf,
+                    const gchar *key,
+                    gint         value)
+{
+       GossipConfPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+       gossip_debug (DEBUG_DOMAIN, "Setting int:'%s' to %d", key, value);
+
+       priv = GET_PRIV (conf);
+
+       return gconf_client_set_int (priv->gconf_client,
+                                    key,
+                                    value,
+                                    NULL);
+}
+
+gboolean
+gossip_conf_get_int (GossipConf  *conf,
+                    const gchar *key,
+                    gint        *value)
+{
+       GossipConfPriv *priv;
+       GError          *error = NULL;
+
+       *value = 0;
+
+       g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+       g_return_val_if_fail (value != NULL, FALSE);
+
+       priv = GET_PRIV (conf);
+
+       *value = gconf_client_get_int (priv->gconf_client,
+                                      key,
+                                      &error);
+
+       gossip_debug (DEBUG_DOMAIN, "Getting int:'%s' (=%d), error:'%s'",
+                     key, *value, error ? error->message : "None");
+
+       if (error) {
+               g_error_free (error);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+gboolean
+gossip_conf_set_bool (GossipConf  *conf,
+                     const gchar *key,
+                     gboolean     value)
+{
+       GossipConfPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+       gossip_debug (DEBUG_DOMAIN, "Setting bool:'%s' to %d ---> %s",
+                     key, value, value ? "true" : "false");
+
+       priv = GET_PRIV (conf);
+
+       return gconf_client_set_bool (priv->gconf_client,
+                                     key,
+                                     value,
+                                     NULL);
+}
+
+gboolean
+gossip_conf_get_bool (GossipConf  *conf,
+                     const gchar *key,
+                     gboolean    *value)
+{
+       GossipConfPriv *priv;
+       GError          *error = NULL;
+
+       *value = FALSE;
+
+       g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+       g_return_val_if_fail (value != NULL, FALSE);
+
+       priv = GET_PRIV (conf);
+
+       *value = gconf_client_get_bool (priv->gconf_client,
+                                       key,
+                                       &error);
+
+       gossip_debug (DEBUG_DOMAIN, "Getting bool:'%s' (=%d ---> %s), error:'%s'",
+                     key, *value, *value ? "true" : "false",
+                     error ? error->message : "None");
+
+       if (error) {
+               g_error_free (error);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+gboolean
+gossip_conf_set_string (GossipConf  *conf,
+                       const gchar *key,
+                       const gchar *value)
+{
+       GossipConfPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+       gossip_debug (DEBUG_DOMAIN, "Setting string:'%s' to '%s'",
+                     key, value);
+
+       priv = GET_PRIV (conf);
+
+       return gconf_client_set_string (priv->gconf_client,
+                                       key,
+                                       value,
+                                       NULL);
+}
+
+gboolean
+gossip_conf_get_string (GossipConf   *conf,
+                       const gchar  *key,
+                       gchar       **value)
+{
+       GossipConfPriv *priv;
+       GError          *error = NULL;
+
+       *value = NULL;
+
+       g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+       priv = GET_PRIV (conf);
+
+       *value = gconf_client_get_string (priv->gconf_client,
+                                         key,
+                                         &error);
+
+       gossip_debug (DEBUG_DOMAIN, "Getting string:'%s' (='%s'), error:'%s'",
+                     key, *value, error ? error->message : "None");
+
+       if (error) {
+               g_error_free (error);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+gboolean
+gossip_conf_set_string_list (GossipConf  *conf,
+                            const gchar *key,
+                            GSList      *value)
+{
+       GossipConfPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+       priv = GET_PRIV (conf);
+
+       return gconf_client_set_list (priv->gconf_client,
+                                     key,
+                                     GCONF_VALUE_STRING,
+                                     value,
+                                     NULL);
+}
+
+gboolean
+gossip_conf_get_string_list (GossipConf   *conf,
+                            const gchar  *key,
+                            GSList      **value)
+{
+       GossipConfPriv *priv;
+       GError          *error = NULL;
+
+       *value = NULL;
+
+       g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+       priv = GET_PRIV (conf);
+
+       *value = gconf_client_get_list (priv->gconf_client,
+                                       key,
+                                       GCONF_VALUE_STRING,
+                                       &error);
+       if (error) {
+               g_error_free (error);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void
+conf_notify_data_free (GossipConfNotifyData *data)
+{
+       g_object_unref (data->conf);
+       g_slice_free (GossipConfNotifyData, data);
+}
+
+static void
+conf_notify_func (GConfClient *client,
+                 guint        id,
+                 GConfEntry  *entry,
+                 gpointer     user_data)
+{
+       GossipConfNotifyData *data;
+
+       data = user_data;
+
+       data->func (data->conf,
+                   gconf_entry_get_key (entry),
+                   data->user_data);
+}
+
+guint
+gossip_conf_notify_add (GossipConf           *conf,
+                       const gchar          *key,
+                       GossipConfNotifyFunc func,
+                       gpointer              user_data)
+{
+       GossipConfPriv       *priv;
+       guint                  id;
+       GossipConfNotifyData *data;
+
+       g_return_val_if_fail (GOSSIP_IS_CONF (conf), 0);
+
+       priv = GET_PRIV (conf);
+
+       data = g_slice_new (GossipConfNotifyData);
+       data->func = func;
+       data->user_data = user_data;
+       data->conf = g_object_ref (conf);
+
+       id = gconf_client_notify_add (priv->gconf_client,
+                                     key,
+                                     conf_notify_func,
+                                     data,
+                                     (GFreeFunc) conf_notify_data_free,
+                                     NULL);
+
+       return id;
+}
+
+gboolean
+gossip_conf_notify_remove (GossipConf *conf,
+                          guint       id)
+{
+       GossipConfPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONF (conf), FALSE);
+
+       priv = GET_PRIV (conf);
+
+       gconf_client_notify_remove (priv->gconf_client, id);
+
+       return TRUE;
+}
+
diff --git a/libempathy/gossip-conf.h b/libempathy/gossip-conf.h
new file mode 100644 (file)
index 0000000..35fdfb9
--- /dev/null
@@ -0,0 +1,87 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 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.
+ */
+
+#ifndef __GOSSIP_CONF_H__
+#define __GOSSIP_CONF_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CONF         (gossip_conf_get_type ())
+#define GOSSIP_CONF(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CONF, GossipConf))
+#define GOSSIP_CONF_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_CONF, GossipConfClass))
+#define GOSSIP_IS_CONF(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CONF))
+#define GOSSIP_IS_CONF_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CONF))
+#define GOSSIP_CONF_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CONF, GossipConfClass))
+
+typedef struct _GossipConf      GossipConf;
+typedef struct _GossipConfClass GossipConfClass;
+
+struct _GossipConf  {
+       GObject parent;
+};
+
+struct _GossipConfClass {
+       GObjectClass parent_class;
+};
+
+typedef void (*GossipConfNotifyFunc) (GossipConf  *conf, 
+                                     const gchar *key,
+                                     gpointer     user_data);
+
+GType       gossip_conf_get_type        (void) G_GNUC_CONST;
+GossipConf *gossip_conf_get             (void);
+void        gossip_conf_shutdown        (void);
+guint       gossip_conf_notify_add      (GossipConf            *conf,
+                                        const gchar           *key,
+                                        GossipConfNotifyFunc   func,
+                                        gpointer               data);
+gboolean    gossip_conf_notify_remove   (GossipConf            *conf,
+                                        guint                  id);
+gboolean    gossip_conf_set_int         (GossipConf            *conf,
+                                        const gchar           *key,
+                                        gint                   value);
+gboolean    gossip_conf_get_int         (GossipConf            *conf,
+                                        const gchar           *key,
+                                        gint                  *value);
+gboolean    gossip_conf_set_bool        (GossipConf            *conf,
+                                        const gchar           *key,
+                                        gboolean               value);
+gboolean    gossip_conf_get_bool        (GossipConf            *conf,
+                                        const gchar           *key,
+                                        gboolean              *value);
+gboolean    gossip_conf_set_string      (GossipConf            *conf,
+                                        const gchar           *key,
+                                        const gchar           *value);
+gboolean    gossip_conf_get_string      (GossipConf            *conf,
+                                        const gchar           *key,
+                                        gchar                **value);
+gboolean    gossip_conf_set_string_list (GossipConf            *conf,
+                                        const gchar           *key,
+                                        GSList                *value);
+gboolean    gossip_conf_get_string_list (GossipConf            *conf,
+                                        const gchar           *key,
+                                        GSList              **value);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CONF_H__ */
+
diff --git a/libempathy/gossip-contact.c b/libempathy/gossip-contact.c
new file mode 100644 (file)
index 0000000..6950c89
--- /dev/null
@@ -0,0 +1,815 @@
+/* -*- 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>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "gossip-contact.h"
+#include "gossip-utils.h"
+#include "gossip-debug.h"
+
+#define DEBUG_DOMAIN "Contact"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT, GossipContactPriv))
+
+typedef struct _GossipContactPriv GossipContactPriv;
+
+struct _GossipContactPriv {
+       gchar              *id;
+       gchar              *name;
+       guint               handle;
+       GList              *presences;
+       GList              *groups;
+       GossipSubscription  subscription;
+       GossipAvatar       *avatar;
+       McAccount          *account;
+};
+
+static void contact_class_init    (GossipContactClass *class);
+static void contact_init          (GossipContact      *contact);
+static void contact_finalize      (GObject            *object);
+static void contact_get_property  (GObject            *object,
+                                  guint               param_id,
+                                  GValue             *value,
+                                  GParamSpec         *pspec);
+static void contact_set_property  (GObject            *object,
+                                  guint               param_id,
+                                  const GValue       *value,
+                                  GParamSpec         *pspec);
+static void contact_set_presences (GossipContact      *contact,
+                                  GList              *presences);
+
+enum {
+       PROP_0,
+       PROP_NAME,
+       PROP_ID,
+       PROP_PRESENCES,
+       PROP_GROUPS,
+       PROP_SUBSCRIPTION,
+       PROP_AVATAR,
+       PROP_HANDLE,
+       PROP_ACCOUNT
+};
+
+static gpointer parent_class = NULL;
+
+GType
+gossip_contact_get_gtype (void)
+{
+       static GType type = 0;
+
+       if (!type) {
+               static const GTypeInfo info = {
+                       sizeof (GossipContactClass),
+                       NULL, /* base_init */
+                       NULL, /* base_finalize */
+                       (GClassInitFunc) contact_class_init,
+                       NULL, /* class_finalize */
+                       NULL, /* class_data */
+                       sizeof (GossipContact),
+                       0,    /* n_preallocs */
+                       (GInstanceInitFunc) contact_init
+               };
+
+               type = g_type_register_static (G_TYPE_OBJECT,
+                                              "GossipContact",
+                                              &info, 0);
+       }
+
+       return type;
+}
+
+static void
+contact_class_init (GossipContactClass *class)
+{
+       GObjectClass *object_class;
+
+       object_class = G_OBJECT_CLASS (class);
+       parent_class = g_type_class_peek_parent (class);
+
+       object_class->finalize     = contact_finalize;
+       object_class->get_property = contact_get_property;
+       object_class->set_property = contact_set_property;
+
+       g_object_class_install_property (object_class,
+                                        PROP_NAME,
+                                        g_param_spec_string ("name",
+                                                             "Contact Name",
+                                                             "The name of the contact",
+                                                             NULL,
+                                                             G_PARAM_READWRITE));
+
+       g_object_class_install_property (object_class,
+                                        PROP_ID,
+                                        g_param_spec_string ("id",
+                                                             "Contact id",
+                                                             "String identifying contact",
+                                                             NULL,
+                                                             G_PARAM_READWRITE));
+
+       g_object_class_install_property (object_class,
+                                        PROP_PRESENCES,
+                                        g_param_spec_pointer ("presences",
+                                                              "Contact presences",
+                                                              "Presences of contact",
+                                                              G_PARAM_READWRITE));
+
+       g_object_class_install_property (object_class,
+                                        PROP_GROUPS,
+                                        g_param_spec_pointer ("groups",
+                                                              "Contact groups",
+                                                              "Groups of contact",
+                                                              G_PARAM_READWRITE));
+
+       g_object_class_install_property (object_class,
+                                        PROP_SUBSCRIPTION,
+                                        g_param_spec_int ("subscription",
+                                                          "Contact Subscription",
+                                                          "The subscription status of the contact",
+                                                          GOSSIP_SUBSCRIPTION_NONE,
+                                                          GOSSIP_SUBSCRIPTION_BOTH,
+                                                          GOSSIP_SUBSCRIPTION_NONE,
+                                                          G_PARAM_READWRITE));
+
+       g_object_class_install_property (object_class,
+                                        PROP_AVATAR,
+                                        g_param_spec_boxed ("avatar",
+                                                            "Avatar image",
+                                                            "The avatar image",
+                                                            GOSSIP_TYPE_AVATAR,
+                                                            G_PARAM_READWRITE));
+
+       g_object_class_install_property (object_class,
+                                        PROP_ACCOUNT,
+                                        g_param_spec_object ("account",
+                                                             "Contact Account",
+                                                             "The account associated with the contact",
+                                                             MC_TYPE_ACCOUNT,
+                                                             G_PARAM_READWRITE));
+
+       g_object_class_install_property (object_class,
+                                        PROP_HANDLE,
+                                        g_param_spec_uint ("handle",
+                                                           "Contact Handle",
+                                                           "The handle of the contact",
+                                                           0,
+                                                           G_MAXUINT,
+                                                           0,
+                                                           G_PARAM_READWRITE));
+
+       g_type_class_add_private (object_class, sizeof (GossipContactPriv));
+}
+
+static void
+contact_init (GossipContact *contact)
+{
+       GossipContactPriv *priv;
+
+       priv = GET_PRIV (contact);
+
+       priv->name        = NULL;
+       priv->id          = NULL;
+       priv->presences   = NULL;
+       priv->account     = NULL;
+       priv->groups      = NULL;
+       priv->avatar      = NULL;
+       priv->handle      = 0;
+}
+
+static void
+contact_finalize (GObject *object)
+{
+       GossipContactPriv *priv;
+
+       priv = GET_PRIV (object);
+
+       gossip_debug (DEBUG_DOMAIN, "finalize: %p", object);
+
+       g_free (priv->name);
+       g_free (priv->id);
+
+       if (priv->avatar) {
+               gossip_avatar_unref (priv->avatar);
+       }
+
+       if (priv->presences) {
+               g_list_foreach (priv->presences, (GFunc) g_object_unref, NULL);
+               g_list_free (priv->presences);
+       }
+
+       if (priv->groups) {
+               g_list_foreach (priv->groups, (GFunc) g_free, NULL);
+               g_list_free (priv->groups);
+       }
+
+       if (priv->account) {
+               g_object_unref (priv->account);
+       }
+
+       (G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+contact_get_property (GObject    *object,
+                     guint       param_id,
+                     GValue     *value,
+                     GParamSpec *pspec)
+{
+       GossipContactPriv *priv;
+
+       priv = GET_PRIV (object);
+
+       switch (param_id) {
+       case PROP_NAME:
+               g_value_set_string (value,
+                                   gossip_contact_get_name (GOSSIP_CONTACT (object)));
+               break;
+       case PROP_ID:
+               g_value_set_string (value,
+                                   gossip_contact_get_id (GOSSIP_CONTACT (object)));
+               break;
+       case PROP_PRESENCES:
+               g_value_set_pointer (value, priv->presences);
+               break;
+       case PROP_GROUPS:
+               g_value_set_pointer (value, priv->groups);
+               break;
+       case PROP_SUBSCRIPTION:
+               g_value_set_int (value, priv->subscription);
+               break;
+       case PROP_AVATAR:
+               g_value_set_boxed (value, priv->avatar);
+               break;
+       case PROP_ACCOUNT:
+               g_value_set_object (value, priv->account);
+               break;
+       case PROP_HANDLE:
+               g_value_set_uint (value, priv->handle);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       };
+}
+
+static void
+contact_set_property (GObject      *object,
+                     guint         param_id,
+                     const GValue *value,
+                     GParamSpec   *pspec)
+{
+       GossipContactPriv *priv;
+
+       priv = GET_PRIV (object);
+
+       switch (param_id) {
+       case PROP_NAME:
+               gossip_contact_set_name (GOSSIP_CONTACT (object),
+                                        g_value_get_string (value));
+               break;
+       case PROP_ID:
+               gossip_contact_set_id (GOSSIP_CONTACT (object),
+                                      g_value_get_string (value));
+               break;
+       case PROP_PRESENCES:
+               contact_set_presences (GOSSIP_CONTACT (object),
+                                      g_value_get_pointer (value));
+               break;
+       case PROP_GROUPS:
+               gossip_contact_set_groups (GOSSIP_CONTACT (object),
+                                          g_value_get_pointer (value));
+               break;
+       case PROP_SUBSCRIPTION:
+               gossip_contact_set_subscription (GOSSIP_CONTACT (object),
+                                                g_value_get_int (value));
+               break;
+       case PROP_AVATAR:
+               gossip_contact_set_avatar (GOSSIP_CONTACT (object),
+                                          g_value_get_boxed (value));
+               break;
+       case PROP_ACCOUNT:
+               gossip_contact_set_account (GOSSIP_CONTACT (object),
+                                           MC_ACCOUNT (g_value_get_object (value)));
+               break;
+       case PROP_HANDLE:
+               gossip_contact_set_handle (GOSSIP_CONTACT (object),
+                                          g_value_get_uint (value));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       };
+}
+
+GossipContact *
+gossip_contact_new (McAccount *account)
+{
+       return g_object_new (GOSSIP_TYPE_CONTACT,
+                            "account", account,
+                            NULL);
+}
+
+GossipContact *
+gossip_contact_new_full (McAccount *account,
+                        const gchar   *id,
+                        const gchar   *name)
+{
+       return g_object_new (GOSSIP_TYPE_CONTACT,
+                            "account", account,
+                            "name", name,
+                            "id", id,
+                            NULL);
+}
+
+const gchar *
+gossip_contact_get_id (GossipContact *contact)
+{
+       GossipContactPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), "");
+
+       priv = GET_PRIV (contact);
+
+       if (priv->id) {
+               return priv->id;
+       }
+
+       return "";
+}
+
+const gchar *
+gossip_contact_get_name (GossipContact *contact)
+{
+       GossipContactPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), "");
+
+       priv = GET_PRIV (contact);
+
+       if (priv->name == NULL) {
+               return gossip_contact_get_id (contact);
+       }
+
+       return priv->name;
+}
+
+GossipAvatar *
+gossip_contact_get_avatar (GossipContact *contact)
+{
+       GossipContactPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+       priv = GET_PRIV (contact);
+
+       return priv->avatar;
+}
+
+McAccount *
+gossip_contact_get_account (GossipContact *contact)
+{
+       GossipContactPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+       priv = GET_PRIV (contact);
+
+       return priv->account;
+}
+
+GossipPresence *
+gossip_contact_get_active_presence (GossipContact *contact)
+{
+       GossipContactPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+       priv = GET_PRIV (contact);
+
+       if (priv->presences) {
+               /* Highest priority of the presences is first */
+               return GOSSIP_PRESENCE (priv->presences->data);
+       }
+
+       return NULL;
+}
+
+GossipPresence *
+gossip_contact_get_presence_for_resource (GossipContact *contact,
+                                         const gchar   *resource)
+{
+       GossipContactPriv *priv;
+       GList             *l;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+       g_return_val_if_fail (resource != NULL, NULL);
+
+       priv = GET_PRIV (contact);
+
+       for (l = priv->presences; l; l = l->next) {
+               const gchar *p_res;
+
+               p_res = gossip_presence_get_resource (GOSSIP_PRESENCE (l->data));
+               if (p_res && strcmp (resource, p_res) == 0) {
+                       return GOSSIP_PRESENCE (l->data);
+               }
+       }
+
+       return NULL;
+}
+
+GList *
+gossip_contact_get_presence_list (GossipContact *contact)
+{
+       GossipContactPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+       priv = GET_PRIV (contact);
+
+       return priv->presences;
+}
+
+GList *
+gossip_contact_get_groups (GossipContact *contact)
+{
+       GossipContactPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+       priv = GET_PRIV (contact);
+
+       return priv->groups;
+}
+
+GossipSubscription
+gossip_contact_get_subscription (GossipContact *contact)
+{
+       GossipContactPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact),
+                             GOSSIP_SUBSCRIPTION_NONE);
+
+       priv = GET_PRIV (contact);
+
+       return priv->subscription;
+}
+
+guint
+gossip_contact_get_handle (GossipContact *contact)
+{
+       GossipContactPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), 0);
+
+       priv = GET_PRIV (contact);
+
+       return priv->handle;
+}
+
+void
+gossip_contact_set_id (GossipContact *contact,
+                      const gchar   *id)
+{
+       GossipContactPriv *priv;
+
+       g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+       g_return_if_fail (id != NULL);
+
+       priv = GET_PRIV (contact);
+
+       g_free (priv->id);
+       priv->id = g_strdup (id);
+
+       g_object_notify (G_OBJECT (contact), "id");
+}
+
+void
+gossip_contact_set_name (GossipContact *contact,
+                        const gchar   *name)
+{
+       GossipContactPriv *priv;
+
+       g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+       g_return_if_fail (name != NULL);
+
+       priv = GET_PRIV (contact);
+
+       g_free (priv->name);
+       priv->name = g_strdup (name);
+
+       g_object_notify (G_OBJECT (contact), "name");
+}
+
+static void
+contact_set_presences (GossipContact *contact,
+                      GList         *presences)
+{
+       GossipContactPriv *priv;
+
+       priv = GET_PRIV (contact);
+
+       if (priv->presences) {
+               g_list_foreach (priv->presences, (GFunc) g_object_unref, NULL);
+               g_list_free (priv->presences);
+       }
+
+       priv->presences = g_list_copy (presences);
+       g_list_foreach (priv->presences, (GFunc) g_object_ref, NULL);
+
+       g_object_notify (G_OBJECT (contact), "presences");
+}
+
+void
+gossip_contact_set_avatar (GossipContact *contact,
+                          GossipAvatar  *avatar)
+{
+       GossipContactPriv *priv;
+
+       g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+       priv = GET_PRIV (contact);
+
+       if (priv->avatar) {
+               gossip_avatar_unref (priv->avatar);
+               priv->avatar = NULL;
+       }
+
+       if (avatar) {
+               priv->avatar = gossip_avatar_ref (avatar);
+       }
+
+       g_object_notify (G_OBJECT (contact), "avatar");
+}
+
+void
+gossip_contact_set_account (GossipContact *contact,
+                           McAccount *account)
+{
+       GossipContactPriv *priv;
+
+       g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+       g_return_if_fail (MC_IS_ACCOUNT (account));
+
+       priv = GET_PRIV (contact);
+
+       if (priv->account) {
+               g_object_unref (priv->account);
+       }
+
+       if (account) {
+               priv->account = g_object_ref (account);
+       } else {
+               priv->account = NULL;
+       }
+
+       g_object_notify (G_OBJECT (contact), "account");
+}
+
+void
+gossip_contact_add_presence (GossipContact  *contact,
+                            GossipPresence *presence)
+{
+       GossipContactPriv *priv;
+       GossipPresence    *this_presence;
+       GList             *l;
+
+       g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+       g_return_if_fail (GOSSIP_IS_PRESENCE (presence));
+
+       priv = GET_PRIV (contact);
+
+       for (l = priv->presences; l && presence; l = l->next) {
+               this_presence = l->data;
+
+               if (gossip_presence_resource_equal (this_presence, presence)) {
+                       gint ref_count;
+
+                       ref_count = G_OBJECT (presence)->ref_count;
+
+                       /* Remove old presence for this resource, we
+                        * would use g_list_remove_all() here but we
+                        * want to make sure we unref for each
+                        * instance we find it in the list.
+                        */
+                       priv->presences = g_list_remove (priv->presences, this_presence);
+                       g_object_unref (this_presence);
+
+                       if (!priv->presences || ref_count <= 1) {
+                               break;
+                       }
+
+                       /* Reset list to beginnging to make sure we
+                        * didn't miss any duplicates.
+                        */
+                       l = priv->presences;
+               }
+       }
+
+       /* Add new presence */
+       priv->presences = g_list_insert_sorted (priv->presences,
+                                               g_object_ref (presence),
+                                               gossip_presence_sort_func);
+
+       g_object_notify (G_OBJECT (contact), "presences");
+}
+
+void
+gossip_contact_remove_presence (GossipContact  *contact,
+                               GossipPresence *presence)
+{
+       GossipContactPriv *priv;
+       GossipPresence    *this_presence;
+       GList             *l;
+
+       g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+       g_return_if_fail (GOSSIP_IS_PRESENCE (presence));
+
+       priv = GET_PRIV (contact);
+
+       for (l = priv->presences; l; l = l->next) {
+               this_presence = l->data;
+
+               if (gossip_presence_resource_equal (this_presence, presence)) {
+                       gint ref_count;
+
+                       ref_count = G_OBJECT (presence)->ref_count;
+
+                       /* Remove old presence for this resource, we
+                        * would use g_list_remove_all() here but we
+                        * want to make sure we unref for each
+                        * instance we find it in the list.
+                        */
+                       priv->presences = g_list_remove (priv->presences, this_presence);
+                       g_object_unref (this_presence);
+
+                       if (!priv->presences || ref_count <= 1) {
+                               break;
+                       }
+
+                       /* Reset list to beginnging to make sure we
+                        * didn't miss any duplicates.
+                        */
+                       l = priv->presences;
+               }
+       }
+
+       priv->presences = g_list_sort (priv->presences,
+                                      gossip_presence_sort_func);
+
+       g_object_notify (G_OBJECT (contact), "presences");
+}
+
+void
+gossip_contact_set_groups (GossipContact *contact,
+                          GList         *groups)
+{
+       GossipContactPriv *priv;
+       GList             *old_groups, *l;
+
+       g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+       priv = GET_PRIV (contact);
+
+       old_groups = priv->groups;
+       priv->groups = NULL;
+
+       for (l = groups; l; l = l->next) {
+               priv->groups = g_list_append (priv->groups,
+                                             g_strdup (l->data));
+       }
+
+       g_list_foreach (old_groups, (GFunc) g_free, NULL);
+       g_list_free (old_groups);
+
+       g_object_notify (G_OBJECT (contact), "groups");
+}
+
+void
+gossip_contact_set_subscription (GossipContact      *contact,
+                                GossipSubscription  subscription)
+{
+       GossipContactPriv *priv;
+
+       g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+       priv = GET_PRIV (contact);
+
+       priv->subscription = subscription;
+
+       g_object_notify (G_OBJECT (contact), "subscription");
+}
+
+void
+gossip_contact_set_handle (GossipContact *contact,
+                          guint          handle)
+{
+       GossipContactPriv *priv;
+
+       priv = GET_PRIV (contact);
+
+       priv->handle = handle;
+
+       g_object_notify (G_OBJECT (contact), "handle");
+}
+
+gboolean
+gossip_contact_is_online (GossipContact *contact)
+{
+       GossipContactPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), FALSE);
+
+       priv = GET_PRIV (contact);
+
+       if (priv->presences == NULL) {
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+const gchar *
+gossip_contact_get_status (GossipContact *contact)
+{
+       GossipContactPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), "");
+
+       priv = GET_PRIV (contact);
+
+       if (priv->presences) {
+               GossipPresence *p;
+               const gchar    *status;
+
+               p = GOSSIP_PRESENCE (priv->presences->data);
+               status = gossip_presence_get_status (p);
+               if (!status) {
+                       status = gossip_presence_state_get_default_status (gossip_presence_get_state (p));
+               }
+               return status;
+       } else {
+               return _("Offline");
+       }
+}
+
+gboolean
+gossip_contact_equal (gconstpointer v1,
+                     gconstpointer v2)
+{
+       McAccount   *account_a;
+       McAccount   *account_b;
+       const gchar *id_a;
+       const gchar *id_b;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (v1), FALSE);
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (v2), FALSE);
+
+       account_a = gossip_contact_get_account (GOSSIP_CONTACT (v1));
+       account_b = gossip_contact_get_account (GOSSIP_CONTACT (v2));
+
+       id_a = gossip_contact_get_id (GOSSIP_CONTACT (v1));
+       id_b = gossip_contact_get_id (GOSSIP_CONTACT (v2));
+
+       return gossip_account_equal (account_a, account_b) && g_str_equal (id_a, id_b);
+}
+
+guint
+gossip_contact_hash (gconstpointer key)
+{
+       GossipContactPriv *priv;
+       guint              hash;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (key), +1);
+
+       priv = GET_PRIV (GOSSIP_CONTACT (key));
+
+       hash = gossip_account_hash (gossip_contact_get_account (GOSSIP_CONTACT (key)));
+       hash += g_str_hash (gossip_contact_get_id (GOSSIP_CONTACT (key)));
+
+       return hash;
+}
+
diff --git a/libempathy/gossip-contact.h b/libempathy/gossip-contact.h
new file mode 100644 (file)
index 0000000..92caee9
--- /dev/null
@@ -0,0 +1,102 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 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.
+ */
+
+#ifndef __GOSSIP_CONTACT_H__
+#define __GOSSIP_CONTACT_H__
+
+#include <glib-object.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+#include "gossip-avatar.h"
+#include "gossip-presence.h"
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CONTACT         (gossip_contact_get_gtype ())
+#define GOSSIP_CONTACT(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CONTACT, GossipContact))
+#define GOSSIP_CONTACT_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_CONTACT, GossipContactClass))
+#define GOSSIP_IS_CONTACT(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CONTACT))
+#define GOSSIP_IS_CONTACT_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CONTACT))
+#define GOSSIP_CONTACT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CONTACT, GossipContactClass))
+
+typedef struct _GossipContact      GossipContact;
+typedef struct _GossipContactClass GossipContactClass;
+
+struct _GossipContact {
+       GObject parent;
+};
+
+struct _GossipContactClass {
+       GObjectClass parent_class;
+};
+
+typedef enum {
+       GOSSIP_SUBSCRIPTION_NONE = 0,
+       GOSSIP_SUBSCRIPTION_TO   = 1 << 0,      /* We send our presence to that contact */
+       GOSSIP_SUBSCRIPTION_FROM = 1 << 1,      /* That contact sends his presence to us */
+       GOSSIP_SUBSCRIPTION_BOTH = GOSSIP_SUBSCRIPTION_TO | GOSSIP_SUBSCRIPTION_FROM
+} GossipSubscription;
+
+GType              gossip_contact_get_gtype                 (void) G_GNUC_CONST;
+
+GossipContact *    gossip_contact_new                       (McAccount          *account);
+GossipContact *    gossip_contact_new_full                  (McAccount          *account,
+                                                            const gchar        *id,
+                                                            const gchar        *name);
+const gchar *      gossip_contact_get_id                    (GossipContact      *contact);
+const gchar *      gossip_contact_get_name                  (GossipContact      *contact);
+GossipAvatar *     gossip_contact_get_avatar                (GossipContact      *contact);
+McAccount *        gossip_contact_get_account               (GossipContact      *contact);
+void               gossip_contact_add_presence              (GossipContact      *contact,
+                                                            GossipPresence     *presence);
+void               gossip_contact_remove_presence           (GossipContact      *contact,
+                                                            GossipPresence     *presence);
+GossipPresence *   gossip_contact_get_presence_for_resource (GossipContact      *contact,
+                                                            const gchar        *resource);
+GossipPresence *   gossip_contact_get_active_presence       (GossipContact      *contact);
+GList *            gossip_contact_get_presence_list         (GossipContact      *contact);
+GList *            gossip_contact_get_groups                (GossipContact      *contact);
+GossipSubscription gossip_contact_get_subscription          (GossipContact      *contact);
+guint              gossip_contact_get_handle                (GossipContact      *contact);
+void               gossip_contact_set_id                    (GossipContact      *contact,
+                                                            const gchar        *id);
+void               gossip_contact_set_name                  (GossipContact      *contact,
+                                                            const gchar        *name);
+void               gossip_contact_set_avatar                (GossipContact      *contact,
+                                                            GossipAvatar       *avatar);
+void               gossip_contact_set_account               (GossipContact      *contact,
+                                                            McAccount          *account);
+void               gossip_contact_set_groups                (GossipContact      *contact,
+                                                            GList              *categories);
+void               gossip_contact_set_subscription          (GossipContact      *contact,
+                                                            GossipSubscription  subscription);
+void               gossip_contact_set_handle                (GossipContact      *contact,
+                                                            guint               handle);
+gboolean           gossip_contact_is_online                 (GossipContact      *contact);
+const gchar *      gossip_contact_get_status                (GossipContact      *contact);
+gboolean           gossip_contact_equal                     (gconstpointer       v1,
+                                                            gconstpointer       v2);
+guint              gossip_contact_hash                      (gconstpointer       key);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CONTACT_H__ */
+
diff --git a/libempathy/gossip-debug.c b/libempathy/gossip-debug.c
new file mode 100644 (file)
index 0000000..a6bbeea
--- /dev/null
@@ -0,0 +1,92 @@
+/* -*- 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: Richard Hult <richard@imendio.com>
+ */
+
+#include "config.h"
+
+#include <stdarg.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gprintf.h>
+
+/* Set EMPATHY_DEBUG to a colon/comma/space separated list of domains, or "all"
+ * to get all debug output.
+ */
+
+#include "gossip-debug.h"
+
+static gchar    **debug_strv;
+static gboolean   all_domains = FALSE;
+
+static void
+debug_init (void)
+{
+       static gboolean inited = FALSE;
+
+       if (!inited) {
+               const gchar *env;
+               gint         i;
+
+               env = g_getenv ("EMPATHY_DEBUG");
+
+               if (env) {
+                       debug_strv = g_strsplit_set (env, ":, ", 0);
+               } else {
+                       debug_strv = NULL;
+               }
+
+               for (i = 0; debug_strv && debug_strv[i]; i++) {
+                       if (strcmp ("all", debug_strv[i]) == 0) {
+                               all_domains = TRUE;
+                       }
+               }
+
+               inited = TRUE;
+       }
+}
+
+void
+gossip_debug_impl (const gchar *domain, const gchar *msg, ...)
+{
+       gint i;
+
+       g_return_if_fail (domain != NULL);
+       g_return_if_fail (msg != NULL);
+
+       debug_init ();
+
+       for (i = 0; debug_strv && debug_strv[i]; i++) {
+               if (all_domains || strcmp (domain, debug_strv[i]) == 0) {
+                       va_list args;
+
+                       g_print ("%s: ", domain);
+
+                       va_start (args, msg);
+                       g_vprintf (msg, args);
+                       va_end (args);
+
+                       g_print ("\n");
+                       break;
+               }
+       }
+}
+
diff --git a/libempathy/gossip-debug.h b/libempathy/gossip-debug.h
new file mode 100644 (file)
index 0000000..39dae0f
--- /dev/null
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 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.
+ */
+
+#ifndef __GOSSIP_DEBUG_H__
+#define __GOSSIP_DEBUG_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#ifdef G_HAVE_ISO_VARARGS
+#  ifdef GOSSIP_DISABLE_DEBUG
+#    define gossip_debug(...)
+#  else
+#    define gossip_debug(...) gossip_debug_impl (__VA_ARGS__)
+#  endif
+#elif defined(G_HAVE_GNUC_VARARGS)
+#  if GOSSIP_DISABLE_DEBUG
+#    define gossip_debug(fmt...)
+#  else
+#    define gossip_debug(fmt...) gossip_debug_impl(fmt)
+#  endif
+#else
+#  if GOSSIP_DISABLE_DEBUG
+#    define gossip_debug(x)
+#  else
+#    define gossip_debug gossip_debug_impl
+#  endif
+#endif
+
+void gossip_debug_impl (const gchar *domain, const gchar *msg, ...);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_DEBUG_H__ */
+
diff --git a/libempathy/gossip-message.c b/libempathy/gossip-message.c
new file mode 100644 (file)
index 0000000..85889e7
--- /dev/null
@@ -0,0 +1,365 @@
+/* -*- 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 "gossip-message.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_MESSAGE, GossipMessagePriv))
+
+typedef struct _GossipMessagePriv GossipMessagePriv;
+
+struct _GossipMessagePriv {
+       GossipMessageType     type;
+       GossipContact        *sender;
+       gchar                *body;
+       GossipTime            timestamp;
+
+};
+
+static void gossip_message_class_init (GossipMessageClass *class);
+static void gossip_message_init       (GossipMessage      *message);
+static void gossip_message_finalize   (GObject            *object);
+static void message_get_property      (GObject            *object,
+                                      guint               param_id,
+                                      GValue             *value,
+                                      GParamSpec         *pspec);
+static void message_set_property      (GObject            *object,
+                                      guint               param_id,
+                                      const GValue       *value,
+                                      GParamSpec         *pspec);
+
+enum {
+       PROP_0,
+       PROP_TYPE,
+       PROP_SENDER,
+       PROP_BODY,
+       PROP_TIMESTAMP,
+};
+
+static gpointer parent_class = NULL;
+
+GType
+gossip_message_get_gtype (void)
+{
+       static GType type = 0;
+
+       if (!type) {
+               static const GTypeInfo info = {
+                       sizeof (GossipMessageClass),
+                       NULL, /* base_init */
+                       NULL, /* base_finalize */
+                       (GClassInitFunc) gossip_message_class_init,
+                       NULL, /* class_finalize */
+                       NULL, /* class_data */
+                       sizeof (GossipMessage),
+                       0,    /* n_preallocs */
+                       (GInstanceInitFunc) gossip_message_init
+               };
+
+               type = g_type_register_static (G_TYPE_OBJECT,
+                                              "GossipMessage",
+                                              &info, 0);
+       }
+
+       return type;
+}
+
+static void
+gossip_message_class_init (GossipMessageClass *class)
+{
+       GObjectClass *object_class;
+
+       object_class = G_OBJECT_CLASS (class);
+       parent_class = g_type_class_peek_parent (class);
+
+       object_class->finalize     = gossip_message_finalize;
+       object_class->get_property = message_get_property;
+       object_class->set_property = message_set_property;
+
+       g_object_class_install_property (object_class,
+                                        PROP_TYPE,
+                                        g_param_spec_int ("type",
+                                                          "Message Type",
+                                                          "The type of message",
+                                                          GOSSIP_MESSAGE_TYPE_NORMAL,
+                                                          GOSSIP_MESSAGE_TYPE_LAST,
+                                                          GOSSIP_MESSAGE_TYPE_NORMAL,
+                                                          G_PARAM_READWRITE));
+       g_object_class_install_property (object_class,
+                                        PROP_SENDER,
+                                        g_param_spec_object ("sender",
+                                                             "Message Sender",
+                                                             "The sender of the message",
+                                                             GOSSIP_TYPE_CONTACT,
+                                                             G_PARAM_READWRITE));
+
+       g_object_class_install_property (object_class,
+                                        PROP_BODY,
+                                        g_param_spec_string ("body",
+                                                             "Message Body",
+                                                             "The content of the message",
+                                                             NULL,
+                                                             G_PARAM_READWRITE));
+       g_object_class_install_property (object_class,
+                                        PROP_TIMESTAMP,
+                                        g_param_spec_long ("timestamp",
+                                                           "timestamp",
+                                                           "timestamp",
+                                                           -1,
+                                                           G_MAXLONG,
+                                                           -1,
+                                                           G_PARAM_READWRITE));
+
+
+       g_type_class_add_private (object_class, sizeof (GossipMessagePriv));
+
+}
+
+static void
+gossip_message_init (GossipMessage *message)
+{
+       GossipMessagePriv *priv;
+
+       priv = GET_PRIV (message);
+
+       priv->type = GOSSIP_MESSAGE_TYPE_NORMAL;
+       priv->sender = NULL;
+       priv->body = NULL;
+       priv->timestamp = gossip_time_get_current ();
+}
+
+static void
+gossip_message_finalize (GObject *object)
+{
+       GossipMessagePriv *priv;
+
+       priv = GET_PRIV (object);
+
+       if (priv->sender) {
+               g_object_unref (priv->sender);
+       }
+
+       g_free (priv->body);
+
+       (G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+message_get_property (GObject    *object,
+                     guint       param_id,
+                     GValue     *value,
+                     GParamSpec *pspec)
+{
+       GossipMessagePriv *priv;
+
+       priv = GET_PRIV (object);
+
+       switch (param_id) {
+       case PROP_TYPE:
+               g_value_set_int (value, priv->type);
+               break;
+       case PROP_SENDER:
+               g_value_set_object (value, priv->sender);
+               break;
+       case PROP_BODY:
+               g_value_set_string (value, priv->body);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       };
+}
+
+static void
+message_set_property (GObject      *object,
+                     guint         param_id,
+                     const GValue *value,
+                     GParamSpec   *pspec)
+{
+       GossipMessagePriv *priv;
+
+       priv = GET_PRIV (object);
+
+       switch (param_id) {
+       case PROP_TYPE:
+               gossip_message_set_type (GOSSIP_MESSAGE (object),
+                                        g_value_get_int (value));
+               break;
+       case PROP_SENDER:
+               gossip_message_set_sender (GOSSIP_MESSAGE (object),
+                                          GOSSIP_CONTACT (g_value_get_object (value)));
+               break;
+       case PROP_BODY:
+               gossip_message_set_body (GOSSIP_MESSAGE (object),
+                                        g_value_get_string (value));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       };
+}
+
+GossipMessage *
+gossip_message_new (const gchar *body)
+{
+       return g_object_new (GOSSIP_TYPE_MESSAGE,
+                            "body", body,
+                            NULL);
+}
+
+GossipMessageType
+gossip_message_get_type (GossipMessage *message)
+{
+       GossipMessagePriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_MESSAGE (message),
+                             GOSSIP_MESSAGE_TYPE_NORMAL);
+
+       priv = GET_PRIV (message);
+
+       return priv->type;
+}
+
+void
+gossip_message_set_type (GossipMessage     *message,
+                        GossipMessageType  type)
+{
+       GossipMessagePriv *priv;
+
+       g_return_if_fail (GOSSIP_IS_MESSAGE (message));
+
+       priv = GET_PRIV (message);
+
+       priv->type = type;
+
+       g_object_notify (G_OBJECT (message), "type");
+}
+
+GossipContact *
+gossip_message_get_sender (GossipMessage *message)
+{
+       GossipMessagePriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_MESSAGE (message), NULL);
+
+       priv = GET_PRIV (message);
+
+       return priv->sender;
+}
+
+void
+gossip_message_set_sender (GossipMessage *message, GossipContact *contact)
+{
+       GossipMessagePriv *priv;
+       GossipContact     *old_sender;
+
+       g_return_if_fail (GOSSIP_IS_MESSAGE (message));
+       g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+       priv = GET_PRIV (message);
+
+       old_sender = priv->sender;
+       priv->sender = g_object_ref (contact);
+
+       if (old_sender) {
+               g_object_unref (old_sender);
+       }
+
+       g_object_notify (G_OBJECT (message), "sender");
+}
+
+const gchar *
+gossip_message_get_body (GossipMessage *message)
+{
+       GossipMessagePriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_MESSAGE (message), NULL);
+
+       priv = GET_PRIV (message);
+
+       return priv->body;
+}
+
+void
+gossip_message_set_body (GossipMessage *message,
+                        const gchar   *body)
+{
+       GossipMessagePriv *priv;
+       GossipMessageType  type;
+
+       g_return_if_fail (GOSSIP_IS_MESSAGE (message));
+
+       priv = GET_PRIV (message);
+
+       g_free (priv->body);
+       priv->body = NULL;
+
+       type = GOSSIP_MESSAGE_TYPE_NORMAL;
+       if (g_str_has_prefix (body, "/me")) {
+               type = GOSSIP_MESSAGE_TYPE_ACTION;
+               body += 4;
+       }
+
+       if (body) {
+               priv->body = g_strdup (body);
+       }
+
+       if (type != priv->type) {
+               gossip_message_set_type (message, type);
+       }
+
+       g_object_notify (G_OBJECT (message), "body");
+}
+
+GossipTime
+gossip_message_get_timestamp (GossipMessage *message)
+{
+       GossipMessagePriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_MESSAGE (message), -1);
+
+       priv = GET_PRIV (message);
+
+       return priv->timestamp;
+}
+
+void
+gossip_message_set_timestamp (GossipMessage *message,
+                             GossipTime     timestamp)
+{
+       GossipMessagePriv *priv;
+
+       g_return_if_fail (GOSSIP_IS_MESSAGE (message));
+       g_return_if_fail (timestamp >= -1);
+
+       priv = GET_PRIV (message);
+
+       if (timestamp <= 0) {
+               priv->timestamp = gossip_time_get_current ();
+       } else {
+               priv->timestamp = timestamp;
+       }
+
+       g_object_notify (G_OBJECT (message), "timestamp");
+}
+
diff --git a/libempathy/gossip-message.h b/libempathy/gossip-message.h
new file mode 100644 (file)
index 0000000..8defba7
--- /dev/null
@@ -0,0 +1,75 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 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.
+ */
+
+#ifndef __GOSSIP_MESSAGE_H__
+#define __GOSSIP_MESSAGE_H__
+
+#include <glib-object.h>
+
+#include "gossip-contact.h"
+#include "gossip-time.h"
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_MESSAGE         (gossip_message_get_gtype ())
+#define GOSSIP_MESSAGE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_MESSAGE, GossipMessage))
+#define GOSSIP_MESSAGE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_MESSAGE, GossipMessageClass))
+#define GOSSIP_IS_MESSAGE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_MESSAGE))
+#define GOSSIP_IS_MESSAGE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_MESSAGE))
+#define GOSSIP_MESSAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_MESSAGE, GossipMessageClass))
+
+typedef struct _GossipMessage      GossipMessage;
+typedef struct _GossipMessageClass GossipMessageClass;
+
+struct _GossipMessage {
+       GObject parent;
+};
+
+struct _GossipMessageClass {
+       GObjectClass parent_class;
+};
+
+typedef enum {
+       GOSSIP_MESSAGE_TYPE_NORMAL,
+       GOSSIP_MESSAGE_TYPE_ACTION,
+       GOSSIP_MESSAGE_TYPE_NOTICE,
+       GOSSIP_MESSAGE_TYPE_AUTO_REPLY,
+       GOSSIP_MESSAGE_TYPE_LAST
+} GossipMessageType;
+
+GType             gossip_message_get_gtype               (void) G_GNUC_CONST;
+GossipMessage *   gossip_message_new                     (const gchar          *body);
+GossipMessageType gossip_message_get_type                (GossipMessage        *message);
+void              gossip_message_set_type                (GossipMessage        *message,
+                                                         GossipMessageType     type);
+GossipContact *   gossip_message_get_sender              (GossipMessage        *message);
+void              gossip_message_set_sender              (GossipMessage        *message,
+                                                         GossipContact        *contact);
+const gchar *     gossip_message_get_body                (GossipMessage        *message);
+void              gossip_message_set_body                (GossipMessage        *message,
+                                                         const gchar          *body);
+/* What return value should we have here? */
+GossipTime        gossip_message_get_timestamp           (GossipMessage        *message);
+void              gossip_message_set_timestamp           (GossipMessage        *message,
+                                                         GossipTime            timestamp);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_MESSAGE_H__ */
diff --git a/libempathy/gossip-paths.c b/libempathy/gossip-paths.c
new file mode 100644 (file)
index 0000000..aa97bdc
--- /dev/null
@@ -0,0 +1,51 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ *
+ * This library 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 library 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 library; 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 "gossip-paths.h"
+
+#define EMPATHY "empathy"
+
+gchar *
+gossip_paths_get_glade_path (const gchar *filename)
+{
+       return g_build_filename (DATADIR, EMPATHY, filename, NULL);
+}
+
+gchar *
+gossip_paths_get_image_path (const gchar *filename)
+{
+       return g_build_filename (DATADIR, EMPATHY, filename, NULL);
+}
+
+gchar *
+gossip_paths_get_dtd_path (const gchar *filename)
+{
+       return g_build_filename (DATADIR, EMPATHY, filename, NULL);
+}
+
+gchar *
+gossip_paths_get_locale_path ()
+{
+       return g_strdup (LOCALEDIR);
+}
diff --git a/libempathy/gossip-paths.h b/libempathy/gossip-paths.h
new file mode 100644 (file)
index 0000000..e00a33e
--- /dev/null
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 Imendio AB
+ *
+ * This library 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 library 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 library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GOSSIP_PATHS_H__
+#define __GOSSIP_PATHS_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gchar *gossip_paths_get_glade_path  (const gchar *filename);
+gchar *gossip_paths_get_image_path  (const gchar *filename);
+gchar *gossip_paths_get_dtd_path    (const gchar *filename);
+gchar *gossip_paths_get_sound_path  (const gchar *filename);
+gchar *gossip_paths_get_locale_path (void);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_PATHS_H__ */
diff --git a/libempathy/gossip-presence.c b/libempathy/gossip-presence.c
new file mode 100644 (file)
index 0000000..e41ae55
--- /dev/null
@@ -0,0 +1,441 @@
+/* -*- 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 <glib/gi18n.h>
+
+#include "gossip-presence.h"
+#include "gossip-time.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_PRESENCE, GossipPresencePriv))
+
+typedef struct _GossipPresencePriv GossipPresencePriv;
+
+struct _GossipPresencePriv {
+       GossipPresenceState  state;
+
+       gchar               *status;
+       gchar               *resource;
+
+       gint                 priority;
+       GossipTime           timestamp;
+};
+
+static void         presence_finalize           (GObject             *object);
+static void         presence_get_property       (GObject             *object,
+                                                guint                param_id,
+                                                GValue              *value,
+                                                GParamSpec          *pspec);
+static void         presence_set_property       (GObject             *object,
+                                                guint                param_id,
+                                                const GValue        *value,
+                                                GParamSpec          *pspec);
+
+enum {
+       PROP_0,
+       PROP_STATE,
+       PROP_STATUS,
+       PROP_RESOURCE,
+       PROP_PRIORITY
+};
+
+G_DEFINE_TYPE (GossipPresence, gossip_presence, G_TYPE_OBJECT);
+
+static void
+gossip_presence_class_init (GossipPresenceClass *class)
+{
+       GObjectClass *object_class;
+
+       object_class = G_OBJECT_CLASS (class);
+
+       object_class->finalize     = presence_finalize;
+       object_class->get_property = presence_get_property;
+       object_class->set_property = presence_set_property;
+
+       g_object_class_install_property (object_class,
+                                        PROP_STATE,
+                                        g_param_spec_int ("state",
+                                                          "Presence State",
+                                                          "The current state of the presence",
+                                                          GOSSIP_PRESENCE_STATE_AVAILABLE,
+                                                          GOSSIP_PRESENCE_STATE_EXT_AWAY,
+                                                          GOSSIP_PRESENCE_STATE_AVAILABLE,
+                                                          G_PARAM_READWRITE));
+       g_object_class_install_property (object_class,
+                                        PROP_STATUS,
+                                        g_param_spec_string ("status",
+                                                             "Presence Status",
+                                                             "Status string set on presence",
+                                                             NULL,
+                                                             G_PARAM_READWRITE));
+       g_object_class_install_property (object_class,
+                                        PROP_RESOURCE,
+                                        g_param_spec_string ("resource",
+                                                             "Presence Resource",
+                                                             "Resource that this presence is for",
+                                                             NULL,
+                                                             G_PARAM_READWRITE));
+       g_object_class_install_property (object_class,
+                                        PROP_PRIORITY,
+                                        g_param_spec_int ("priority",
+                                                          "Presence Priority",
+                                                          "Priority value of presence",
+                                                          G_MININT,
+                                                          G_MAXINT,
+                                                          0,
+                                                          G_PARAM_READWRITE));
+
+       g_type_class_add_private (object_class, sizeof (GossipPresencePriv));
+}
+
+static void
+gossip_presence_init (GossipPresence *presence)
+{
+       GossipPresencePriv *priv;
+
+       priv = GET_PRIV (presence);
+
+       priv->state = GOSSIP_PRESENCE_STATE_AVAILABLE;
+
+       priv->status = NULL;
+       priv->resource = NULL;
+
+       priv->priority = 0;
+
+       priv->timestamp = gossip_time_get_current ();
+}
+
+static void
+presence_finalize (GObject *object)
+{
+       GossipPresencePriv *priv;
+
+       priv = GET_PRIV (object);
+
+       g_free (priv->status);
+       g_free (priv->resource);
+
+       (G_OBJECT_CLASS (gossip_presence_parent_class)->finalize) (object);
+}
+
+static void
+presence_get_property (GObject    *object,
+                      guint       param_id,
+                      GValue     *value,
+                      GParamSpec *pspec)
+{
+       GossipPresencePriv *priv;
+
+       priv = GET_PRIV (object);
+
+       switch (param_id) {
+       case PROP_STATE:
+               g_value_set_int (value, priv->state);
+               break;
+       case PROP_STATUS:
+               g_value_set_string (value,
+                                   gossip_presence_get_status (GOSSIP_PRESENCE (object)));
+               break;
+       case PROP_RESOURCE:
+               g_value_set_string (value,
+                                   gossip_presence_get_resource (GOSSIP_PRESENCE (object)));
+               break;
+       case PROP_PRIORITY:
+               g_value_set_int (value, priv->priority);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       }
+}
+static void
+presence_set_property (GObject      *object,
+                      guint         param_id,
+                      const GValue *value,
+                      GParamSpec   *pspec)
+{
+       GossipPresencePriv *priv;
+
+       priv = GET_PRIV (object);
+
+       switch (param_id) {
+       case PROP_STATE:
+               priv->state = g_value_get_int (value);
+               break;
+       case PROP_STATUS:
+               gossip_presence_set_status (GOSSIP_PRESENCE (object),
+                                           g_value_get_string (value));
+               break;
+       case PROP_RESOURCE:
+               gossip_presence_set_resource (GOSSIP_PRESENCE (object),
+                                             g_value_get_string (value));
+               break;
+       case PROP_PRIORITY:
+               priv->priority = g_value_get_int (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+               break;
+       }
+}
+
+GossipPresence *
+gossip_presence_new (void)
+{
+       return g_object_new (GOSSIP_TYPE_PRESENCE, NULL);
+}
+
+GossipPresence *
+gossip_presence_new_full (GossipPresenceState  state,
+                         const gchar         *status)
+{
+       return g_object_new (GOSSIP_TYPE_PRESENCE,
+                            "state", state,
+                            "status", status,
+                            NULL);
+}
+
+const gchar *
+gossip_presence_get_resource (GossipPresence *presence)
+{
+       GossipPresencePriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_PRESENCE (presence), NULL);
+
+       priv = GET_PRIV (presence);
+
+       if (priv->resource) {
+               return priv->resource;
+       }
+
+       return NULL;
+}
+
+const gchar *
+gossip_presence_get_status (GossipPresence *presence)
+{
+       GossipPresencePriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_PRESENCE (presence),
+                             _("Offline"));
+
+       priv = GET_PRIV (presence);
+
+       return priv->status;
+}
+
+gint
+gossip_presence_get_priority (GossipPresence *presence)
+{
+       GossipPresencePriv *priv;
+
+       priv = GET_PRIV (presence);
+       g_return_val_if_fail (GOSSIP_IS_PRESENCE (presence), 0);
+
+       return priv->priority;
+}
+
+void
+gossip_presence_set_resource (GossipPresence *presence,
+                             const gchar    *resource)
+{
+       GossipPresencePriv *priv;
+
+       g_return_if_fail (GOSSIP_IS_PRESENCE (presence));
+       g_return_if_fail (resource != NULL);
+
+       priv = GET_PRIV (presence);
+
+       g_free (priv->resource);
+       priv->resource = g_strdup (resource);
+
+       g_object_notify (G_OBJECT (presence), "resource");
+}
+
+GossipPresenceState
+gossip_presence_get_state (GossipPresence *presence)
+{
+       GossipPresencePriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_PRESENCE (presence),
+                             GOSSIP_PRESENCE_STATE_AVAILABLE);
+
+       priv = GET_PRIV (presence);
+
+       return priv->state;
+}
+
+void
+gossip_presence_set_state (GossipPresence      *presence,
+                          GossipPresenceState  state)
+{
+       GossipPresencePriv *priv;
+
+       g_return_if_fail (GOSSIP_IS_PRESENCE (presence));
+
+       priv = GET_PRIV (presence);
+
+       priv->state = state;
+
+       g_object_notify (G_OBJECT (presence), "state");
+}
+
+void
+gossip_presence_set_status (GossipPresence *presence,
+                           const gchar    *status)
+{
+       GossipPresencePriv *priv;
+
+       priv = GET_PRIV (presence);
+       g_return_if_fail (GOSSIP_IS_PRESENCE (presence));
+
+       g_free (priv->status);
+
+       if (status) {
+               priv->status = g_strdup (status);
+       } else {
+               priv->status = NULL;
+       }
+
+       g_object_notify (G_OBJECT (presence), "status");
+}
+
+void
+gossip_presence_set_priority (GossipPresence *presence,
+                             gint            priority)
+{
+       GossipPresencePriv *priv;
+
+       g_return_if_fail (GOSSIP_IS_PRESENCE (presence));
+
+       priv = GET_PRIV (presence);
+
+       priv->priority = priority;
+
+       g_object_notify (G_OBJECT (presence), "priority");
+}
+
+gboolean
+gossip_presence_resource_equal (gconstpointer a,
+                               gconstpointer b)
+{
+       GossipPresencePriv *priv1;
+       GossipPresencePriv *priv2;
+
+       g_return_val_if_fail (GOSSIP_IS_PRESENCE (a), FALSE);
+       g_return_val_if_fail (GOSSIP_IS_PRESENCE (b), FALSE);
+
+       priv1 = GET_PRIV (a);
+       priv2 = GET_PRIV (b);
+
+       if (!priv1->resource) {
+               if (!priv2->resource) {
+                       return TRUE;
+               }
+
+               return FALSE;
+       }
+
+       if (!priv2->resource) {
+               return FALSE;
+       }
+
+       if (strcmp (priv1->resource, priv2->resource) == 0) {
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+gint
+gossip_presence_sort_func (gconstpointer a,
+                          gconstpointer b)
+{
+       GossipPresencePriv *priv_a;
+       GossipPresencePriv *priv_b;
+       gint                diff;
+
+       g_return_val_if_fail (GOSSIP_IS_PRESENCE (a), 0);
+       g_return_val_if_fail (GOSSIP_IS_PRESENCE (b), 0);
+
+       /* We sort here by priority AND status, in theory, the
+        * priority would be enough for JUST Jabber contacts which
+        * actually abide to the protocol, but for other protocols and
+        * dodgy clients, we will sort by:
+        *   
+        *    1. State
+        *    2. Priority
+        *    3. Time it was set (most recent first).
+        */
+        
+       priv_a = GET_PRIV (a);
+       priv_b = GET_PRIV (b);
+
+       /* 1. State */
+       diff = priv_a->state - priv_b->state;
+       if (diff != 0) {
+               return diff < 1 ? -1 : +1;
+       }
+
+       /* 2. Priority */
+       diff = priv_a->priority - priv_b->priority;
+       if (diff != 0) {
+               return diff < 1 ? -1 : +1;
+       }
+
+       /* 3. Time (newest first) */
+       diff = priv_b->timestamp - priv_a->timestamp;
+       if (diff != 0) {
+               return diff < 1 ? -1 : +1;
+       }
+               
+       /* No real difference, except maybe resource */
+       return 0;
+}
+
+const gchar *
+gossip_presence_state_get_default_status (GossipPresenceState state)
+{
+       switch (state) {
+       case GOSSIP_PRESENCE_STATE_AVAILABLE:
+               return _("Available");
+               break;
+
+       case GOSSIP_PRESENCE_STATE_BUSY:
+               return _("Busy");
+               break;
+
+       case GOSSIP_PRESENCE_STATE_AWAY:
+       case GOSSIP_PRESENCE_STATE_EXT_AWAY:
+               return _("Away");
+               break;
+
+       case GOSSIP_PRESENCE_STATE_HIDDEN:
+       case GOSSIP_PRESENCE_STATE_UNAVAILABLE:
+               return _("Unavailable");
+       }
+
+       return _("Available");
+}
diff --git a/libempathy/gossip-presence.h b/libempathy/gossip-presence.h
new file mode 100644 (file)
index 0000000..64a0b8e
--- /dev/null
@@ -0,0 +1,84 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 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.
+ */
+
+#ifndef __GOSSIP_PRESENCE_H__
+#define __GOSSIP_PRESENCE_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_PRESENCE         (gossip_presence_get_type ())
+#define GOSSIP_PRESENCE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_PRESENCE, GossipPresence))
+#define GOSSIP_PRESENCE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_PRESENCE, GossipPresenceClass))
+#define GOSSIP_IS_PRESENCE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_PRESENCE))
+#define GOSSIP_IS_PRESENCE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_PRESENCE))
+#define GOSSIP_PRESENCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_PRESENCE, GossipPresenceClass))
+
+typedef struct _GossipPresence      GossipPresence;
+typedef struct _GossipPresenceClass GossipPresenceClass;
+
+struct _GossipPresence {
+       GObject parent;
+};
+
+struct _GossipPresenceClass {
+       GObjectClass parent_class;
+};
+
+typedef enum {
+       GOSSIP_PRESENCE_STATE_AVAILABLE,
+       GOSSIP_PRESENCE_STATE_BUSY,
+       GOSSIP_PRESENCE_STATE_AWAY,
+       GOSSIP_PRESENCE_STATE_EXT_AWAY,
+       GOSSIP_PRESENCE_STATE_HIDDEN,      /* When you appear offline to others */
+       GOSSIP_PRESENCE_STATE_UNAVAILABLE,
+} GossipPresenceState;
+
+GType               gossip_presence_get_type                 (void) G_GNUC_CONST;
+
+GossipPresence *    gossip_presence_new                      (void);
+GossipPresence *    gossip_presence_new_full                 (GossipPresenceState  state,
+                                                             const gchar         *status);
+
+const gchar *       gossip_presence_get_resource             (GossipPresence      *presence);
+GossipPresenceState gossip_presence_get_state                (GossipPresence      *presence);
+const gchar *       gossip_presence_get_status               (GossipPresence      *presence);
+gint                gossip_presence_get_priority             (GossipPresence      *presence);
+
+void                gossip_presence_set_resource             (GossipPresence      *presence,
+                                                             const gchar         *resource);
+void                gossip_presence_set_state                (GossipPresence      *presence,
+                                                             GossipPresenceState  state);
+void                gossip_presence_set_status               (GossipPresence      *presence,
+                                                             const gchar         *status);
+void                gossip_presence_set_priority             (GossipPresence      *presence,
+                                                             gint                 priority);
+gboolean            gossip_presence_resource_equal           (gconstpointer        a,
+                                                             gconstpointer        b);
+gint                gossip_presence_sort_func                (gconstpointer        a,
+                                                             gconstpointer        b);
+
+const gchar *       gossip_presence_state_get_default_status (GossipPresenceState  state);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_PRESENCE_H__ */
+
diff --git a/libempathy/gossip-telepathy-group.c b/libempathy/gossip-telepathy-group.c
new file mode 100644 (file)
index 0000000..4b04ac4
--- /dev/null
@@ -0,0 +1,496 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 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 <dbus/dbus-glib.h>
+#include <libtelepathy/tp-chan.h>
+#include <libtelepathy/tp-chan-iface-group-gen.h>
+#include <libtelepathy/tp-constants.h>
+#include <libtelepathy/tp-conn.h>
+
+#include "gossip-debug.h"
+#include "gossip-telepathy-group.h"
+#include "empathy-marshal.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
+                      GOSSIP_TYPE_TELEPATHY_GROUP, GossipTelepathyGroupPriv))
+
+#define DEBUG_DOMAIN "TelepathyGroup"
+
+struct _GossipTelepathyGroupPriv {
+       DBusGProxy *group_iface;
+       TpConn     *tp_conn;
+       TpChan     *tp_chan;
+       gchar      *group_name;
+};
+
+static void gossip_telepathy_group_class_init    (GossipTelepathyGroupClass *klass);
+static void gossip_telepathy_group_init          (GossipTelepathyGroup      *group);
+static void telepathy_group_finalize             (GObject                   *object);
+static void telepathy_group_destroy_cb           (DBusGProxy                *proxy,
+                                                 GossipTelepathyGroup      *group);
+static void telepathy_group_members_changed_cb   (DBusGProxy                *group_iface,
+                                                 gchar                     *message,
+                                                 GArray                    *added,
+                                                 GArray                    *removed,
+                                                 GArray                    *local_pending,
+                                                 GArray                    *remote_pending,
+                                                 guint                      actor,
+                                                 guint                      reason,
+                                                 GossipTelepathyGroup      *group);
+
+enum {
+       MEMBERS_ADDED,
+       MEMBERS_REMOVED,
+       LOCAL_PENDING,
+       REMOTE_PENDING,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (GossipTelepathyGroup, gossip_telepathy_group, G_TYPE_OBJECT);
+
+static void
+gossip_telepathy_group_class_init (GossipTelepathyGroupClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = telepathy_group_finalize;
+
+       signals[MEMBERS_ADDED] =
+               g_signal_new ("members-added",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             empathy_marshal_VOID__POINTER_UINT_UINT_STRING,
+                             G_TYPE_NONE,
+                             4, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING);
+
+       signals[MEMBERS_REMOVED] =
+               g_signal_new ("members-removed",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             empathy_marshal_VOID__POINTER_UINT_UINT_STRING,
+                             G_TYPE_NONE,
+                             4, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING);
+
+       signals[LOCAL_PENDING] =
+               g_signal_new ("local-pending",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             empathy_marshal_VOID__POINTER_UINT_UINT_STRING,
+                             G_TYPE_NONE,
+                             4, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING);
+
+       signals[REMOTE_PENDING] =
+               g_signal_new ("remote-pending",
+                             G_TYPE_FROM_CLASS (klass),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             empathy_marshal_VOID__POINTER_UINT_UINT_STRING,
+                             G_TYPE_NONE,
+                             4, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING);
+
+       g_type_class_add_private (object_class, sizeof (GossipTelepathyGroupPriv));
+}
+
+static void
+gossip_telepathy_group_init (GossipTelepathyGroup *group)
+{
+}
+
+static void
+telepathy_group_finalize (GObject *object)
+{
+       GossipTelepathyGroupPriv *priv;
+
+       priv = GET_PRIV (object);
+
+       if (priv->group_iface) {
+               g_signal_handlers_disconnect_by_func (priv->group_iface,
+                                                     telepathy_group_destroy_cb,
+                                                     object);
+               dbus_g_proxy_disconnect_signal (priv->group_iface, "MembersChanged",
+                                               G_CALLBACK (telepathy_group_members_changed_cb),
+                                               object);
+               g_object_unref (priv->group_iface);
+       }
+
+       if (priv->tp_conn) {
+               g_object_unref (priv->tp_conn);
+       }
+
+       if (priv->tp_chan) {
+               g_object_unref (priv->tp_chan);
+       }
+
+       g_free (priv->group_name);
+
+       G_OBJECT_CLASS (gossip_telepathy_group_parent_class)->finalize (object);
+}
+
+GossipTelepathyGroup *
+gossip_telepathy_group_new (TpChan *tp_chan,
+                           TpConn *tp_conn)
+{
+       GossipTelepathyGroup     *group;
+       GossipTelepathyGroupPriv *priv;
+       DBusGProxy               *group_iface;
+
+       g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
+
+       group_iface = tp_chan_get_interface (tp_chan,
+                                            TELEPATHY_CHAN_IFACE_GROUP_QUARK);
+       g_return_val_if_fail (group_iface != NULL, NULL);
+
+       group = g_object_new (GOSSIP_TYPE_TELEPATHY_GROUP, NULL);
+       priv = GET_PRIV (group);
+
+       priv->tp_conn = g_object_ref (tp_conn);
+       priv->tp_chan = g_object_ref (tp_chan);
+       priv->group_iface = g_object_ref (group_iface);
+
+       dbus_g_proxy_connect_signal (priv->group_iface, "MembersChanged",
+                                    G_CALLBACK (telepathy_group_members_changed_cb),
+                                    group, NULL);
+       g_signal_connect (group_iface, "destroy",
+                         G_CALLBACK (telepathy_group_destroy_cb),
+                         group);
+
+
+       return group;
+}
+
+void
+gossip_telepathy_group_add_members (GossipTelepathyGroup *group,
+                                   GArray               *handles,
+                                   const gchar          *message)
+{
+       GossipTelepathyGroupPriv *priv;
+       GError                   *error = NULL;
+
+       g_return_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group));
+       g_return_if_fail (handles != NULL);
+
+       priv = GET_PRIV (group);
+
+       if (!tp_chan_iface_group_add_members (priv->group_iface,
+                                             handles,
+                                             message,
+                                             &error)) {
+               gossip_debug (DEBUG_DOMAIN,
+                             "Failed to add members: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+       }
+}
+
+void
+gossip_telepathy_group_add_member (GossipTelepathyGroup *group,
+                                  guint                 handle,
+                                  const gchar          *message)
+{
+       GArray *handles;
+
+       handles = g_array_new (FALSE, FALSE, sizeof (guint));
+       g_array_append_val (handles, handle);
+
+       gossip_telepathy_group_add_members (group, handles, message);
+
+       g_array_free (handles, TRUE);
+}
+
+void
+gossip_telepathy_group_remove_members (GossipTelepathyGroup *group,
+                                      GArray               *handles,
+                                      const gchar          *message)
+{
+       GossipTelepathyGroupPriv *priv;
+       GError                   *error = NULL;
+
+       g_return_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group));
+
+       priv = GET_PRIV (group);
+
+       if (!tp_chan_iface_group_remove_members (priv->group_iface,
+                                                handles,
+                                                message,
+                                                &error)) {
+               gossip_debug (DEBUG_DOMAIN, 
+                             "Failed to remove members: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+       }
+}
+
+void
+gossip_telepathy_group_remove_member (GossipTelepathyGroup *group,
+                                     guint                 handle,
+                                     const gchar          *message)
+{
+       GArray *handles;
+
+       g_return_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group));
+
+       handles = g_array_new (FALSE, FALSE, sizeof (guint));
+       g_array_append_val (handles, handle);
+
+       gossip_telepathy_group_remove_members (group, handles, message);
+
+       g_array_free (handles, TRUE);
+}
+
+GArray *
+gossip_telepathy_group_get_members (GossipTelepathyGroup *group)
+{
+       GossipTelepathyGroupPriv *priv;
+       GArray                   *members;
+       GError                   *error = NULL;
+
+       g_return_val_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group), NULL);
+
+       priv = GET_PRIV (group);
+
+       if (!tp_chan_iface_group_get_members (priv->group_iface,
+                                             &members,
+                                             &error)) {
+               gossip_debug (DEBUG_DOMAIN, 
+                             "Couldn't get members: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+               return NULL;
+       }
+
+       return members;
+}
+
+void
+gossip_telepathy_group_get_all_members (GossipTelepathyGroup  *group,
+                                       GArray               **members,
+                                       GArray               **local_pending,
+                                       GArray               **remote_pending)
+{
+       GossipTelepathyGroupPriv *priv;
+       GError                   *error = NULL;
+
+       g_return_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group));
+
+       priv = GET_PRIV (group);
+
+       if (!tp_chan_iface_group_get_all_members (priv->group_iface,
+                                                 members,
+                                                 local_pending,
+                                                 remote_pending,
+                                                 &error)) {
+               gossip_debug (DEBUG_DOMAIN,
+                             "Couldn't get all members: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+       }
+}
+
+GPtrArray *
+gossip_telepathy_group_get_local_pending_members_with_info (GossipTelepathyGroup  *group)
+{
+       GossipTelepathyGroupPriv *priv;
+       GPtrArray                *info = NULL;
+       GError                   *error = NULL;
+
+       g_return_val_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group), NULL);
+
+       priv = GET_PRIV (group);
+
+       if (!tp_chan_iface_group_get_local_pending_members_with_info (priv->group_iface,
+                                                                     &info,
+                                                                     &error)) {
+               gossip_debug (DEBUG_DOMAIN, 
+                             "GetLocalPendingMembersWithInfo failed: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+       }
+
+       return info;
+}
+
+static void
+telepathy_group_destroy_cb (DBusGProxy           *proxy,
+                           GossipTelepathyGroup *group)
+{
+       GossipTelepathyGroupPriv *priv;
+
+       priv = GET_PRIV (group);
+
+       g_object_unref (priv->group_iface);
+       g_object_unref (priv->tp_conn);
+       g_object_unref (priv->tp_chan);
+       priv->group_iface = NULL;
+       priv->tp_chan = NULL;
+       priv->tp_conn = NULL;
+}
+
+static void
+telepathy_group_members_changed_cb (DBusGProxy           *group_iface,
+                                   gchar                *message,
+                                   GArray               *added,
+                                   GArray               *removed,
+                                   GArray               *local_pending,
+                                   GArray               *remote_pending,
+                                   guint                 actor,
+                                   guint                 reason,
+                                   GossipTelepathyGroup *group)
+{
+       GossipTelepathyGroupPriv *priv;
+
+       priv = GET_PRIV (group);
+
+       /* emit signals */
+       if (added->len > 0) {
+               g_signal_emit (group, signals[MEMBERS_ADDED], 0, 
+                              added, actor, reason, message);
+       }
+       if (removed->len > 0) {
+               g_signal_emit (group, signals[MEMBERS_REMOVED], 0, 
+                              removed, actor, reason, message);
+       }
+       if (local_pending->len > 0) {
+               g_signal_emit (group, signals[LOCAL_PENDING], 0,
+                              local_pending, actor, reason, message);
+       }
+       if (remote_pending->len > 0) {
+               g_signal_emit (group, signals[REMOTE_PENDING], 0,
+                              remote_pending, actor, reason, message);
+       }
+}
+
+const gchar *
+gossip_telepathy_group_get_name (GossipTelepathyGroup *group)
+{
+       TelepathyHandleType  handle_type;
+       guint                channel_handle;
+       GArray              *group_handles;
+       gchar              **group_names;
+       GError              *error = NULL;
+
+       GossipTelepathyGroupPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group), NULL);
+
+       priv = GET_PRIV (group);
+
+       /* Lazy initialisation */
+       if (priv->group_name) {
+               return priv->group_name;
+       }
+
+       if (!tp_chan_get_handle (DBUS_G_PROXY (priv->tp_chan),
+                                &handle_type,
+                                &channel_handle,
+                                &error)) {
+               gossip_debug (DEBUG_DOMAIN, 
+                             "Couldn't retreive channel handle for group: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+               return NULL;
+       }
+
+       group_handles = g_array_new (FALSE, FALSE, sizeof (gint));
+       g_array_append_val (group_handles, channel_handle);
+       if (!tp_conn_inspect_handles (DBUS_G_PROXY (priv->tp_conn),
+                                     handle_type,
+                                     group_handles,
+                                     &group_names,
+                                     &error)) {
+               gossip_debug (DEBUG_DOMAIN, 
+                             "Couldn't get group name: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+               g_array_free (group_handles, TRUE);
+               return NULL;
+       }
+
+       priv->group_name = *group_names;
+       g_array_free (group_handles, TRUE);
+       g_free (group_names);
+
+       return priv->group_name;
+}
+
+guint 
+gossip_telepathy_group_get_self_handle (GossipTelepathyGroup *group)
+{
+       GossipTelepathyGroupPriv *priv;
+       guint                     handle;
+       GError                   *error = NULL;
+
+       g_return_val_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group), 0 );
+
+       priv = GET_PRIV (group);
+
+       if (!tp_chan_iface_group_get_self_handle (priv->group_iface, &handle, &error)) {
+               gossip_debug (DEBUG_DOMAIN, 
+                             "Failed to get self handle: %s",
+                             error ? error->message : "No error given");
+               g_clear_error (&error);
+               return 0;
+       }
+
+       return handle;
+}
+
+const gchar *
+gossip_telepathy_group_get_object_path (GossipTelepathyGroup *group)
+{
+       GossipTelepathyGroupPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_TELEPATHY_GROUP (group), NULL);
+
+       priv = GET_PRIV (group);
+
+       return dbus_g_proxy_get_path (DBUS_G_PROXY (priv->tp_chan));
+}
+
+gboolean
+gossip_telepathy_group_is_member (GossipTelepathyGroup *group,
+                                 guint                 handle)
+{
+       GArray   *members;
+       guint     i;
+       gboolean  found = FALSE;
+
+       members = gossip_telepathy_group_get_members (group);
+       for (i = 0; i < members->len; i++) {
+               if (g_array_index (members, guint, i) == handle) {
+                       found = TRUE;
+                       break;
+               }
+       }
+       g_array_free (members, TRUE);
+       
+       return found;
+}
+
diff --git a/libempathy/gossip-telepathy-group.h b/libempathy/gossip-telepathy-group.h
new file mode 100644 (file)
index 0000000..9c61bdb
--- /dev/null
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006 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.
+ */
+
+#ifndef __GOSSIP_TELEPATHY_GROUP_H__
+#define __GOSSIP_TELEPATHY_GROUP_H__
+
+#include <glib.h>
+
+#include <libtelepathy/tp-chan.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_TELEPATHY_GROUP         (gossip_telepathy_group_get_type ())
+#define GOSSIP_TELEPATHY_GROUP(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_TELEPATHY_GROUP, GossipTelepathyGroup))
+#define GOSSIP_TELEPATHY_GROUP_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), GOSSIP_TYPE_TELEPATHY_GROUP, GossipTelepathyGroupClass))
+#define GOSSIP_IS_TELEPATHY_GROUP(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_TELEPATHY_GROUP))
+#define GOSSIP_IS_TELEPATHY_GROUP_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_TELEPATHY_GROUP))
+#define GOSSIP_TELEPATHY_GROUP_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_TELEPATHY_GROUP, GossipTelepathyGroupClass))
+
+typedef struct _GossipTelepathyGroup      GossipTelepathyGroup;
+typedef struct _GossipTelepathyGroupClass GossipTelepathyGroupClass;
+typedef struct _GossipTelepathyGroupPriv  GossipTelepathyGroupPriv;
+
+struct _GossipTelepathyGroup {
+       GObject      parent;
+};
+
+struct _GossipTelepathyGroupClass {
+       GObjectClass parent_class;
+};
+
+GType                 gossip_telepathy_group_get_type        (void) G_GNUC_CONST;
+GossipTelepathyGroup *gossip_telepathy_group_new             (TpChan                *tp_chan,
+                                                             TpConn                *tp_conn);
+void                  gossip_telepathy_group_add_members     (GossipTelepathyGroup  *group,
+                                                             GArray                *handles,
+                                                             const gchar           *message);
+void                  gossip_telepathy_group_add_member      (GossipTelepathyGroup  *group,
+                                                             guint                  handle,
+                                                             const gchar           *message);
+void                  gossip_telepathy_group_remove_members  (GossipTelepathyGroup  *group,
+                                                             GArray                *handle,
+                                                             const gchar           *message);
+void                  gossip_telepathy_group_remove_member   (GossipTelepathyGroup  *group,
+                                                             guint                  handle,
+                                                             const gchar           *message);
+GArray *              gossip_telepathy_group_get_members     (GossipTelepathyGroup  *group);
+void                  gossip_telepathy_group_get_all_members (GossipTelepathyGroup  *group,
+                                                             GArray               **members,
+                                                             GArray               **local_pending,
+                                                             GArray               **remote_pending);
+GPtrArray *           gossip_telepathy_group_get_local_pending_members_with_info
+                                                            (GossipTelepathyGroup  *group);
+const gchar *         gossip_telepathy_group_get_name        (GossipTelepathyGroup  *group);
+guint                 gossip_telepathy_group_get_self_handle (GossipTelepathyGroup  *group);
+const gchar *         gossip_telepathy_group_get_object_path (GossipTelepathyGroup  *group);
+gboolean              gossip_telepathy_group_is_member       (GossipTelepathyGroup  *group,
+                                                             guint                  handle);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_TELEPATHY_GROUP_H__ */
diff --git a/libempathy/gossip-time.c b/libempathy/gossip-time.c
new file mode 100644 (file)
index 0000000..a195635
--- /dev/null
@@ -0,0 +1,124 @@
+/* -*- 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: Richard Hult <richard@imendio.com>
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gossip-time.h"
+
+/* Note: GossipTime is always in UTC. */
+
+GossipTime
+gossip_time_get_current (void)
+{
+       return time (NULL);
+}
+
+time_t
+gossip_time_get_local_time (struct tm *tm)
+{
+       const gchar *timezone;
+       time_t       t;
+       
+       timezone = g_getenv ("TZ");
+       g_setenv ("TZ", "", TRUE);
+
+       tzset ();
+
+       t = mktime (tm);
+
+       if (timezone) {
+               g_setenv ("TZ", timezone, TRUE);
+       } else {
+               g_unsetenv ("TZ");
+       }
+
+       tzset ();
+
+       return t;
+}
+
+/* The format is: "20021209T23:51:30" and is in UTC. 0 is returned on
+ * failure. The alternative format "20021209" is also accepted.
+ */
+GossipTime
+gossip_time_parse (const gchar *str)
+{
+       struct tm tm;
+       gint      year, month;
+       gint      n_parsed;
+
+       memset (&tm, 0, sizeof (struct tm));
+
+       n_parsed = sscanf (str, "%4d%2d%2dT%2d:%2d:%2d",
+                   &year, &month, &tm.tm_mday, &tm.tm_hour,
+                          &tm.tm_min, &tm.tm_sec);
+       if (n_parsed != 3 && n_parsed != 6) {
+               return 0;
+       }
+
+       tm.tm_year = year - 1900;
+       tm.tm_mon = month - 1;
+       tm.tm_isdst = -1;
+
+       return gossip_time_get_local_time (&tm);
+}
+
+/* Converts the UTC timestamp to a string, also in UTC. Returns NULL on failure. */
+gchar *
+gossip_time_to_string_utc (GossipTime   t,
+                          const gchar *format)
+{
+       gchar      stamp[128];
+       struct tm *tm;
+
+       g_return_val_if_fail (format != NULL, NULL);
+
+       tm = gmtime (&t);
+       if (strftime (stamp, sizeof (stamp), format, tm) == 0) {
+               return NULL;
+       }
+
+       return g_strdup (stamp);
+}
+
+/* Converts the UTC timestamp to a string, in local time. Returns NULL on failure. */
+gchar *
+gossip_time_to_string_local (GossipTime   t,
+                            const gchar *format)
+{
+       gchar      stamp[128];
+       struct tm *tm;
+
+       g_return_val_if_fail (format != NULL, NULL);
+
+       tm = localtime (&t);
+       if (strftime (stamp, sizeof (stamp), format, tm) == 0) {
+               return NULL;
+       }
+
+       return g_strdup (stamp);
+}
+
diff --git a/libempathy/gossip-time.h b/libempathy/gossip-time.h
new file mode 100644 (file)
index 0000000..06057aa
--- /dev/null
@@ -0,0 +1,50 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004 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.
+ */
+
+#ifndef __GOSSIP_TIME_H__
+#define __GOSSIP_TIME_H__
+
+#define __USE_XOPEN
+#include <time.h>
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TIME_FORMAT_DISPLAY_SHORT "%H:%M"
+#define GOSSIP_TIME_FORMAT_DISPLAY_LONG  "%a %d %b %Y"
+
+/* Note: Always in UTC. */
+typedef long GossipTime;
+
+GossipTime  gossip_time_get_current     (void);
+time_t      gossip_time_get_local_time  (struct tm   *tm);
+GossipTime  gossip_time_parse           (const gchar *str);
+GossipTime  gossip_time_parse_format    (const gchar *str,
+                                        const gchar *format);
+gchar      *gossip_time_to_string_utc   (GossipTime   t,
+                                        const gchar *format);
+gchar      *gossip_time_to_string_local (GossipTime   t,
+                                        const gchar *format);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_TIME_H__ */
+
diff --git a/libempathy/gossip-utils.c b/libempathy/gossip-utils.c
new file mode 100644 (file)
index 0000000..24f5cd4
--- /dev/null
@@ -0,0 +1,447 @@
+/* -*- 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: Richard Hult <richard@imendio.com>
+ *          Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <regex.h>
+
+#include <glib/gi18n.h>
+
+#include <libxml/uri.h>
+#include <libmissioncontrol/mc-account.h>
+
+#include "gossip-debug.h"
+#include "gossip-utils.h"
+#include "gossip-paths.h"
+#include "empathy-session.h"
+#include "empathy-contact-manager.h"
+
+#define DEBUG_DOMAIN "Utils"
+
+static void regex_init (void);
+
+gchar *
+gossip_substring (const gchar *str,
+                 gint         start,
+                 gint         end)
+{
+       return g_strndup (str + start, end - start);
+}
+
+/*
+ * Regular Expression code to match urls.
+ */
+#define USERCHARS "-A-Za-z0-9"
+#define PASSCHARS "-A-Za-z0-9,?;.:/!%$^*&~\"#'"
+#define HOSTCHARS "-A-Za-z0-9"
+#define PATHCHARS "-A-Za-z0-9_$.+!*(),;:@&=?/~#%"
+#define SCHEME    "(news:|telnet:|nntp:|file:/|https?:|ftps?:|webcal:)"
+#define USER      "[" USERCHARS "]+(:["PASSCHARS "]+)?"
+#define URLPATH   "/[" PATHCHARS "]*[^]'.}>) \t\r\n,\\\"]"
+
+static regex_t dingus[GOSSIP_REGEX_ALL];
+
+static void
+regex_init (void)
+{
+       static gboolean  inited = FALSE;
+       const gchar     *expression;
+       gint             i;
+
+       if (inited) {
+               return;
+       }
+
+       for (i = 0; i < GOSSIP_REGEX_ALL; i++) {
+               switch (i) {
+               case GOSSIP_REGEX_AS_IS:
+                       expression =
+                               SCHEME "//(" USER "@)?[" HOSTCHARS ".]+"
+                               "(:[0-9]+)?(" URLPATH ")?";
+                       break;
+               case GOSSIP_REGEX_BROWSER:
+                       expression =
+                               "(www|ftp)[" HOSTCHARS "]*\\.[" HOSTCHARS ".]+"
+                               "(:[0-9]+)?(" URLPATH ")?";
+                       break;
+               case GOSSIP_REGEX_EMAIL:
+                       expression =
+                               "(mailto:)?[a-z0-9][a-z0-9.-]*@[a-z0-9]"
+                               "[a-z0-9-]*(\\.[a-z0-9][a-z0-9-]*)+";
+                       break;
+               case GOSSIP_REGEX_OTHER:
+                       expression =
+                               "news:[-A-Z\\^_a-z{|}~!\"#$%&'()*+,./0-9;:=?`]+"
+                               "@[" HOSTCHARS ".]+(:[0-9]+)?";
+                       break;
+               default:
+                       /* Silence the compiler. */
+                       expression = NULL;
+                       continue;
+               }
+
+               memset (&dingus[i], 0, sizeof (regex_t));
+               regcomp (&dingus[i], expression, REG_EXTENDED | REG_ICASE);
+       }
+
+       inited = TRUE;
+}
+
+gint
+gossip_regex_match (GossipRegExType  type,
+                   const gchar     *msg,
+                   GArray          *start,
+                   GArray          *end)
+{
+       regmatch_t matches[1];
+       gint       ret = 0;
+       gint       num_matches = 0;
+       gint       offset = 0;
+       gint       i;
+
+       g_return_val_if_fail (type >= 0 || type <= GOSSIP_REGEX_ALL, 0);
+
+       regex_init ();
+
+       while (!ret && type != GOSSIP_REGEX_ALL) {
+               ret = regexec (&dingus[type], msg + offset, 1, matches, 0);
+               if (ret == 0) {
+                       gint s;
+
+                       num_matches++;
+
+                       s = matches[0].rm_so + offset;
+                       offset = matches[0].rm_eo + offset;
+
+                       g_array_append_val (start, s);
+                       g_array_append_val (end, offset);
+               }
+       }
+
+       if (type != GOSSIP_REGEX_ALL) {
+               gossip_debug (DEBUG_DOMAIN,
+                             "Found %d matches for regex type:%d",
+                             num_matches, type);
+               return num_matches;
+       }
+
+       /* If GOSSIP_REGEX_ALL then we run ALL regex's on the string. */
+       for (i = 0; i < GOSSIP_REGEX_ALL; i++, ret = 0) {
+               while (!ret) {
+                       ret = regexec (&dingus[i], msg + offset, 1, matches, 0);
+                       if (ret == 0) {
+                               gint s;
+
+                               num_matches++;
+
+                               s = matches[0].rm_so + offset;
+                               offset = matches[0].rm_eo + offset;
+
+                               g_array_append_val (start, s);
+                               g_array_append_val (end, offset);
+                       }
+               }
+       }
+
+       gossip_debug (DEBUG_DOMAIN,
+                     "Found %d matches for ALL regex types",
+                     num_matches);
+
+       return num_matches;
+}
+
+gint
+gossip_strcasecmp (const gchar *s1,
+                  const gchar *s2)
+{
+       return gossip_strncasecmp (s1, s2, -1);
+}
+
+gint
+gossip_strncasecmp (const gchar *s1,
+                   const gchar *s2,
+                   gsize        n)
+{
+       gchar *u1, *u2;
+       gint   ret_val;
+
+       u1 = g_utf8_casefold (s1, n);
+       u2 = g_utf8_casefold (s2, n);
+
+       ret_val = g_utf8_collate (u1, u2);
+       g_free (u1);
+       g_free (u2);
+
+       return ret_val;
+}
+
+gboolean
+gossip_xml_validate (xmlDoc      *doc,
+                    const gchar *dtd_filename)
+{
+       gchar        *path, *escaped;
+       xmlValidCtxt  cvp;
+       xmlDtd       *dtd;
+       gboolean      ret;
+
+       path = gossip_paths_get_dtd_path (dtd_filename);
+
+       /* The list of valid chars is taken from libxml. */
+       escaped = xmlURIEscapeStr (path, ":@&=+$,/?;");
+
+       g_free (path);
+
+       memset (&cvp, 0, sizeof (cvp));
+       dtd = xmlParseDTD (NULL, escaped);
+       ret = xmlValidateDtd (&cvp, doc, dtd);
+
+       xmlFree (escaped);
+       xmlFreeDtd (dtd);
+
+       return ret;
+}
+
+xmlNodePtr
+gossip_xml_node_get_child (xmlNodePtr   node, 
+                          const gchar *child_name)
+{
+       xmlNodePtr l;
+
+        g_return_val_if_fail (node != NULL, NULL);
+        g_return_val_if_fail (child_name != NULL, NULL);
+
+       for (l = node->children; l; l = l->next) {
+               if (l->name && strcmp (l->name, child_name) == 0) {
+                       return l;
+               }
+       }
+
+       return NULL;
+}
+
+xmlChar *
+gossip_xml_node_get_child_content (xmlNodePtr   node, 
+                                  const gchar *child_name)
+{
+       xmlNodePtr l;
+
+        g_return_val_if_fail (node != NULL, NULL);
+        g_return_val_if_fail (child_name != NULL, NULL);
+
+       l = gossip_xml_node_get_child (node, child_name);
+       if (l) {
+               return xmlNodeGetContent (l);
+       }
+               
+       return NULL;
+}
+
+xmlNodePtr
+gossip_xml_node_find_child_prop_value (xmlNodePtr   node, 
+                                      const gchar *prop_name,
+                                      const gchar *prop_value)
+{
+       xmlNodePtr l;
+       xmlNodePtr found = NULL;
+
+        g_return_val_if_fail (node != NULL, NULL);
+        g_return_val_if_fail (prop_name != NULL, NULL);
+        g_return_val_if_fail (prop_value != NULL, NULL);
+
+       for (l = node->children; l && !found; l = l->next) {
+               xmlChar *prop;
+
+               if (!xmlHasProp (l, prop_name)) {
+                       continue;
+               }
+
+               prop = xmlGetProp (l, prop_name);
+               if (prop && strcmp (prop, prop_value) == 0) {
+                       found = l;
+               }
+               
+               xmlFree (prop);
+       }
+               
+       return found;
+}
+
+GType
+gossip_dbus_type_to_g_type (const gchar *dbus_type_string)
+{
+       if (dbus_type_string == NULL)
+               return G_TYPE_NONE;
+
+       if (dbus_type_string[0] == 's') {
+               return G_TYPE_STRING;
+       }
+       else if (dbus_type_string[0] == 'b') {
+               return G_TYPE_BOOLEAN;
+       }
+       else if (dbus_type_string[0] == 'q') {
+               return G_TYPE_UINT;
+       }
+       else if (dbus_type_string[0] == 'n') {
+               return G_TYPE_INT;
+       }
+
+       g_assert_not_reached ();
+       return G_TYPE_NONE;
+}
+
+const gchar *
+gossip_g_type_to_dbus_type (GType g_type)
+{
+       switch (g_type)
+       {
+       case G_TYPE_STRING:
+               return "s";
+       case G_TYPE_BOOLEAN:
+               return "b";
+       case G_TYPE_UINT:
+               return "q";
+       case G_TYPE_INT:
+               return "n";
+       default:
+               g_assert_not_reached ();
+       }
+
+       return NULL;
+}
+
+gchar *
+gossip_g_value_to_string (const GValue *value)
+{
+       gchar  *return_string = NULL;
+       GValue  string_g_value = {0, };
+
+       g_value_init (&string_g_value, G_TYPE_STRING);
+       g_value_transform (value, &string_g_value);
+       return_string = g_value_dup_string (&string_g_value);
+       g_value_unset (&string_g_value);
+
+       return return_string;
+}
+
+GValue *
+gossip_string_to_g_value (const gchar *str, GType type)
+{
+       GValue *g_value;
+
+       g_value = g_new0 (GValue, 1);
+       g_value_init (g_value, type);
+
+       switch (type) {
+       case G_TYPE_STRING:
+               g_value_set_string (g_value, str);
+               break;
+       case G_TYPE_BOOLEAN:
+               g_value_set_boolean (g_value, (str[0] == 'y' || str[0] == 'T'));
+               break;
+       case G_TYPE_UINT:
+               g_value_set_uint (g_value, atoi (str));
+               break;
+       case G_TYPE_INT:
+               g_value_set_int (g_value, atoi (str));
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return g_value;
+}
+
+gboolean
+gossip_g_value_equal (const GValue *value1,
+                     const GValue *value2)
+{
+       GType type;
+
+       g_return_val_if_fail (value1 != NULL, FALSE);
+       g_return_val_if_fail (value2 != NULL, FALSE);
+
+       type = G_VALUE_TYPE (value1);
+       if (type != G_VALUE_TYPE (value2)) {
+               return FALSE;
+       }
+
+       switch (type)
+       {
+       case G_TYPE_STRING: {
+               const gchar *str1;
+               const gchar *str2;
+
+               str1 = g_value_get_string (value1);
+               str2 = g_value_get_string (value2);
+               return (str1 && str2 && strcmp (str1, str2) == 0) ||
+                      (G_STR_EMPTY (str1) && G_STR_EMPTY (str2));
+       }
+       case G_TYPE_BOOLEAN:
+               return g_value_get_boolean (value1) == g_value_get_boolean (value2);
+       case G_TYPE_UINT:
+               return g_value_get_uint (value1) == g_value_get_uint (value2);
+       case G_TYPE_INT:
+               return g_value_get_int (value1) == g_value_get_int (value2);
+       default:
+               g_warning ("Unsupported GType in value comparaison");
+       }
+
+       return FALSE;
+}
+
+guint
+gossip_account_hash (gconstpointer key)
+{
+       return g_str_hash (mc_account_get_unique_name (MC_ACCOUNT (key)));
+}
+
+gboolean
+gossip_account_equal (gconstpointer a,
+                     gconstpointer b)
+{
+       const gchar *name_a;
+       const gchar *name_b;
+
+       name_a = mc_account_get_unique_name (MC_ACCOUNT (a));
+       name_b = mc_account_get_unique_name (MC_ACCOUNT (b));
+
+       return g_str_equal (name_a, name_b);
+}
+
+GossipContact *
+gossip_get_own_contact_from_contact (GossipContact  *contact)
+{
+       EmpathyContactManager *manager;
+       McAccount             *account;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+       manager = empathy_session_get_contact_manager ();
+       account = gossip_contact_get_account (contact);
+
+       return empathy_contact_manager_get_own (manager, account);
+}
+
diff --git a/libempathy/gossip-utils.h b/libempathy/gossip-utils.h
new file mode 100644 (file)
index 0000000..7f0ab90
--- /dev/null
@@ -0,0 +1,87 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-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.
+ */
+
+#ifndef __GOSSIP_UTILS_H__
+#define __GOSSIP_UTILS_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#include "gossip-contact.h"
+
+G_BEGIN_DECLS
+
+#define G_STR_EMPTY(x) ((x) == NULL || (x)[0] == '\0')
+
+typedef enum {
+       GOSSIP_REGEX_AS_IS,
+       GOSSIP_REGEX_BROWSER,
+       GOSSIP_REGEX_EMAIL,
+       GOSSIP_REGEX_OTHER,
+       GOSSIP_REGEX_ALL,
+} GossipRegExType;
+
+/* Regular expressions */
+gchar *      gossip_substring                      (const gchar     *str,
+                                                   gint             start,
+                                                   gint             end);
+gint         gossip_regex_match                    (GossipRegExType  type,
+                                                   const gchar     *msg,
+                                                   GArray          *start,
+                                                   GArray          *end);
+
+/* Strings */
+gint         gossip_strcasecmp                     (const gchar     *s1,
+                                                   const gchar     *s2);
+gint         gossip_strncasecmp                    (const gchar     *s1,
+                                                   const gchar     *s2,
+                                                   gsize            n);
+
+/* XML */
+gboolean     gossip_xml_validate                   (xmlDoc          *doc,
+                                                   const gchar     *dtd_filename);
+xmlNodePtr   gossip_xml_node_get_child             (xmlNodePtr       node,
+                                                   const gchar     *child_name);
+xmlChar *    gossip_xml_node_get_child_content     (xmlNodePtr       node,
+                                                   const gchar     *child_name);
+xmlNodePtr   gossip_xml_node_find_child_prop_value (xmlNodePtr       node,
+                                                   const gchar     *prop_name,
+                                                   const gchar     *prop_value);
+
+
+/* GValue/GType */
+GType        gossip_dbus_type_to_g_type            (const gchar     *dbus_type_string);
+const gchar *gossip_g_type_to_dbus_type            (GType            g_type);
+gchar *      gossip_g_value_to_string              (const GValue    *value);
+GValue *     gossip_string_to_g_value              (const gchar     *str,
+                                                   GType            type);
+gboolean     gossip_g_value_equal                  (const GValue    *value1,
+                                                   const GValue    *value2);
+
+guint        gossip_account_hash                    (gconstpointer   key);
+gboolean     gossip_account_equal                   (gconstpointer   a,
+                                                    gconstpointer   b);
+GossipContact * gossip_get_own_contact_from_contact (GossipContact  *contact);
+G_END_DECLS
+
+#endif /*  __GOSSIP_UTILS_H__ */
diff --git a/pixmaps/Makefile.am b/pixmaps/Makefile.am
new file mode 100644 (file)
index 0000000..11df65c
--- /dev/null
@@ -0,0 +1,30 @@
+imagedir = $(datadir)/empathy
+
+image_DATA =                           \
+       gossip-offline.png              \
+       gossip-available.png            \
+       gossip-busy.png                 \
+       gossip-away.png                 \
+       gossip-extended-away.png        \
+       gossip-pending.png              \
+       gossip-message.png              \
+       gossip-typing.png               \
+       gossip-group-message.png        \
+       vcard_16.png                    \
+       vcard_48.png
+
+gtk_update_icon_cache = gtk-update-icon-cache -f -t $(datadir)/icons/hicolor
+
+install-data-hook:
+       @-if test -z "$(DESTDIR)"; then \
+               echo "Updating Gtk icon cache."; \
+               $(gtk_update_icon_cache); \
+       else \
+               echo "*** Icon cache not updated.  After install, run this:"; \
+               echo "***   $(gtk_update_icon_cache)"; \
+       fi
+
+EXTRA_DIST =           \
+       $(image_DATA)
+
+
diff --git a/pixmaps/gossip-available.png b/pixmaps/gossip-available.png
new file mode 100644 (file)
index 0000000..c2d074f
Binary files /dev/null and b/pixmaps/gossip-available.png differ
diff --git a/pixmaps/gossip-away.png b/pixmaps/gossip-away.png
new file mode 100644 (file)
index 0000000..ba9ed23
Binary files /dev/null and b/pixmaps/gossip-away.png differ
diff --git a/pixmaps/gossip-busy.png b/pixmaps/gossip-busy.png
new file mode 100644 (file)
index 0000000..190c5ae
Binary files /dev/null and b/pixmaps/gossip-busy.png differ
diff --git a/pixmaps/gossip-extended-away.png b/pixmaps/gossip-extended-away.png
new file mode 100644 (file)
index 0000000..59dbe08
Binary files /dev/null and b/pixmaps/gossip-extended-away.png differ
diff --git a/pixmaps/gossip-group-message.png b/pixmaps/gossip-group-message.png
new file mode 100644 (file)
index 0000000..ed2d33b
Binary files /dev/null and b/pixmaps/gossip-group-message.png differ
diff --git a/pixmaps/gossip-message.png b/pixmaps/gossip-message.png
new file mode 100644 (file)
index 0000000..84c3cef
Binary files /dev/null and b/pixmaps/gossip-message.png differ
diff --git a/pixmaps/gossip-offline.png b/pixmaps/gossip-offline.png
new file mode 100644 (file)
index 0000000..528eae4
Binary files /dev/null and b/pixmaps/gossip-offline.png differ
diff --git a/pixmaps/gossip-pending.png b/pixmaps/gossip-pending.png
new file mode 100644 (file)
index 0000000..f48de81
Binary files /dev/null and b/pixmaps/gossip-pending.png differ
diff --git a/pixmaps/gossip-typing.png b/pixmaps/gossip-typing.png
new file mode 100644 (file)
index 0000000..defad7b
Binary files /dev/null and b/pixmaps/gossip-typing.png differ
diff --git a/pixmaps/vcard_16.png b/pixmaps/vcard_16.png
new file mode 100644 (file)
index 0000000..0dfdb77
Binary files /dev/null and b/pixmaps/vcard_16.png differ
diff --git a/pixmaps/vcard_48.png b/pixmaps/vcard_48.png
new file mode 100644 (file)
index 0000000..776006c
Binary files /dev/null and b/pixmaps/vcard_48.png differ
diff --git a/po/ChangeLog b/po/ChangeLog
new file mode 100644 (file)
index 0000000..703e508
--- /dev/null
@@ -0,0 +1,3 @@
+2007-04-25  Xavier Claessens  <xclaesse@gmail.com>
+
+       * Initial version       
diff --git a/po/Makefile.in.in b/po/Makefile.in.in
new file mode 100644 (file)
index 0000000..40c8833
--- /dev/null
@@ -0,0 +1,218 @@
+# Makefile for program source directory in GNU NLS utilities package.
+# Copyright (C) 1995, 1996, 1997 by Ulrich Drepper <drepper@gnu.ai.mit.edu>
+#
+# This file file be copied and used freely without restrictions.  It can
+# be used in projects which are not available under the GNU Public License
+# but which still want to provide support for the GNU gettext functionality.
+# Please note that the actual code is *not* freely available.
+#
+# - Modified by Owen Taylor <otaylor@redhat.com> to use GETTEXT_PACKAGE
+#   instead of PACKAGE and to look for po2tbl in ./ not in intl/
+#
+# - Modified by jacob berkman <jacob@ximian.com> to install
+#   Makefile.in.in and po2tbl.sed.in for use with glib-gettextize
+#
+# - Modified by Rodney Dawes <dobey@novell.com> for use with intltool
+#
+# We have the following line for use by intltoolize:
+# INTLTOOL_MAKEFILE
+
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+PACKAGE = @PACKAGE@
+VERSION = @VERSION@
+
+SHELL = /bin/sh
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+top_builddir = @top_builddir@
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datadir = @datadir@
+datarootdir = @datarootdir@
+libdir = @libdir@
+DATADIRNAME = @DATADIRNAME@
+itlocaledir = $(prefix)/$(DATADIRNAME)/locale
+subdir = po
+install_sh = @install_sh@
+# Automake >= 1.8 provides @mkdir_p@.
+# Until it can be supposed, use the safe fallback:
+mkdir_p = $(install_sh) -d
+
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+
+GMSGFMT = @GMSGFMT@
+MSGFMT = @MSGFMT@
+XGETTEXT = @XGETTEXT@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+MSGMERGE = INTLTOOL_EXTRACT=$(INTLTOOL_EXTRACT) srcdir=$(srcdir) $(INTLTOOL_UPDATE) --gettext-package $(GETTEXT_PACKAGE) --dist
+GENPOT   = INTLTOOL_EXTRACT=$(INTLTOOL_EXTRACT) srcdir=$(srcdir) $(INTLTOOL_UPDATE) --gettext-package $(GETTEXT_PACKAGE) --pot
+
+ALL_LINGUAS = @ALL_LINGUAS@
+
+PO_LINGUAS=$(shell if test -r $(srcdir)/LINGUAS; then grep -v "^\#" $(srcdir)/LINGUAS; fi)
+
+USER_LINGUAS=$(shell if test -n "$(LINGUAS)"; then LLINGUAS="$(LINGUAS)"; ALINGUAS="$(ALL_LINGUAS)"; for lang in $$LLINGUAS; do if test -n "`grep ^$$lang$$ $(srcdir)/LINGUAS`" -o -n "`echo $$ALINGUAS|grep ' ?$$lang ?'`"; then printf "$$lang "; fi; done; fi)
+
+USE_LINGUAS=$(shell if test -n "$(USER_LINGUAS)"; then LLINGUAS="$(USER_LINGUAS)"; else if test -n "$(PO_LINGUAS)"; then LLINGUAS="$(PO_LINGUAS)"; else LLINGUAS="$(ALL_LINGUAS)"; fi; fi; for lang in $$LLINGUAS; do printf "$$lang "; done)
+
+POFILES=$(shell LINGUAS="$(USE_LINGUAS)"; for lang in $$LINGUAS; do printf "$$lang.po "; done)
+
+DISTFILES = ChangeLog Makefile.in.in POTFILES.in $(POFILES)
+EXTRA_DISTFILES = POTFILES.skip Makevars LINGUAS
+
+POTFILES = \
+# This comment gets stripped out
+
+CATALOGS=$(shell LINGUAS="$(USE_LINGUAS)"; for lang in $$LINGUAS; do printf "$$lang.gmo "; done)
+
+.SUFFIXES:
+.SUFFIXES: .po .pox .gmo .mo .msg .cat
+
+.po.pox:
+       $(MAKE) $(GETTEXT_PACKAGE).pot
+       $(MSGMERGE) $< $(GETTEXT_PACKAGE).pot -o $*.pox
+
+.po.mo:
+       $(MSGFMT) -o $@ $<
+
+.po.gmo:
+       file=`echo $* | sed 's,.*/,,'`.gmo \
+         && rm -f $$file && $(GMSGFMT) -o $$file $<
+
+.po.cat:
+       sed -f ../intl/po2msg.sed < $< > $*.msg \
+         && rm -f $@ && gencat $@ $*.msg
+
+
+all: all-@USE_NLS@
+
+all-yes: $(CATALOGS)
+all-no:
+
+$(GETTEXT_PACKAGE).pot: $(POTFILES)
+       $(GENPOT)
+
+install: install-data
+install-data: install-data-@USE_NLS@
+install-data-no: all
+install-data-yes: all
+       $(mkdir_p) $(DESTDIR)$(itlocaledir)
+       linguas="$(USE_LINGUAS)"; \
+       for lang in $$linguas; do \
+         dir=$(DESTDIR)$(itlocaledir)/$$lang/LC_MESSAGES; \
+         $(mkdir_p) $$dir; \
+         if test -r $$lang.gmo; then \
+           $(INSTALL_DATA) $$lang.gmo $$dir/$(GETTEXT_PACKAGE).mo; \
+           echo "installing $$lang.gmo as $$dir/$(GETTEXT_PACKAGE).mo"; \
+         else \
+           $(INSTALL_DATA) $(srcdir)/$$lang.gmo $$dir/$(GETTEXT_PACKAGE).mo; \
+           echo "installing $(srcdir)/$$lang.gmo as" \
+                "$$dir/$(GETTEXT_PACKAGE).mo"; \
+         fi; \
+         if test -r $$lang.gmo.m; then \
+           $(INSTALL_DATA) $$lang.gmo.m $$dir/$(GETTEXT_PACKAGE).mo.m; \
+           echo "installing $$lang.gmo.m as $$dir/$(GETTEXT_PACKAGE).mo.m"; \
+         else \
+           if test -r $(srcdir)/$$lang.gmo.m ; then \
+             $(INSTALL_DATA) $(srcdir)/$$lang.gmo.m \
+               $$dir/$(GETTEXT_PACKAGE).mo.m; \
+             echo "installing $(srcdir)/$$lang.gmo.m as" \
+                  "$$dir/$(GETTEXT_PACKAGE).mo.m"; \
+           else \
+             true; \
+           fi; \
+         fi; \
+       done
+
+# Empty stubs to satisfy archaic automake needs
+dvi info tags TAGS ID:
+
+# Define this as empty until I found a useful application.
+installcheck:
+
+uninstall:
+       linguas="$(USE_LINGUAS)"; \
+       for lang in $$linguas; do \
+         rm -f $(DESTDIR)$(itlocaledir)/$$lang/LC_MESSAGES/$(GETTEXT_PACKAGE).mo; \
+         rm -f $(DESTDIR)$(itlocaledir)/$$lang/LC_MESSAGES/$(GETTEXT_PACKAGE).mo.m; \
+       done
+
+check: all $(GETTEXT_PACKAGE).pot
+       rm -f missing notexist
+       srcdir=$(srcdir) $(INTLTOOL_UPDATE) -m
+       if [ -r missing -o -r notexist ]; then \
+         exit 1; \
+       fi
+
+mostlyclean:
+       rm -f *.pox $(GETTEXT_PACKAGE).pot *.old.po cat-id-tbl.tmp
+       rm -f .intltool-merge-cache
+
+clean: mostlyclean
+
+distclean: clean
+       rm -f Makefile Makefile.in POTFILES stamp-it
+       rm -f *.mo *.msg *.cat *.cat.m *.gmo
+
+maintainer-clean: distclean
+       @echo "This command is intended for maintainers to use;"
+       @echo "it deletes files that may require special tools to rebuild."
+       rm -f Makefile.in.in
+
+distdir = ../$(PACKAGE)-$(VERSION)/$(subdir)
+dist distdir: $(DISTFILES)
+       dists="$(DISTFILES)"; \
+       extra_dists="$(EXTRA_DISTFILES)"; \
+       for file in $$extra_dists; do \
+         test -f $(srcdir)/$$file && dists="$$dists $(srcdir)/$$file"; \
+       done; \
+       for file in $$dists; do \
+         test -f $$file || file="$(srcdir)/$$file"; \
+         ln $$file $(distdir) 2> /dev/null \
+           || cp -p $$file $(distdir); \
+       done
+
+update-po: Makefile
+       $(MAKE) $(GETTEXT_PACKAGE).pot
+       tmpdir=`pwd`; \
+       linguas="$(USE_LINGUAS)"; \
+       for lang in $$linguas; do \
+         echo "$$lang:"; \
+         result="`$(MSGMERGE) -o $$tmpdir/$$lang.new.po $$lang`"; \
+         if $$result; then \
+           if cmp $(srcdir)/$$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \
+             rm -f $$tmpdir/$$lang.new.po; \
+            else \
+             if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \
+               :; \
+             else \
+               echo "msgmerge for $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \
+               rm -f $$tmpdir/$$lang.new.po; \
+               exit 1; \
+             fi; \
+           fi; \
+         else \
+           echo "msgmerge for $$lang.gmo failed!"; \
+           rm -f $$tmpdir/$$lang.new.po; \
+         fi; \
+       done
+
+Makefile POTFILES: stamp-it
+       @if test ! -f $@; then \
+         rm -f stamp-it; \
+         $(MAKE) stamp-it; \
+       fi
+
+stamp-it: Makefile.in.in $(top_builddir)/config.status POTFILES.in
+       cd $(top_builddir) \
+         && CONFIG_FILES=$(subdir)/Makefile.in CONFIG_HEADERS= CONFIG_LINKS= \
+              $(SHELL) ./config.status
+
+# Tell versions [3.59,3.63) of GNU make not to export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644 (file)
index 0000000..3b7c299
--- /dev/null
@@ -0,0 +1,20 @@
+# List of source files containing translatable strings.
+# Please keep this file sorted alphabetically.
+
+
+libempathy/gossip-contact.c
+libempathy/gossip-presence.c
+
+libempathy-gtk/empathy.schemas.in
+libempathy-gtk/empathy-chat.glade
+libempathy-gtk/empathy-accounts.glade
+libempathy-gtk/gossip-accounts-dialog.c
+libempathy-gtk/gossip-account-widget-generic.c
+libempathy-gtk/gossip-chat.c
+libempathy-gtk/gossip-chat-view.c
+libempathy-gtk/gossip-chat-window.c
+libempathy-gtk/gossip-contact-list.c
+libempathy-gtk/gossip-preferences.c
+libempathy-gtk/gossip-private-chat.c
+libempathy-gtk/gossip-theme-manager.c
+
diff --git a/profiles/Makefile.am b/profiles/Makefile.am
new file mode 100644 (file)
index 0000000..2536260
--- /dev/null
@@ -0,0 +1,8 @@
+profiledir = $(datadir)/mission-control/profiles
+
+profile_DATA =                                 \
+       jabber.profile                  \
+       msn.profile                     \
+       gtalk.profile
+
+EXTRA_DIST = $(profile_DATA)
diff --git a/profiles/gtalk.profile b/profiles/gtalk.profile
new file mode 100644 (file)
index 0000000..c70bfa8
--- /dev/null
@@ -0,0 +1,14 @@
+[Profile]
+Manager=gabble
+Protocol=jabber
+DisplayName=Google Talk
+IconName = im-jabber
+ConfigurationUI = jabber
+DefaultAccountDomain = gmail.com, googlemail.com
+Default-server = talk.google.com
+Default-port = 5223
+Default-old-ssl = 1
+Default-fallback-conference-server = conference.jabber.org
+Default-randomize-resource = 1
+Default-ignore-ssl-errors = 1
+SupportsInvisible = 0
diff --git a/profiles/jabber.profile b/profiles/jabber.profile
new file mode 100644 (file)
index 0000000..811132c
--- /dev/null
@@ -0,0 +1,8 @@
+[Profile]
+Manager=gabble
+Protocol=jabber
+DisplayName=Jabber
+IconName = im-jabber
+ConfigurationUI = jabber
+DefaultAccountDomain = jabber.org
+SupportsInvisible = 0
diff --git a/profiles/msn.profile b/profiles/msn.profile
new file mode 100644 (file)
index 0000000..599204c
--- /dev/null
@@ -0,0 +1,7 @@
+[Profile]
+Manager=butterfly
+Protocol=msn
+DisplayName=MSN
+IconName = im-msn
+ConfigurationUI = msn
+SupportsInvisible = 1