aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS4
-rw-r--r--CONTRIBUTORS17
-rw-r--r--COPYING340
-rw-r--r--ChangeLog4
-rw-r--r--MAINTAINERS1
-rw-r--r--Makefile.am24
-rw-r--r--NEWS0
-rw-r--r--README15
-rw-r--r--TODO20
-rw-r--r--accounts/Makefile.am19
-rw-r--r--accounts/empathy-accounts-main.c59
-rw-r--r--acinclude.m465
-rw-r--r--autogen.sh174
-rw-r--r--chat/Makefile.am40
-rw-r--r--chat/empathy-chat-main.c93
-rw-r--r--chat/empathy-chat.chandler5
-rw-r--r--chat/org.gnome.Empathy.Chat.service.in3
-rw-r--r--configure.ac75
-rw-r--r--contact-list/Makefile.am20
-rw-r--r--contact-list/empathy-contact-list-main.c94
-rw-r--r--launcher/Makefile.am12
-rw-r--r--launcher/empathy-launcher.c47
-rw-r--r--libempathy-gtk/Makefile.am68
-rw-r--r--libempathy-gtk/empathy-accounts.glade843
-rw-r--r--libempathy-gtk/empathy-chat.glade700
-rw-r--r--libempathy-gtk/empathy.schemas.in237
-rw-r--r--libempathy-gtk/gossip-account-widget-generic.c309
-rw-r--r--libempathy-gtk/gossip-account-widget-generic.h38
-rw-r--r--libempathy-gtk/gossip-accounts-dialog.c1032
-rw-r--r--libempathy-gtk/gossip-accounts-dialog.h34
-rw-r--r--libempathy-gtk/gossip-cell-renderer-expander.c482
-rw-r--r--libempathy-gtk/gossip-cell-renderer-expander.h59
-rw-r--r--libempathy-gtk/gossip-cell-renderer-text.c368
-rw-r--r--libempathy-gtk/gossip-cell-renderer-text.h56
-rw-r--r--libempathy-gtk/gossip-chat-manager.c327
-rw-r--r--libempathy-gtk/gossip-chat-manager.h65
-rw-r--r--libempathy-gtk/gossip-chat-manager.loT7
-rw-r--r--libempathy-gtk/gossip-chat-view.c2151
-rw-r--r--libempathy-gtk/gossip-chat-view.h134
-rw-r--r--libempathy-gtk/gossip-chat-window.c1894
-rw-r--r--libempathy-gtk/gossip-chat-window.h74
-rw-r--r--libempathy-gtk/gossip-chat.c1295
-rw-r--r--libempathy-gtk/gossip-chat.h139
-rw-r--r--libempathy-gtk/gossip-contact-groups.c286
-rw-r--r--libempathy-gtk/gossip-contact-groups.dtd17
-rw-r--r--libempathy-gtk/gossip-contact-groups.h38
-rw-r--r--libempathy-gtk/gossip-contact-list.c2419
-rw-r--r--libempathy-gtk/gossip-contact-list.h72
-rw-r--r--libempathy-gtk/gossip-preferences.c885
-rw-r--r--libempathy-gtk/gossip-preferences.h55
-rw-r--r--libempathy-gtk/gossip-private-chat.c495
-rw-r--r--libempathy-gtk/gossip-private-chat.h62
-rw-r--r--libempathy-gtk/gossip-profile-chooser.c102
-rw-r--r--libempathy-gtk/gossip-profile-chooser.h34
-rw-r--r--libempathy-gtk/gossip-spell.c457
-rw-r--r--libempathy-gtk/gossip-spell.h39
-rw-r--r--libempathy-gtk/gossip-stock.c105
-rw-r--r--libempathy-gtk/gossip-stock.h60
-rw-r--r--libempathy-gtk/gossip-theme-manager.c1045
-rw-r--r--libempathy-gtk/gossip-theme-manager.h64
-rw-r--r--libempathy-gtk/gossip-ui-utils.c1360
-rw-r--r--libempathy-gtk/gossip-ui-utils.h115
-rw-r--r--libempathy/Makefile.am54
-rw-r--r--libempathy/empathy-chandler.c150
-rw-r--r--libempathy/empathy-chandler.h53
-rw-r--r--libempathy/empathy-chandler.xml13
-rw-r--r--libempathy/empathy-contact-list.c1793
-rw-r--r--libempathy/empathy-contact-list.h76
-rw-r--r--libempathy/empathy-contact-manager.c550
-rw-r--r--libempathy/empathy-contact-manager.h77
-rw-r--r--libempathy/empathy-marshal-main.c2
-rw-r--r--libempathy/empathy-marshal.list3
-rw-r--r--libempathy/empathy-session.c161
-rw-r--r--libempathy/empathy-session.h38
-rw-r--r--libempathy/empathy-tp-chat.c474
-rw-r--r--libempathy/empathy-tp-chat.h75
-rw-r--r--libempathy/gossip-avatar.c86
-rw-r--r--libempathy/gossip-avatar.h48
-rw-r--r--libempathy/gossip-conf.c382
-rw-r--r--libempathy/gossip-conf.h87
-rw-r--r--libempathy/gossip-contact.c815
-rw-r--r--libempathy/gossip-contact.h102
-rw-r--r--libempathy/gossip-debug.c92
-rw-r--r--libempathy/gossip-debug.h53
-rw-r--r--libempathy/gossip-message.c365
-rw-r--r--libempathy/gossip-message.h75
-rw-r--r--libempathy/gossip-paths.c51
-rw-r--r--libempathy/gossip-paths.h36
-rw-r--r--libempathy/gossip-presence.c441
-rw-r--r--libempathy/gossip-presence.h84
-rw-r--r--libempathy/gossip-telepathy-group.c496
-rw-r--r--libempathy/gossip-telepathy-group.h79
-rw-r--r--libempathy/gossip-time.c124
-rw-r--r--libempathy/gossip-time.h50
-rw-r--r--libempathy/gossip-utils.c447
-rw-r--r--libempathy/gossip-utils.h87
-rw-r--r--pixmaps/Makefile.am30
-rw-r--r--pixmaps/gossip-available.pngbin0 -> 741 bytes
-rw-r--r--pixmaps/gossip-away.pngbin0 -> 609 bytes
-rw-r--r--pixmaps/gossip-busy.pngbin0 -> 888 bytes
-rw-r--r--pixmaps/gossip-extended-away.pngbin0 -> 773 bytes
-rw-r--r--pixmaps/gossip-group-message.pngbin0 -> 764 bytes
-rw-r--r--pixmaps/gossip-message.pngbin0 -> 594 bytes
-rw-r--r--pixmaps/gossip-offline.pngbin0 -> 524 bytes
-rw-r--r--pixmaps/gossip-pending.pngbin0 -> 786 bytes
-rw-r--r--pixmaps/gossip-typing.pngbin0 -> 820 bytes
-rw-r--r--pixmaps/vcard_16.pngbin0 -> 411 bytes
-rw-r--r--pixmaps/vcard_48.pngbin0 -> 3155 bytes
-rw-r--r--po/ChangeLog3
-rw-r--r--po/Makefile.in.in218
-rw-r--r--po/POTFILES.in20
-rw-r--r--profiles/Makefile.am8
-rw-r--r--profiles/gtalk.profile14
-rw-r--r--profiles/jabber.profile8
-rw-r--r--profiles/msn.profile7
115 files changed, 26980 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 000000000..2847eaa37
--- /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
index 000000000..a9a8cd1fb
--- /dev/null
+++ b/CONTRIBUTORS
@@ -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
index 000000000..d60c31a97
--- /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.
+
+ 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.)
+
+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.
+
+ 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.
+
+ 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
+
+ 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
index 000000000..86b8105ed
--- /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
index 000000000..422a3ed6c
--- /dev/null
+++ b/MAINTAINERS
@@ -0,0 +1 @@
+Xavier Claessens <xclaesse@gmail.com>
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 000000000..bf4cebf43
--- /dev/null
+++ b/Makefile.am
@@ -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
index 000000000..e69de29bb
--- /dev/null
+++ b/NEWS
diff --git a/README b/README
new file mode 100644
index 000000000..ef16e2c7c
--- /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
index 000000000..9fe1f1c50
--- /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
index 000000000..589cae3a3
--- /dev/null
+++ b/accounts/Makefile.am
@@ -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
index 000000000..ec30b8338
--- /dev/null
+++ b/accounts/empathy-accounts-main.c
@@ -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
index 000000000..3120d8620
--- /dev/null
+++ b/acinclude.m4
@@ -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
index 000000000..1b93b756f
--- /dev/null
+++ b/autogen.sh
@@ -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
index 000000000..d8ee78a07
--- /dev/null
+++ b/chat/Makefile.am
@@ -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
index 000000000..f27944994
--- /dev/null
+++ b/chat/empathy-chat-main.c
@@ -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
index 000000000..4cfe75b01
--- /dev/null
+++ b/chat/empathy-chat.chandler
@@ -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
index 000000000..6a794e4b8
--- /dev/null
+++ b/chat/org.gnome.Empathy.Chat.service.in
@@ -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
index 000000000..42a162120
--- /dev/null
+++ b/configure.ac
@@ -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
index 000000000..de6786368
--- /dev/null
+++ b/contact-list/Makefile.am
@@ -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
index 000000000..2f4328480
--- /dev/null
+++ b/contact-list/empathy-contact-list-main.c
@@ -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
index 000000000..b0662539d
--- /dev/null
+++ b/launcher/Makefile.am
@@ -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
index 000000000..44b390fb3
--- /dev/null
+++ b/launcher/empathy-launcher.c
@@ -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
index 000000000..162715684
--- /dev/null
+++ b/libempathy-gtk/Makefile.am
@@ -0,0 +1,68 @@
+AM_CPPFLAGS = \
+ -I. \
+ -I$(top_srcdir) \
+ -DDATADIR=\""$(datadir)"\" \
+ -DLOCALEDIR=\""$(datadir)/locale"\" \
+ $(EMPATHY_CFLAGS) \
+ $(WARN_CFLAGS)
+
+noinst_LTLIBRARIES = libempathy-gtk.la
+
+libempathy_gtk_la_SOURCES = \
+ gossip-accounts-dialog.c gossip-accounts-dialog.h \
+ gossip-account-widget-generic.c gossip-account-widget-generic.h \
+ gossip-profile-chooser.c gossip-profile-chooser.h \
+ gossip-cell-renderer-expander.c gossip-cell-renderer-expander.h \
+ gossip-cell-renderer-text.c gossip-cell-renderer-text.h \
+ gossip-stock.c gossip-stock.h \
+ gossip-spell.c gossip-spell.h \
+ gossip-contact-groups.c gossip-contact-groups.h \
+ gossip-contact-list.c gossip-contact-list.h \
+ gossip-preferences.c gossip-preferences.h \
+ gossip-theme-manager.c gossip-theme-manager.h \
+ gossip-chat.c gossip-chat.h \
+ gossip-chat-view.c gossip-chat-view.h \
+ gossip-chat-window.c gossip-chat-window.h \
+ gossip-private-chat.c gossip-private-chat.h \
+ gossip-ui-utils.c gossip-ui-utils.h
+
+libempathy_gtk_la_LIBADD = \
+ $(top_builddir)/libempathy/libempathy.la \
+ $(EMPATHY_LIBS)
+
+libempathy_gtk_includedir = $(includedir)/empathy/
+
+gladedir = $(datadir)/empathy
+glade_DATA = \
+ empathy-accounts.glade \
+ empathy-chat.glade
+
+dtddir = $(datadir)/empathy
+dtd_DATA = \
+ gossip-contact-groups.dtd
+
+schemasdir = $(GCONF_SCHEMA_FILE_DIR)
+schemas_in_files = empathy.schemas.in
+schemas_DATA = $(schemas_in_files:.schemas.in=.schemas)
+@INTLTOOL_SCHEMAS_RULE@
+
+if GCONF_SCHEMAS_INSTALL
+install-data-local:
+ if test -z "$(DESTDIR)" ; then \
+ for p in $(schemas_DATA) ; do \
+ GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(srcdir)/$$p ; \
+ done \
+ fi
+else
+install-data-local:
+endif
+
+EXTRA_DIST = \
+ $(glade_DATA) \
+ $(dtd_DATA) \
+ $(schemas_in_files) \
+ $(schemas_DATA)
+
+DISTCLEANFILES = \
+ $(schemas_DATA)
+
diff --git a/libempathy-gtk/empathy-accounts.glade b/libempathy-gtk/empathy-accounts.glade
new file mode 100644
index 000000000..a7bb391d2
--- /dev/null
+++ b/libempathy-gtk/empathy-accounts.glade
@@ -0,0 +1,843 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--*- mode: xml -*-->
+<glade-interface>
+ <widget class="GtkDialog" id="accounts_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Accounts</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox3">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkHBox" id="hbox146">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="spacing">18</property>
+ <child>
+ <widget class="GtkVBox" id="vbox195">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow17">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <child>
+ <widget class="GtkTreeView" id="treeview">
+ <property name="height_request">200</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="enable_search">False</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="vbox196">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkButton" id="button_connect">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-connect</property>
+ <property name="use_stock">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox148">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <widget class="GtkButton" id="button_add">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-add</property>
+ <property name="use_stock">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button_remove">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-remove</property>
+ <property name="use_stock">True</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="vbox214">
+ <property name="width_request">415</property>
+ <property name="visible">True</property>
+ <property name="spacing">18</property>
+ <child>
+ <widget class="GtkVBox" id="vbox_details">
+ <property name="visible">True</property>
+ <property name="spacing">18</property>
+ <child>
+ <widget class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">20</property>
+ <child>
+ <widget class="GtkVBox" id="vbox213">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkTable" id="table14">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label_type">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Jabber</property>
+ </widget>
+ <packing>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_name">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Imendio </property>
+ <property name="width_chars">0</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkImage" id="image_type">
+ <property name="visible">True</property>
+ <property name="yalign">0</property>
+ <property name="stock">gtk-cut</property>
+ <property name="icon_size">6</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label598">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Account&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkFrame" id="frame2">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment_settings">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">20</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label599">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;Settings&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkFrame" id="frame_new_account">
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment29">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">20</property>
+ <child>
+ <widget class="GtkVBox" id="vbox216">
+ <property name="visible">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <widget class="GtkTable" id="table_new_account">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label638">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Name:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_name</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label640">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Type:</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox180">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkEntry" id="entry_name">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip" translatable="yes">A unique name for this account to identify it personally to you.</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox181">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkButton" id="button_cancel">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button_create">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment30">
+ <property name="visible">True</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <child>
+ <widget class="GtkHBox" id="hbox181">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkImage" id="image838">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label642">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Cr_eate!</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label643">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;New Account&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkFrame" id="frame_no_account">
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">GTK_SHADOW_NONE</property>
+ <child>
+ <widget class="GtkAlignment" id="alignment21">
+ <property name="visible">True</property>
+ <property name="top_padding">6</property>
+ <property name="left_padding">12</property>
+ <child>
+ <widget class="GtkLabel" id="label_no_account_blurb">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">To add a new account, you can click on the 'Add' button and a new entry will be created for you to started configuring.
+
+If you do not want to add an account, simply click on the account you want to configure in the list on the left.</property>
+ <property name="use_markup">True</property>
+ <property name="wrap">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_no_account">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">&lt;b&gt;No Account Selected&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="type">label_item</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="button_close">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkWindow" id="account_jabber_settings">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">jabber account settings</property>
+ <property name="resizable">False</property>
+ <child>
+ <widget class="GtkTable" id="vbox_jabber_settings">
+ <property name="visible">True</property>
+ <property name="n_rows">6</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label_id">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Login I_D:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_id</property>
+ </widget>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_password">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Pass_word:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_password</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_resource">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Reso_urce:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_resource</property>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_server">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Server:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_server</property>
+ </widget>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_port">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Port:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_port</property>
+ </widget>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry_resource">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry_server">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry_port">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkCheckButton" id="checkbutton_ssl">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Use encryption (SS_L)</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox174">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkEntry" id="entry_password">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button_forget">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip" translatable="yes">Forget password and clear the entry.</property>
+ <child>
+ <widget class="GtkImage" id="image834">
+ <property name="visible">True</property>
+ <property name="stock">gtk-clear</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button_change_password">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">C_hange</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry_id">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button_register">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">R_egister</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkWindow" id="account_msn_settings">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">msn account settings</property>
+ <property name="resizable">False</property>
+ <child>
+ <widget class="GtkTable" id="vbox_msn_settings">
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label_id">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Login I_D:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_id</property>
+ </widget>
+ <packing>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_password">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Pass_word:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_password</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHBox" id="hbox182">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkEntry" id="entry_password">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button_forget">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip" translatable="yes">Forget password and clear the entry.</property>
+ <child>
+ <widget class="GtkImage" id="image839">
+ <property name="visible">True</property>
+ <property name="stock">gtk-clear</property>
+ <property name="icon_size">1</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry_id">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_server">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Server:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_server</property>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry_server">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label_port">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Port:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_port</property>
+ </widget>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry_port">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">*</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
diff --git a/libempathy-gtk/empathy-chat.glade b/libempathy-gtk/empathy-chat.glade
new file mode 100644
index 000000000..e8bd47cfe
--- /dev/null
+++ b/libempathy-gtk/empathy-chat.glade
@@ -0,0 +1,700 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+<requires lib="gnome"/>
+
+<widget class="GtkWindow" id="chat_page_window">
+ <property name="title" translatable="yes">Chat</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">350</property>
+ <property name="default_height">250</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+
+ <child>
+ <widget class="GtkVBox" id="chat_widget">
+ <property name="border_width">4</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">3</property>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="chat_view_sw">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="input_text_view_sw">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="vscrollbar_policy">GTK_POLICY_NEVER</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+<widget class="GtkWindow" id="chat_window">
+ <property name="title" translatable="yes">Chat</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">350</property>
+ <property name="default_height">250</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+
+ <child>
+ <widget class="GtkVBox" id="chat_vbox">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child>
+ <widget class="GtkMenuBar" id="chats_menubar">
+ <property name="visible">True</property>
+ <property name="pack_direction">GTK_PACK_DIRECTION_LTR</property>
+ <property name="child_pack_direction">GTK_PACK_DIRECTION_LTR</property>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_conv">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Conversation</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="menu_conv_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_conv_clear">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">C_lear</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image235">
+ <property name="visible">True</property>
+ <property name="stock">gtk-clear</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_conv_insert_smiley">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Insert _Smiley</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator11">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_conv_log">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_View Previous Conversations</property>
+ <property name="use_underline">True</property>
+ <accelerator key="F3" modifiers="0" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image236">
+ <property name="visible">True</property>
+ <property name="stock">gtk-justify-left</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="menu_conv_separator">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_conv_add_contact">
+ <property name="label" translatable="yes">_Add Contact...</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image237">
+ <property name="visible">True</property>
+ <property name="stock">gtk-add</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_conv_info">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Contact Infor_mation</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image238">
+ <property name="visible">True</property>
+ <property name="stock">gtk-info</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator7">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_conv_close">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Close</property>
+ <property name="use_underline">True</property>
+ <accelerator key="W" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image239">
+ <property name="visible">True</property>
+ <property name="stock">gtk-close</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_room">
+ <property name="label" translatable="yes">_Room</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="menu_room_menu">
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_room_set_topic">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Change _Topic...</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator12">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_room_join_new">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Join _New...</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image240">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_room_invite">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">In_vite...</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator7">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_room_add">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Add To Favorites</property>
+ <property name="use_underline">True</property>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image241">
+ <property name="visible">True</property>
+ <property name="stock">gtk-add</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator10">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkCheckMenuItem" id="menu_room_show_contacts">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Show Contacts</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <accelerator key="F11" modifiers="0" signal="activate"/>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_edit">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Edit</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="menu_edit_menu">
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_edit_cut">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Cu_t</property>
+ <property name="use_underline">True</property>
+ <accelerator key="X" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image242">
+ <property name="visible">True</property>
+ <property name="stock">gtk-cut</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_edit_copy">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Copy</property>
+ <property name="use_underline">True</property>
+ <accelerator key="C" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image243">
+ <property name="visible">True</property>
+ <property name="stock">gtk-copy</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkImageMenuItem" id="menu_edit_paste">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Paste</property>
+ <property name="use_underline">True</property>
+ <accelerator key="V" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+
+ <child internal-child="image">
+ <widget class="GtkImage" id="image244">
+ <property name="visible">True</property>
+ <property name="stock">gtk-paste</property>
+ <property name="icon_size">1</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_tabs">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Tabs</property>
+ <property name="use_underline">True</property>
+
+ <child>
+ <widget class="GtkMenu" id="menu_tabs_menu">
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_tabs_prev">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Previous Tab</property>
+ <property name="use_underline">True</property>
+ <accelerator key="Page_Up" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_tabs_next">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Next Tab</property>
+ <property name="use_underline">True</property>
+ <accelerator key="Page_Down" modifiers="GDK_CONTROL_MASK" signal="activate"/>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkSeparatorMenuItem" id="separator4">
+ <property name="visible">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_tabs_left">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Move Tab _Left</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_tabs_right">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Move Tab _Right</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkMenuItem" id="menu_tabs_detach">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Detach Tab</property>
+ <property name="use_underline">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+<widget class="GtkDialog" id="chat_invite_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Invite</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="modal">True</property>
+ <property name="default_width">275</property>
+ <property name="default_height">225</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+
+ <child>
+ <widget class="GtkButton" id="button_cancel">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-6</property>
+ </widget>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="button_invite">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">In_vite</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="response_id">-5</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox7">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+
+ <child>
+ <widget class="GtkVBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkLabel" id="label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Select who would you like to invite:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+
+ <child>
+ <widget class="GtkTreeView" id="treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkVBox" id="vbox8">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+
+ <child>
+ <widget class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Invitation _message:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">You have been invited to join a chat conference.</property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">True</property>
+ <property name="width_chars">40</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/libempathy-gtk/empathy.schemas.in b/libempathy-gtk/empathy.schemas.in
new file mode 100644
index 000000000..243e6ce27
--- /dev/null
+++ b/libempathy-gtk/empathy.schemas.in
@@ -0,0 +1,237 @@
+<gconfschemafile>
+ <schemalist>
+
+ <schema>
+ <key>/schemas/apps/empathy/ui/show_offline</key>
+ <applyto>/apps/empathy/ui/show_offline</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Show offline contacts</short>
+ <long>
+ Whether or not to show contacts that are offline in the contact list.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/ui/show_avatars</key>
+ <applyto>/apps/empathy/ui/show_avatars</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Show avatars</short>
+ <long>
+ Whether or not to show avatars for contacts in the contact
+ list and chat windows.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/ui/compact_contact_list</key>
+ <applyto>/apps/empathy/ui/compact_contact_list</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Compact contact list</short>
+ <long>
+ Whether to show the contact list in compact mode or not.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/ui/main_window_hidden</key>
+ <applyto>/apps/empathy/ui/main_window_hidden</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Hide main window</short>
+ <long>
+ Hide the main window.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/ui/avatar_directory</key>
+ <applyto>/apps/empathy/ui/avatar_directory</applyto>
+ <owner>empathy</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>Default directory to select an avatar image from</short>
+ <long>
+ The last directory that an avatar image was chosen from.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/notifications/play_sounds</key>
+ <applyto>/apps/empathy/notifications/play_sounds</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Use notification sounds</short>
+ <long>
+ Whether or not to play a sound when messages arrive.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/notifications/sound_when_away</key>
+ <applyto>/apps/empathy/notifications/sound_when_away</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Enable sound when away</short>
+ <long>
+ Whether or not to play sounds when away.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/notifications/sound_when_busy</key>
+ <applyto>/apps/empathy/notifications/sound_when_busy</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Enable sound when busy</short>
+ <long>
+ Whether or not to play sounds when busy.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/notifications/popup_when_contact_available</key>
+ <applyto>/apps/empathy/notifications/popup_when_contact_available</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Enable popup when contact is available</short>
+ <long>
+ Whether or not to show a popup when a contact becomes available.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/ui/separate_chat_windows</key>
+ <applyto>/apps/empathy/ui/separate_chat_windows</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Open new chats in separate windows</short>
+ <long>
+ Always open a separate chat window for new chats.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/conversation/graphical_smileys</key>
+ <applyto>/apps/empathy/conversation/graphical_smileys</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Use graphical smileys</short>
+ <long>
+ Whether or not to convert smileys into graphical images in
+ conversations.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/conversation/theme</key>
+ <applyto>/apps/empathy/conversation/theme</applyto>
+ <owner>empathy</owner>
+ <type>string</type>
+ <default>classic</default>
+ <locale name="C">
+ <short>Chat window theme</short>
+ <long>
+ The theme that is used to display the conversation in chat windows.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/conversation/theme_chat_room</key>
+ <applyto>/apps/empathy/conversation/theme_chat_room</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Use theme for chat rooms</short>
+ <long>
+ Whether to use the theme for chat rooms or not.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/conversation/spell_checker_languages</key>
+ <applyto>/apps/empathy/conversation/spell_checker_languages</applyto>
+ <owner>empathy</owner>
+ <type>string</type>
+ <default>en</default>
+ <locale name="C">
+ <short>Spell checking languages</short>
+ <long>
+ Comma separated list of spell checker languages to use (e.g. en, fr, nl).
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/conversation/spell_checker_enabled</key>
+ <applyto>/apps/empathy/conversation/spell_checker_enabled</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Enable spell checker</short>
+ <long>
+ Whether or not to check words typed against the languages you
+ want to check with.
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/empathy/hints/close_main_window</key>
+ <applyto>/apps/empathy/hints/close_main_window</applyto>
+ <owner>empathy</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Show hint about closing the main window</short>
+ <long>
+ Whether or not to show the message dialog about closing the
+ main window with the 'x' button in the title bar.
+ </long>
+ </locale>
+ </schema>
+
+ </schemalist>
+</gconfschemafile>
+
+
+
+
diff --git a/libempathy-gtk/gossip-account-widget-generic.c b/libempathy-gtk/gossip-account-widget-generic.c
new file mode 100644
index 000000000..7f7846397
--- /dev/null
+++ b/libempathy-gtk/gossip-account-widget-generic.c
@@ -0,0 +1,309 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+
+#include <libmissioncontrol/mc-account.h>
+#include <libmissioncontrol/mc-protocol.h>
+
+#include <libempathy-gtk/gossip-ui-utils.h>
+
+#include "gossip-account-widget-generic.h"
+
+typedef struct {
+ McAccount *account;
+
+ GtkWidget *sw;
+ GtkWidget *table_settings;
+ GtkSizeGroup *size_group;
+
+ guint n_rows;
+} GossipAccountWidgetGeneric;
+
+static gboolean account_widget_generic_entry_focus_cb (GtkWidget *widget,
+ GdkEventFocus *event,
+ GossipAccountWidgetGeneric *settings);
+static void account_widget_generic_int_changed_cb (GtkWidget *widget,
+ GossipAccountWidgetGeneric *settings);
+static void account_widget_generic_checkbutton_toggled_cb (GtkWidget *widget,
+ GossipAccountWidgetGeneric *settings);
+static gchar * account_widget_generic_format_param_name (const gchar *param_name);
+static void account_widget_generic_setup_foreach (McProtocolParam *param,
+ GossipAccountWidgetGeneric *settings);
+static void account_widget_generic_destroy_cb (GtkWidget *widget,
+ GossipAccountWidgetGeneric *settings);
+
+static gboolean
+account_widget_generic_entry_focus_cb (GtkWidget *widget,
+ GdkEventFocus *event,
+ GossipAccountWidgetGeneric *settings)
+{
+ const gchar *str;
+ const gchar *param_name;
+
+ str = gtk_entry_get_text (GTK_ENTRY (widget));
+ param_name = g_object_get_data (G_OBJECT (widget), "param_name");
+
+ mc_account_set_param_string (settings->account, param_name, str);
+
+ return FALSE;
+}
+
+static void
+account_widget_generic_int_changed_cb (GtkWidget *widget,
+ GossipAccountWidgetGeneric *settings)
+{
+ const gchar *param_name;
+ gint value;
+
+ value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (widget));
+ param_name = g_object_get_data (G_OBJECT (widget), "param_name");
+
+ mc_account_set_param_int (settings->account, param_name, value);
+}
+
+static void
+account_widget_generic_checkbutton_toggled_cb (GtkWidget *widget,
+ GossipAccountWidgetGeneric *settings)
+{
+ gboolean active;
+ const gchar *param_name;
+
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
+ param_name = g_object_get_data (G_OBJECT (widget), "param_name");
+
+ mc_account_set_param_boolean (settings->account, param_name, active);
+}
+
+static gchar *
+account_widget_generic_format_param_name (const gchar *param_name)
+{
+ gchar *str;
+ gchar *p;
+
+ str = g_strdup (param_name);
+
+ if (str && g_ascii_isalpha (str[0])) {
+ str[0] = g_ascii_toupper (str[0]);
+ }
+
+ while ((p = strchr (str, '-')) != NULL) {
+ if (p[1] != '\0' && g_ascii_isalpha (p[1])) {
+ p[0] = ' ';
+ p[1] = g_ascii_toupper (p[1]);
+ }
+
+ p++;
+ }
+
+ return str;
+}
+
+static void
+account_widget_generic_setup_foreach (McProtocolParam *param,
+ GossipAccountWidgetGeneric *settings)
+{
+ GtkWidget *widget;
+ gchar *param_name_formatted;
+
+ param_name_formatted = account_widget_generic_format_param_name (param->name);
+
+ gtk_table_resize (GTK_TABLE (settings->table_settings),
+ ++settings->n_rows,
+ 2);
+
+ if (param->signature[0] == 's') {
+ gchar *str = NULL;
+
+ str = g_strdup_printf (_("%s:"), param_name_formatted);
+ widget = gtk_label_new (str);
+ g_free (str);
+
+ gtk_size_group_add_widget (settings->size_group, widget);
+ gtk_table_attach (GTK_TABLE (settings->table_settings),
+ widget,
+ 0, 1,
+ settings->n_rows - 1, settings->n_rows,
+ GTK_FILL, 0,
+ 0, 0);
+
+ str = NULL;
+ widget = gtk_entry_new ();
+ mc_account_get_param_string (settings->account,
+ param->name,
+ &str);
+ if (str) {
+ gtk_entry_set_text (GTK_ENTRY (widget), str);
+ g_free (str);
+ }
+
+ if (strstr (param->name, "password")) {
+ gtk_entry_set_visibility (GTK_ENTRY (widget), FALSE);
+ }
+
+ g_signal_connect (widget, "focus-out-event",
+ G_CALLBACK (account_widget_generic_entry_focus_cb),
+ settings);
+
+ gtk_table_attach (GTK_TABLE (settings->table_settings),
+ widget,
+ 1, 2,
+ settings->n_rows - 1, settings->n_rows,
+ GTK_FILL | GTK_EXPAND, 0,
+ 0, 0);
+ }
+ else if (param->signature[0] == 'q' ||
+ param->signature[0] == 'n') {
+ gchar *str = NULL;
+ gint value = 0;
+
+ str = g_strdup_printf (_("%s:"), param_name_formatted);
+ widget = gtk_label_new (str);
+ g_free (str);
+
+ gtk_size_group_add_widget (settings->size_group, widget);
+ gtk_table_attach (GTK_TABLE (settings->table_settings),
+ widget,
+ 0, 1,
+ settings->n_rows - 1, settings->n_rows,
+ GTK_FILL, 0,
+ 0, 0);
+
+ widget = gtk_spin_button_new_with_range (0, G_MAXINT, 1);
+ mc_account_get_param_int (settings->account,
+ param->name,
+ &value);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), value);
+
+ g_signal_connect (widget, "value-changed",
+ G_CALLBACK (account_widget_generic_int_changed_cb),
+ settings);
+
+ gtk_table_attach (GTK_TABLE (settings->table_settings),
+ widget,
+ 1, 2,
+ settings->n_rows - 1, settings->n_rows,
+ GTK_FILL | GTK_EXPAND, 0,
+ 0, 0);
+ }
+ else if (param->signature[0] == 'b') {
+ gboolean value;
+
+ mc_account_get_param_boolean (settings->account,
+ param->name,
+ &value);
+
+ widget = gtk_check_button_new_with_label (param_name_formatted);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), value);
+
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (account_widget_generic_checkbutton_toggled_cb),
+ settings);
+
+ gtk_table_attach (GTK_TABLE (settings->table_settings),
+ widget,
+ 0, 2,
+ settings->n_rows - 1, settings->n_rows,
+ GTK_FILL | GTK_EXPAND, 0,
+ 0, 0);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ g_free (param_name_formatted);
+
+ g_object_set_data_full (G_OBJECT (widget), "param_name",
+ g_strdup (param->name), g_free);
+}
+
+static void
+accounts_widget_generic_setup (GossipAccountWidgetGeneric *settings)
+{
+ McProtocol *protocol;
+ McProfile *profile;
+ GSList *params;
+
+ profile = mc_account_get_profile (settings->account);
+ protocol = mc_profile_get_protocol (profile);
+ params = mc_protocol_get_params (protocol);
+
+ g_slist_foreach (params,
+ (GFunc) account_widget_generic_setup_foreach,
+ settings);
+
+ g_slist_free (params);
+}
+
+static void
+account_widget_generic_destroy_cb (GtkWidget *widget,
+ GossipAccountWidgetGeneric *settings)
+{
+ g_object_unref (settings->account);
+ g_object_unref (settings->size_group);
+
+ g_free (settings);
+}
+
+GtkWidget *
+gossip_account_widget_generic_new (McAccount *account,
+ GtkWidget *label_name)
+{
+ GossipAccountWidgetGeneric *settings;
+
+ g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL);
+ g_return_val_if_fail (GTK_IS_WIDGET (label_name), NULL);
+
+ settings = g_new0 (GossipAccountWidgetGeneric, 1);
+
+ settings->account = g_object_ref (account);
+
+ settings->table_settings = gtk_table_new (0, 2, FALSE);
+ gtk_table_set_row_spacings (GTK_TABLE (settings->table_settings), 6);
+ gtk_table_set_col_spacings (GTK_TABLE (settings->table_settings), 6);
+ settings->sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (settings->sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (settings->sw),
+ settings->table_settings);
+
+ settings->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ if (label_name) {
+ gtk_size_group_add_widget (settings->size_group, label_name);
+ }
+
+ accounts_widget_generic_setup (settings);
+
+ g_signal_connect (settings->sw, "destroy",
+ G_CALLBACK (account_widget_generic_destroy_cb),
+ settings);
+
+ gtk_widget_show_all (settings->sw);
+
+ return settings->sw;
+}
diff --git a/libempathy-gtk/gossip-account-widget-generic.h b/libempathy-gtk/gossip-account-widget-generic.h
new file mode 100644
index 000000000..1a3db63ae
--- /dev/null
+++ b/libempathy-gtk/gossip-account-widget-generic.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_ACCOUNT_WIDGET_GENERIC_H__
+#define __GOSSIP_ACCOUNT_WIDGET_GENERIC_H__
+
+#include <gtk/gtk.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+G_BEGIN_DECLS
+
+GtkWidget *gossip_account_widget_generic_new (McAccount *account,
+ GtkWidget *label_name);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_ACCOUNT_WIDGET_GENERIC_H__ */
diff --git a/libempathy-gtk/gossip-accounts-dialog.c b/libempathy-gtk/gossip-accounts-dialog.c
new file mode 100644
index 000000000..43d974778
--- /dev/null
+++ b/libempathy-gtk/gossip-accounts-dialog.c
@@ -0,0 +1,1032 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ * Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+#include <dbus/dbus-glib.h>
+
+#include <libmissioncontrol/mc-account.h>
+#include <libmissioncontrol/mc-profile.h>
+#include <libmissioncontrol/mission-control.h>
+#include <libmissioncontrol/mc-account-monitor.h>
+#include <libtelepathy/tp-constants.h>
+
+#include <libempathy/empathy-session.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-paths.h>
+#include <libempathy/gossip-utils.h>
+#include <libempathy-gtk/gossip-ui-utils.h>
+
+#include "gossip-accounts-dialog.h"
+#include "gossip-profile-chooser.h"
+#include "gossip-account-widget-generic.h"
+
+#define DEBUG_DOMAIN "AccountDialog"
+
+/* Flashing delay for icons (milliseconds). */
+#define FLASH_TIMEOUT 500
+
+typedef struct {
+ GtkWidget *window;
+
+ GtkWidget *alignment_settings;
+
+ GtkWidget *vbox_details;
+ GtkWidget *frame_no_account;
+ GtkWidget *label_no_account;
+ GtkWidget *label_no_account_blurb;
+
+ GtkWidget *treeview;
+
+ GtkWidget *button_remove;
+ GtkWidget *button_connect;
+
+ GtkWidget *frame_new_account;
+ GtkWidget *combobox_profile;
+ GtkWidget *entry_name;
+ GtkWidget *table_new_account;
+ GtkWidget *button_create;
+ GtkWidget *button_cancel;
+
+ GtkWidget *image_type;
+ GtkWidget *label_name;
+ GtkWidget *label_type;
+ GtkWidget *settings_widget;
+
+ gboolean connecting_show;
+ guint connecting_id;
+ gboolean account_changed;
+} GossipAccountsDialog;
+
+enum {
+ COL_NAME,
+ COL_STATUS,
+ COL_ACCOUNT_POINTER,
+ COL_COUNT
+};
+
+static void accounts_dialog_setup (GossipAccountsDialog *dialog);
+static void accounts_dialog_update_account (GossipAccountsDialog *dialog,
+ McAccount *account);
+static void accounts_dialog_model_setup (GossipAccountsDialog *dialog);
+static void accounts_dialog_model_add_columns (GossipAccountsDialog *dialog);
+static void accounts_dialog_model_select_first (GossipAccountsDialog *dialog);
+static void accounts_dialog_model_pixbuf_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipAccountsDialog *dialog);
+static McAccount *accounts_dialog_model_get_selected (GossipAccountsDialog *dialog);
+static void accounts_dialog_model_set_selected (GossipAccountsDialog *dialog,
+ McAccount *account);
+static gboolean accounts_dialog_model_remove_selected (GossipAccountsDialog *dialog);
+static void accounts_dialog_model_selection_changed (GtkTreeSelection *selection,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_add_account (GossipAccountsDialog *dialog,
+ McAccount *account);
+static void accounts_dialog_account_added_cb (McAccountMonitor *monitor,
+ gchar *unique_name,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_account_removed_cb (McAccountMonitor *monitor,
+ gchar *unique_name,
+ GossipAccountsDialog *dialog);
+static gboolean accounts_dialog_row_changed_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data);
+static gboolean accounts_dialog_flash_connecting_cb (GossipAccountsDialog *dialog);
+static void accounts_dialog_status_changed_cb (MissionControl *mc,
+ TelepathyConnectionStatus status,
+ McPresence presence,
+ TelepathyConnectionStatusReason reason,
+ const gchar *unique_name,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_entry_name_changed_cb (GtkWidget *widget,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_button_create_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_button_cancel_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_button_connect_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_button_add_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_remove_response_cb (GtkWidget *dialog,
+ gint response,
+ McAccount *account);
+static void accounts_dialog_button_remove_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_treeview_row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_response_cb (GtkWidget *widget,
+ gint response,
+ GossipAccountsDialog *dialog);
+static void accounts_dialog_destroy_cb (GtkWidget *widget,
+ GossipAccountsDialog *dialog);
+
+static void
+accounts_dialog_setup (GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkListStore *store;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GList *accounts, *l;
+ MissionControl *mc;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
+ selection = gtk_tree_view_get_selection (view);
+
+ mc = empathy_session_get_mission_control ();
+ accounts = mc_accounts_list ();
+
+ for (l = accounts; l; l = l->next) {
+ McAccount *account;
+ const gchar *name;
+ TelepathyConnectionStatus status;
+
+ account = l->data;
+
+ name = mc_account_get_display_name (account);
+ if (!name) {
+ continue;
+ }
+
+ status = mission_control_get_connection_status (mc, account, NULL);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COL_NAME, name,
+ COL_STATUS, status,
+ COL_ACCOUNT_POINTER, account,
+ -1);
+
+ accounts_dialog_status_changed_cb (mc,
+ status,
+ MC_PRESENCE_UNSET,
+ TP_CONN_STATUS_REASON_NONE_SPECIFIED,
+ mc_account_get_unique_name (account),
+ dialog);
+
+ g_object_unref (account);
+ }
+
+ g_list_free (accounts);
+}
+
+static void
+accounts_dialog_update_connect_button (GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+ GtkWidget *image;
+ const gchar *stock_id;
+ const gchar *label;
+
+ account = accounts_dialog_model_get_selected (dialog);
+
+ if (!account) {
+ gtk_widget_set_sensitive (dialog->button_connect, FALSE);
+ return;
+ }
+
+ if (mc_account_is_enabled (account)) {
+ label = _("Disable");
+ stock_id = GTK_STOCK_DISCONNECT;
+ } else {
+ label = _("Enable");
+ stock_id = GTK_STOCK_CONNECT;
+ }
+
+ image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_BUTTON);
+
+ gtk_button_set_label (GTK_BUTTON (dialog->button_connect), label);
+ gtk_button_set_image (GTK_BUTTON (dialog->button_connect), image);
+}
+
+static void
+accounts_dialog_update_account (GossipAccountsDialog *dialog,
+ McAccount *account)
+{
+ if (dialog->settings_widget) {
+ gtk_widget_destroy (dialog->settings_widget);
+ dialog->settings_widget = NULL;
+ }
+
+ if (!account) {
+ GtkTreeView *view;
+ GtkTreeModel *model;
+
+ gtk_widget_show (dialog->frame_no_account);
+ gtk_widget_hide (dialog->vbox_details);
+
+ gtk_widget_set_sensitive (dialog->button_connect, FALSE);
+ gtk_widget_set_sensitive (dialog->button_remove, FALSE);
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ model = gtk_tree_view_get_model (view);
+
+ if (gtk_tree_model_iter_n_children (model, NULL) > 0) {
+ gtk_label_set_markup (GTK_LABEL (dialog->label_no_account),
+ _("<b>No Account Selected</b>"));
+ gtk_label_set_markup (GTK_LABEL (dialog->label_no_account_blurb),
+ _("To add a new account, you can click on the "
+ "'Add' button and a new entry will be created "
+ "for you to start configuring.\n"
+ "\n"
+ "If you do not want to add an account, simply "
+ "click on the account you want to configure in "
+ "the list on the left."));
+ } else {
+ gtk_label_set_markup (GTK_LABEL (dialog->label_no_account),
+ _("<b>No Accounts Configured</b>"));
+ gtk_label_set_markup (GTK_LABEL (dialog->label_no_account_blurb),
+ _("To add a new account, you can click on the "
+ "'Add' button and a new entry will be created "
+ "for you to start configuring."));
+ }
+ } else {
+ McProfile *profile;
+ const gchar *config_ui;
+
+ gtk_widget_hide (dialog->frame_no_account);
+ gtk_widget_show (dialog->vbox_details);
+
+ profile = mc_account_get_profile (account);
+ config_ui = mc_profile_get_configuration_ui (profile);
+
+ if (strcmp (config_ui, "blah") == 0) {
+ } else {
+ dialog->settings_widget =
+ gossip_account_widget_generic_new (account,
+ dialog->label_name);
+ }
+
+ gtk_widget_grab_focus (dialog->settings_widget);
+ }
+
+ if (dialog->settings_widget) {
+ gtk_container_add (GTK_CONTAINER (dialog->alignment_settings),
+ dialog->settings_widget);
+ }
+
+ if (account) {
+ McProfile *profile;
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gossip_pixbuf_from_account (account, GTK_ICON_SIZE_DIALOG);
+ gtk_image_set_from_pixbuf (GTK_IMAGE (dialog->image_type), pixbuf);
+ if (pixbuf) {
+ g_object_unref (pixbuf);
+ }
+
+ profile = mc_account_get_profile (account);
+
+ gtk_label_set_text (GTK_LABEL (dialog->label_type),
+ mc_profile_get_display_name (profile));
+ gtk_label_set_text (GTK_LABEL (dialog->label_name),
+ mc_account_get_display_name (account));
+ }
+}
+
+static void
+accounts_dialog_model_setup (GossipAccountsDialog *dialog)
+{
+ GtkListStore *store;
+ GtkTreeSelection *selection;
+
+ store = gtk_list_store_new (COL_COUNT,
+ G_TYPE_STRING, /* name */
+ G_TYPE_UINT, /* status */
+ MC_TYPE_ACCOUNT); /* account */
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview),
+ GTK_TREE_MODEL (store));
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->treeview));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (accounts_dialog_model_selection_changed),
+ dialog);
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+ COL_NAME, GTK_SORT_ASCENDING);
+
+ accounts_dialog_model_add_columns (dialog);
+
+ g_object_unref (store);
+}
+
+static void
+accounts_dialog_model_add_columns (GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ gtk_tree_view_set_headers_visible (view, TRUE);
+
+ /* account name/status */
+ column = gtk_tree_view_column_new ();
+ gtk_tree_view_column_set_title (column, _("Accounts"));
+
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+ gtk_tree_view_column_set_cell_data_func (column, cell,
+ (GtkTreeCellDataFunc)
+ accounts_dialog_model_pixbuf_data_func,
+ dialog,
+ NULL);
+
+ cell = gtk_cell_renderer_text_new ();
+ g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_tree_view_column_pack_start (column, cell, TRUE);
+ gtk_tree_view_column_add_attribute (column,
+ cell,
+ "text", COL_NAME);
+
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_append_column (view, column);
+}
+
+static void
+accounts_dialog_model_select_first (GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ /* select first */
+ view = GTK_TREE_VIEW (dialog->treeview);
+ model = gtk_tree_view_get_model (view);
+
+ if (gtk_tree_model_get_iter_first (model, &iter)) {
+ selection = gtk_tree_view_get_selection (view);
+ gtk_tree_selection_select_iter (selection, &iter);
+ } else {
+ accounts_dialog_update_account (dialog, NULL);
+ }
+}
+
+static void
+accounts_dialog_model_pixbuf_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+ GdkPixbuf *pixbuf;
+ TelepathyConnectionStatus status;
+
+ gtk_tree_model_get (model, iter,
+ COL_STATUS, &status,
+ COL_ACCOUNT_POINTER, &account,
+ -1);
+
+ pixbuf = gossip_pixbuf_from_account (account, GTK_ICON_SIZE_BUTTON);
+
+ if (pixbuf) {
+ if (status == TP_CONN_STATUS_DISCONNECTED ||
+ (status == TP_CONN_STATUS_CONNECTING &&
+ !dialog->connecting_show)) {
+ GdkPixbuf *modded_pixbuf;
+
+ modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ TRUE,
+ 8,
+ gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf));
+
+ gdk_pixbuf_saturate_and_pixelate (pixbuf,
+ modded_pixbuf,
+ 1.0,
+ TRUE);
+ g_object_unref (pixbuf);
+ pixbuf = modded_pixbuf;
+ }
+ }
+
+ g_object_set (cell,
+ "visible", TRUE,
+ "pixbuf", pixbuf,
+ NULL);
+
+ g_object_unref (account);
+ if (pixbuf) {
+ g_object_unref (pixbuf);
+ }
+}
+
+static McAccount *
+accounts_dialog_model_get_selected (GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ McAccount *account;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ selection = gtk_tree_view_get_selection (view);
+
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ return NULL;
+ }
+
+ gtk_tree_model_get (model, &iter, COL_ACCOUNT_POINTER, &account, -1);
+
+ return account;
+}
+
+static void
+accounts_dialog_model_set_selected (GossipAccountsDialog *dialog,
+ McAccount *account)
+{
+ GtkTreeView *view;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean ok;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ model = gtk_tree_view_get_model (view);
+ selection = gtk_tree_view_get_selection (view);
+
+ for (ok = gtk_tree_model_get_iter_first (model, &iter);
+ ok;
+ ok = gtk_tree_model_iter_next (model, &iter)) {
+ McAccount *this_account;
+ gboolean equal;
+
+ gtk_tree_model_get (model, &iter,
+ COL_ACCOUNT_POINTER, &this_account,
+ -1);
+
+ equal = gossip_account_equal (this_account, account);
+ g_object_unref (this_account);
+
+ if (equal) {
+ gtk_tree_selection_select_iter (selection, &iter);
+ break;
+ }
+ }
+}
+
+static gboolean
+accounts_dialog_model_remove_selected (GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ selection = gtk_tree_view_get_selection (view);
+
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ return FALSE;
+ }
+
+ return gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+}
+
+static void
+accounts_dialog_model_selection_changed (GtkTreeSelection *selection,
+ GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean is_selection;
+
+ is_selection = gtk_tree_selection_get_selected (selection, &model, &iter);
+
+ gtk_widget_set_sensitive (dialog->button_remove, is_selection);
+ gtk_widget_set_sensitive (dialog->button_connect, is_selection);
+
+ accounts_dialog_update_connect_button (dialog);
+
+ account = accounts_dialog_model_get_selected (dialog);
+ accounts_dialog_update_account (dialog, account);
+
+ if (account) {
+ g_object_unref (account);
+ }
+}
+
+static void
+accounts_dialog_add_account (GossipAccountsDialog *dialog,
+ McAccount *account)
+{
+ MissionControl *mc;
+ TelepathyConnectionStatus status;
+ const gchar *name;
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ gboolean ok;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ model = gtk_tree_view_get_model (view);
+ store = GTK_LIST_STORE (model);
+
+ for (ok = gtk_tree_model_get_iter_first (model, &iter);
+ ok;
+ ok = gtk_tree_model_iter_next (model, &iter)) {
+ McAccount *this_account;
+ gboolean equal;
+
+ gtk_tree_model_get (model, &iter,
+ COL_ACCOUNT_POINTER, &this_account,
+ -1);
+
+ equal = gossip_account_equal (this_account, account);
+ g_object_unref (this_account);
+
+ if (equal) {
+ return;
+ }
+ }
+
+ mc = empathy_session_get_mission_control ();
+ status = mission_control_get_connection_status (mc, account, NULL);
+ name = mc_account_get_display_name (account);
+
+ g_return_if_fail (name != NULL);
+
+ gossip_debug (DEBUG_DOMAIN, "Adding new account: %s", name);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COL_NAME, name,
+ COL_STATUS, status,
+ COL_ACCOUNT_POINTER, account,
+ -1);
+}
+
+static void
+accounts_dialog_account_added_cb (McAccountMonitor *monitor,
+ gchar *unique_name,
+ GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+
+ account = mc_account_lookup (unique_name);
+ accounts_dialog_add_account (dialog, account);
+ g_object_unref (account);
+}
+
+static void
+accounts_dialog_account_removed_cb (McAccountMonitor *monitor,
+ gchar *unique_name,
+ GossipAccountsDialog *dialog)
+{
+ MissionControl *mc;
+ McAccount *account;
+
+ mc = empathy_session_get_mission_control ();
+ account = mc_account_lookup (unique_name);
+
+ accounts_dialog_model_set_selected (dialog, account);
+ accounts_dialog_model_remove_selected (dialog);
+
+ g_object_unref (account);
+}
+
+static gboolean
+accounts_dialog_row_changed_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ gtk_tree_model_row_changed (model, path, iter);
+
+ return FALSE;
+}
+
+static gboolean
+accounts_dialog_flash_connecting_cb (GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+
+ dialog->connecting_show = !dialog->connecting_show;
+
+ view = GTK_TREE_VIEW (dialog->treeview);
+ model = gtk_tree_view_get_model (view);
+
+ gtk_tree_model_foreach (model, accounts_dialog_row_changed_foreach, NULL);
+
+ return TRUE;
+}
+
+static void
+accounts_dialog_status_changed_cb (MissionControl *mc,
+ TelepathyConnectionStatus status,
+ McPresence presence,
+ TelepathyConnectionStatusReason reason,
+ const gchar *unique_name,
+ GossipAccountsDialog *dialog)
+{
+ GtkTreeView *view;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean ok;
+ McAccount *account;
+ GList *accounts, *l;
+ gboolean found = FALSE;
+
+ /* Update the status in the model */
+ view = GTK_TREE_VIEW (dialog->treeview);
+ selection = gtk_tree_view_get_selection (view);
+ model = gtk_tree_view_get_model (view);
+ account = mc_account_lookup (unique_name);
+
+ for (ok = gtk_tree_model_get_iter_first (model, &iter);
+ ok;
+ ok = gtk_tree_model_iter_next (model, &iter)) {
+ McAccount *this_account;
+ gboolean equal;
+
+ gtk_tree_model_get (model, &iter,
+ COL_ACCOUNT_POINTER, &this_account,
+ -1);
+
+ equal = gossip_account_equal (this_account, account);
+ g_object_unref (this_account);
+
+ if (equal) {
+ GtkTreePath *path;
+
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+ COL_STATUS, status,
+ -1);
+
+ path = gtk_tree_model_get_path (model, &iter);
+ gtk_tree_model_row_changed (model, path, &iter);
+ gtk_tree_path_free (path);
+
+ break;
+ }
+ }
+
+ g_object_unref (account);
+
+ /* Start to flash account if status is connecting */
+ if (status == TP_CONN_STATUS_CONNECTING) {
+ if (!dialog->connecting_id) {
+ dialog->connecting_id = g_timeout_add (FLASH_TIMEOUT,
+ (GSourceFunc) accounts_dialog_flash_connecting_cb,
+ dialog);
+ }
+
+ return;
+ }
+
+ /* Stop to flash if no account is connecting */
+ accounts = mc_accounts_list ();
+ for (l = accounts; l; l = l->next) {
+ McAccount *this_account;
+
+ this_account = l->data;
+
+ if (mission_control_get_connection_status (mc, this_account, NULL) == TP_CONN_STATUS_CONNECTING) {
+ found = TRUE;
+ break;
+ }
+
+ g_object_unref (this_account);
+ }
+ g_list_free (accounts);
+
+ if (!found && dialog->connecting_id) {
+ g_source_remove (dialog->connecting_id);
+ dialog->connecting_id = 0;
+ }
+}
+
+static void
+accounts_dialog_entry_name_changed_cb (GtkWidget *widget,
+ GossipAccountsDialog *dialog)
+{
+ const gchar *str;
+
+ str = gtk_entry_get_text (GTK_ENTRY (widget));
+ gtk_widget_set_sensitive (dialog->button_create, !G_STR_EMPTY (str));
+}
+
+static void
+accounts_dialog_button_create_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog)
+{
+ McProfile *profile;
+ McAccount *account;
+ const gchar *str;
+
+ /* Update widgets */
+ gtk_widget_show (dialog->vbox_details);
+ gtk_widget_hide (dialog->frame_no_account);
+ gtk_widget_hide (dialog->frame_new_account);
+
+ profile = gossip_profile_chooser_get_selected (dialog->combobox_profile);
+
+ /* Create account */
+ account = mc_account_create (profile);
+
+ str = gtk_entry_get_text (GTK_ENTRY (dialog->entry_name));
+ mc_account_set_display_name (account, str);
+
+ accounts_dialog_add_account (dialog, account);
+ accounts_dialog_model_set_selected (dialog, account);
+
+ g_object_unref (account);
+ g_object_unref (profile);
+}
+
+static void
+accounts_dialog_button_cancel_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+
+ gtk_widget_hide (dialog->vbox_details);
+ gtk_widget_hide (dialog->frame_no_account);
+ gtk_widget_hide (dialog->frame_new_account);
+
+ account = accounts_dialog_model_get_selected (dialog);
+ accounts_dialog_update_account (dialog, account);
+}
+
+static void
+accounts_dialog_button_connect_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+ gboolean enable;
+
+ account = accounts_dialog_model_get_selected (dialog);
+ enable = (!mc_account_is_enabled (account));
+ mc_account_set_enabled (account, enable);
+ accounts_dialog_update_connect_button (dialog);
+
+ g_object_unref (account);
+}
+
+static void
+accounts_dialog_button_add_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog)
+{
+ gtk_widget_hide (dialog->vbox_details);
+ gtk_widget_hide (dialog->frame_no_account);
+ gtk_widget_show (dialog->frame_new_account);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (dialog->combobox_profile), 0);
+ gtk_entry_set_text (GTK_ENTRY (dialog->entry_name), "");
+ gtk_widget_grab_focus (dialog->entry_name);
+}
+
+static void
+accounts_dialog_remove_response_cb (GtkWidget *dialog,
+ gint response,
+ McAccount *account)
+{
+ if (response == GTK_RESPONSE_YES) {
+ mc_account_delete (account);
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+static void
+accounts_dialog_button_remove_clicked_cb (GtkWidget *button,
+ GossipAccountsDialog *dialog)
+{
+ McAccount *account;
+ GtkWidget *message_dialog;
+
+ account = accounts_dialog_model_get_selected (dialog);
+
+ if (!mc_account_is_complete (account)) {
+ accounts_dialog_model_remove_selected (dialog);
+ return;
+ }
+ message_dialog = gtk_message_dialog_new
+ (GTK_WINDOW (dialog->window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("You are about to remove your %s account!\n"
+ "Are you sure you want to proceed?"),
+ mc_account_get_display_name (account));
+
+ gtk_message_dialog_format_secondary_text
+ (GTK_MESSAGE_DIALOG (message_dialog),
+ _("Any associated conversations and chat rooms will NOT be "
+ "removed if you decide to proceed.\n"
+ "\n"
+ "Should you decide to add the account back at a later time, "
+ "they will still be available."));
+
+ gtk_dialog_add_button (GTK_DIALOG (message_dialog),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_NO);
+ gtk_dialog_add_button (GTK_DIALOG (message_dialog),
+ GTK_STOCK_REMOVE,
+ GTK_RESPONSE_YES);
+
+ g_signal_connect (message_dialog, "response",
+ G_CALLBACK (accounts_dialog_remove_response_cb),
+ account);
+
+ gtk_widget_show (message_dialog);
+}
+
+static void
+accounts_dialog_treeview_row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GossipAccountsDialog *dialog)
+{
+
+ accounts_dialog_button_connect_clicked_cb (dialog->button_connect,
+ dialog);
+}
+
+static void
+accounts_dialog_response_cb (GtkWidget *widget,
+ gint response,
+ GossipAccountsDialog *dialog)
+{
+ gtk_widget_destroy (widget);
+}
+
+static void
+accounts_dialog_destroy_cb (GtkWidget *widget,
+ GossipAccountsDialog *dialog)
+{
+ MissionControl *mc;
+ McAccountMonitor *monitor;
+ GList *accounts, *l;
+
+ mc = empathy_session_get_mission_control ();
+ monitor = mc_account_monitor_new ();
+
+ /* Disconnect signals */
+ g_signal_handlers_disconnect_by_func (monitor,
+ accounts_dialog_account_added_cb,
+ dialog);
+ g_signal_handlers_disconnect_by_func (monitor,
+ accounts_dialog_account_removed_cb,
+ dialog);
+ dbus_g_proxy_disconnect_signal (DBUS_G_PROXY (mc), "AccountStatusChanged",
+ G_CALLBACK (accounts_dialog_status_changed_cb),
+ dialog);
+
+ /* Delete incomplete accounts */
+ accounts = mc_accounts_list ();
+ for (l = accounts; l; l = l->next) {
+ McAccount *account;
+
+ account = l->data;
+ if (!mc_account_is_complete (account)) {
+ /* FIXME: Warn the user the account is not complete
+ * and is going to be removed. */
+ mc_account_delete (account);
+ }
+
+ g_object_unref (account);
+ }
+ g_list_free (accounts);
+
+ if (dialog->connecting_id) {
+ g_source_remove (dialog->connecting_id);
+ }
+
+ g_free (dialog);
+}
+
+GtkWidget *
+gossip_accounts_dialog_show (void)
+{
+ static GossipAccountsDialog *dialog = NULL;
+ MissionControl *mc;
+ McAccountMonitor *monitor;
+ GladeXML *glade;
+ GtkWidget *bbox;
+ GtkWidget *button_close;
+
+ if (dialog) {
+ gtk_window_present (GTK_WINDOW (dialog->window));
+ return dialog->window;
+ }
+
+ dialog = g_new0 (GossipAccountsDialog, 1);
+
+ glade = gossip_glade_get_file ("empathy-accounts.glade",
+ "accounts_dialog",
+ NULL,
+ "accounts_dialog", &dialog->window,
+ "vbox_details", &dialog->vbox_details,
+ "frame_no_account", &dialog->frame_no_account,
+ "label_no_account", &dialog->label_no_account,
+ "label_no_account_blurb", &dialog->label_no_account_blurb,
+ "alignment_settings", &dialog->alignment_settings,
+ "dialog-action_area", &bbox,
+ "treeview", &dialog->treeview,
+ "frame_new_account", &dialog->frame_new_account,
+ "entry_name", &dialog->entry_name,
+ "table_new_account", &dialog->table_new_account,
+ "button_create", &dialog->button_create,
+ "button_cancel", &dialog->button_cancel,
+ "image_type", &dialog->image_type,
+ "label_type", &dialog->label_type,
+ "label_name", &dialog->label_name,
+ "button_remove", &dialog->button_remove,
+ "button_connect", &dialog->button_connect,
+ "button_close", &button_close,
+ NULL);
+
+ gossip_glade_connect (glade,
+ dialog,
+ "accounts_dialog", "destroy", accounts_dialog_destroy_cb,
+ "accounts_dialog", "response", accounts_dialog_response_cb,
+ "button_create", "clicked", accounts_dialog_button_create_clicked_cb,
+ "button_cancel", "clicked", accounts_dialog_button_cancel_clicked_cb,
+ "entry_name", "changed", accounts_dialog_entry_name_changed_cb,
+ "treeview", "row-activated", accounts_dialog_treeview_row_activated_cb,
+ "button_connect", "clicked", accounts_dialog_button_connect_clicked_cb,
+ "button_add", "clicked", accounts_dialog_button_add_clicked_cb,
+ "button_remove", "clicked", accounts_dialog_button_remove_clicked_cb,
+ NULL);
+
+ g_object_add_weak_pointer (G_OBJECT (dialog->window), (gpointer) &dialog);
+
+ g_object_unref (glade);
+
+ /* Create profile chooser */
+ dialog->combobox_profile = gossip_profile_chooser_new ();
+ gtk_table_attach_defaults (GTK_TABLE (dialog->table_new_account),
+ dialog->combobox_profile,
+ 1, 2,
+ 0, 1);
+ gtk_widget_show (dialog->combobox_profile);
+
+ /* Set up signalling */
+ mc = empathy_session_get_mission_control ();
+ monitor = mc_account_monitor_new ();
+
+ /* FIXME: connect account-enabled/disabled too */
+ g_signal_connect (monitor, "account-created",
+ G_CALLBACK (accounts_dialog_account_added_cb),
+ dialog);
+ g_signal_connect (monitor, "account-deleted",
+ G_CALLBACK (accounts_dialog_account_removed_cb),
+ dialog);
+ dbus_g_proxy_connect_signal (DBUS_G_PROXY (mc), "AccountStatusChanged",
+ G_CALLBACK (accounts_dialog_status_changed_cb),
+ dialog, NULL);
+
+ accounts_dialog_model_setup (dialog);
+ accounts_dialog_setup (dialog);
+
+ gtk_widget_show (dialog->window);
+
+ accounts_dialog_model_select_first (dialog);
+
+ return dialog->window;
+}
+
diff --git a/libempathy-gtk/gossip-accounts-dialog.h b/libempathy-gtk/gossip-accounts-dialog.h
new file mode 100644
index 000000000..b6b0593c7
--- /dev/null
+++ b/libempathy-gtk/gossip-accounts-dialog.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_ACCOUNTS_DIALOG_H__
+#define __GOSSIP_ACCOUNTS_DIALOG_H__
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+GtkWidget *gossip_accounts_dialog_show (void);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_ACCOUNTS_DIALOG_H__ */
diff --git a/libempathy-gtk/gossip-cell-renderer-expander.c b/libempathy-gtk/gossip-cell-renderer-expander.c
new file mode 100644
index 000000000..e116ace7b
--- /dev/null
+++ b/libempathy-gtk/gossip-cell-renderer-expander.c
@@ -0,0 +1,482 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Kristian Rietveld <kris@imendio.com>
+ */
+
+/* To do:
+ * - should probably cancel animation if model changes
+ * - need to handle case where node-in-animation is removed
+ * - it only handles a single animation at a time; but I guess users
+ * aren't fast enough to trigger two or more animations at once anyway :P
+ * (could guard for this by just cancelling the "old" animation, and
+ * start the new one).
+ */
+
+#include <gtk/gtktreeview.h>
+
+#include "gossip-cell-renderer-expander.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CELL_RENDERER_EXPANDER, GossipCellRendererExpanderPriv))
+
+static void gossip_cell_renderer_expander_init (GossipCellRendererExpander *expander);
+static void gossip_cell_renderer_expander_class_init (GossipCellRendererExpanderClass *klass);
+static void gossip_cell_renderer_expander_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gossip_cell_renderer_expander_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gossip_cell_renderer_expander_finalize (GObject *object);
+static void gossip_cell_renderer_expander_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height);
+static void gossip_cell_renderer_expander_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags);
+static gboolean gossip_cell_renderer_expander_activate (GtkCellRenderer *cell,
+ GdkEvent *event,
+ GtkWidget *widget,
+ const gchar *path,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+
+enum {
+ PROP_0,
+ PROP_EXPANDER_STYLE,
+ PROP_EXPANDER_SIZE,
+ PROP_ACTIVATABLE
+};
+
+typedef struct _GossipCellRendererExpanderPriv GossipCellRendererExpanderPriv;
+
+struct _GossipCellRendererExpanderPriv {
+ GtkExpanderStyle expander_style;
+ gint expander_size;
+
+ GtkTreeView *animation_view;
+ GtkTreeRowReference *animation_node;
+ GtkExpanderStyle animation_style;
+ guint animation_timeout;
+ GdkRectangle animation_area;
+
+ guint activatable : 1;
+ guint animation_expanding : 1;
+};
+
+G_DEFINE_TYPE (GossipCellRendererExpander, gossip_cell_renderer_expander, GTK_TYPE_CELL_RENDERER)
+
+static void
+gossip_cell_renderer_expander_init (GossipCellRendererExpander *expander)
+{
+ GossipCellRendererExpanderPriv *priv;
+
+ priv = GET_PRIV (expander);
+
+ priv->expander_style = GTK_EXPANDER_COLLAPSED;
+ priv->expander_size = 12;
+ priv->activatable = TRUE;
+ priv->animation_node = NULL;
+
+ GTK_CELL_RENDERER (expander)->xpad = 2;
+ GTK_CELL_RENDERER (expander)->ypad = 2;
+ GTK_CELL_RENDERER (expander)->mode = GTK_CELL_RENDERER_MODE_ACTIVATABLE;
+}
+
+static void
+gossip_cell_renderer_expander_class_init (GossipCellRendererExpanderClass *klass)
+{
+ GObjectClass *object_class;
+ GtkCellRendererClass *cell_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ cell_class = GTK_CELL_RENDERER_CLASS (klass);
+
+ object_class->finalize = gossip_cell_renderer_expander_finalize;
+
+ object_class->get_property = gossip_cell_renderer_expander_get_property;
+ object_class->set_property = gossip_cell_renderer_expander_set_property;
+
+ cell_class->get_size = gossip_cell_renderer_expander_get_size;
+ cell_class->render = gossip_cell_renderer_expander_render;
+ cell_class->activate = gossip_cell_renderer_expander_activate;
+
+ g_object_class_install_property (object_class,
+ PROP_EXPANDER_STYLE,
+ g_param_spec_enum ("expander-style",
+ "Expander Style",
+ "Style to use when painting the expander",
+ GTK_TYPE_EXPANDER_STYLE,
+ GTK_EXPANDER_COLLAPSED,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_EXPANDER_SIZE,
+ g_param_spec_int ("expander-size",
+ "Expander Size",
+ "The size of the expander",
+ 0,
+ G_MAXINT,
+ 12,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_ACTIVATABLE,
+ g_param_spec_boolean ("activatable",
+ "Activatable",
+ "The expander can be activated",
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (GossipCellRendererExpanderPriv));
+}
+
+static void
+gossip_cell_renderer_expander_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+
+ expander = GOSSIP_CELL_RENDERER_EXPANDER (object);
+ priv = GET_PRIV (expander);
+
+ switch (param_id) {
+ case PROP_EXPANDER_STYLE:
+ g_value_set_enum (value, priv->expander_style);
+ break;
+
+ case PROP_EXPANDER_SIZE:
+ g_value_set_int (value, priv->expander_size);
+ break;
+
+ case PROP_ACTIVATABLE:
+ g_value_set_boolean (value, priv->activatable);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+gossip_cell_renderer_expander_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+
+ expander = GOSSIP_CELL_RENDERER_EXPANDER (object);
+ priv = GET_PRIV (expander);
+
+ switch (param_id) {
+ case PROP_EXPANDER_STYLE:
+ priv->expander_style = g_value_get_enum (value);
+ break;
+
+ case PROP_EXPANDER_SIZE:
+ priv->expander_size = g_value_get_int (value);
+ break;
+
+ case PROP_ACTIVATABLE:
+ priv->activatable = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+gossip_cell_renderer_expander_finalize (GObject *object)
+{
+ GossipCellRendererExpanderPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ if (priv->animation_timeout) {
+ g_source_remove (priv->animation_timeout);
+ priv->animation_timeout = 0;
+ }
+
+ if (priv->animation_node) {
+ gtk_tree_row_reference_free (priv->animation_node);
+ }
+
+ (* G_OBJECT_CLASS (gossip_cell_renderer_expander_parent_class)->finalize) (object);
+}
+
+GtkCellRenderer *
+gossip_cell_renderer_expander_new (void)
+{
+ return g_object_new (GOSSIP_TYPE_CELL_RENDERER_EXPANDER, NULL);
+}
+
+static void
+gossip_cell_renderer_expander_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+
+ expander = (GossipCellRendererExpander*) cell;
+ priv = GET_PRIV (expander);
+
+ if (cell_area) {
+ if (x_offset) {
+ *x_offset = cell->xalign * (cell_area->width - (priv->expander_size + (2 * cell->xpad)));
+ *x_offset = MAX (*x_offset, 0);
+ }
+
+ if (y_offset) {
+ *y_offset = cell->yalign * (cell_area->height - (priv->expander_size + (2 * cell->ypad)));
+ *y_offset = MAX (*y_offset, 0);
+ }
+ } else {
+ if (x_offset)
+ *x_offset = 0;
+
+ if (y_offset)
+ *y_offset = 0;
+ }
+
+ if (width)
+ *width = cell->xpad * 2 + priv->expander_size;
+
+ if (height)
+ *height = cell->ypad * 2 + priv->expander_size;
+}
+
+static void
+gossip_cell_renderer_expander_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+ GtkExpanderStyle expander_style;
+ gint x_offset, y_offset;
+
+ expander = (GossipCellRendererExpander*) cell;
+ priv = GET_PRIV (expander);
+
+ if (priv->animation_node) {
+ GtkTreePath *path;
+ GdkRectangle rect;
+
+ /* Not sure if I like this ... */
+ path = gtk_tree_row_reference_get_path (priv->animation_node);
+ gtk_tree_view_get_background_area (priv->animation_view, path,
+ NULL, &rect);
+ gtk_tree_path_free (path);
+
+ if (background_area->y == rect.y)
+ expander_style = priv->animation_style;
+ else
+ expander_style = priv->expander_style;
+ } else
+ expander_style = priv->expander_style;
+
+ gossip_cell_renderer_expander_get_size (cell, widget, cell_area,
+ &x_offset, &y_offset,
+ NULL, NULL);
+
+ gtk_paint_expander (widget->style,
+ window,
+ GTK_STATE_NORMAL,
+ expose_area,
+ widget,
+ "treeview",
+ cell_area->x + x_offset + cell->xpad + priv->expander_size / 2,
+ cell_area->y + y_offset + cell->ypad + priv->expander_size / 2,
+ expander_style);
+}
+
+static void
+invalidate_node (GtkTreeView *tree_view,
+ GtkTreePath *path)
+{
+ GdkWindow *bin_window;
+ GdkRectangle rect;
+
+ bin_window = gtk_tree_view_get_bin_window (tree_view);
+
+ gtk_tree_view_get_background_area (tree_view, path, NULL, &rect);
+
+ rect.x = 0;
+ rect.width = GTK_WIDGET (tree_view)->allocation.width;
+
+ gdk_window_invalidate_rect (bin_window, &rect, TRUE);
+}
+
+static gboolean
+do_animation (GossipCellRendererExpander *expander)
+{
+ GossipCellRendererExpanderPriv *priv;
+ GtkTreePath *path;
+ gboolean done = FALSE;
+
+ priv = GET_PRIV (expander);
+
+ if (priv->animation_expanding) {
+ if (priv->animation_style == GTK_EXPANDER_SEMI_COLLAPSED)
+ priv->animation_style = GTK_EXPANDER_SEMI_EXPANDED;
+ else if (priv->animation_style == GTK_EXPANDER_SEMI_EXPANDED) {
+ priv->animation_style = GTK_EXPANDER_EXPANDED;
+ done = TRUE;
+ }
+ } else {
+ if (priv->animation_style == GTK_EXPANDER_SEMI_EXPANDED)
+ priv->animation_style = GTK_EXPANDER_SEMI_COLLAPSED;
+ else if (priv->animation_style == GTK_EXPANDER_SEMI_COLLAPSED) {
+ priv->animation_style = GTK_EXPANDER_COLLAPSED;
+ done = TRUE;
+ }
+ }
+
+ path = gtk_tree_row_reference_get_path (priv->animation_node);
+ invalidate_node (priv->animation_view, path);
+ gtk_tree_path_free (path);
+
+ if (done) {
+ gtk_tree_row_reference_free (priv->animation_node);
+ priv->animation_node = NULL;
+ priv->animation_timeout = 0;
+ }
+
+ return !done;
+}
+
+static gboolean
+animation_timeout (gpointer data)
+{
+ gboolean retval;
+
+ GDK_THREADS_ENTER ();
+
+ retval = do_animation (data);
+
+ GDK_THREADS_LEAVE ();
+
+ return retval;
+}
+
+static void
+gossip_cell_renderer_expander_start_animation (GossipCellRendererExpander *expander,
+ GtkTreeView *tree_view,
+ GtkTreePath *path,
+ gboolean expanding,
+ GdkRectangle *background_area)
+{
+ GossipCellRendererExpanderPriv *priv;
+
+ priv = GET_PRIV (expander);
+
+ if (expanding) {
+ priv->animation_style = GTK_EXPANDER_SEMI_COLLAPSED;
+ } else {
+ priv->animation_style = GTK_EXPANDER_SEMI_EXPANDED;
+ }
+
+ invalidate_node (tree_view, path);
+
+ priv->animation_expanding = expanding;
+ priv->animation_view = tree_view;
+ priv->animation_node = gtk_tree_row_reference_new (gtk_tree_view_get_model (tree_view), path);
+ priv->animation_timeout = g_timeout_add (50, animation_timeout, expander);
+}
+
+static gboolean
+gossip_cell_renderer_expander_activate (GtkCellRenderer *cell,
+ GdkEvent *event,
+ GtkWidget *widget,
+ const gchar *path_string,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+ GtkTreePath *path;
+ gboolean animate;
+ gboolean expanding;
+
+ expander = GOSSIP_CELL_RENDERER_EXPANDER (cell);
+ priv = GET_PRIV (cell);
+
+ if (!GTK_IS_TREE_VIEW (widget) || !priv->activatable)
+ return FALSE;
+
+ path = gtk_tree_path_new_from_string (path_string);
+
+ if (gtk_tree_path_get_depth (path) > 1) {
+ gtk_tree_path_free (path);
+ return TRUE;
+ }
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (widget)),
+ "gtk-enable-animations", &animate,
+ NULL);
+
+ if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
+ gtk_tree_view_collapse_row (GTK_TREE_VIEW (widget), path);
+ expanding = FALSE;
+ } else {
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (widget), path, FALSE);
+ expanding = TRUE;
+ }
+
+ if (animate) {
+ gossip_cell_renderer_expander_start_animation (expander,
+ GTK_TREE_VIEW (widget),
+ path,
+ expanding,
+ background_area);
+ }
+
+ gtk_tree_path_free (path);
+
+ return TRUE;
+}
diff --git a/libempathy-gtk/gossip-cell-renderer-expander.h b/libempathy-gtk/gossip-cell-renderer-expander.h
new file mode 100644
index 000000000..7df474668
--- /dev/null
+++ b/libempathy-gtk/gossip-cell-renderer-expander.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Kristian Rietveld <kris@imendio.com>
+ */
+
+#ifndef __GOSSIP_CELL_RENDERER_EXPANDER_H__
+#define __GOSSIP_CELL_RENDERER_EXPANDER_H__
+
+#include <gtk/gtkcellrenderer.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CELL_RENDERER_EXPANDER (gossip_cell_renderer_expander_get_type ())
+#define GOSSIP_CELL_RENDERER_EXPANDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOSSIP_TYPE_CELL_RENDERER_EXPANDER, GossipCellRendererExpander))
+#define GOSSIP_CELL_RENDERER_EXPANDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GOSSIP_TYPE_CELL_RENDERER_EXPANDER, GossipCellRendererExpanderClass))
+#define GOSSIP_IS_CELL_RENDERER_EXPANDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOSSIP_TYPE_CELL_RENDERER_EXPANDER))
+#define GOSSIP_IS_CELL_RENDERER_EXPANDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOSSIP_TYPE_CELL_RENDERER_EXPANDER))
+#define GOSSIP_CELL_RENDERER_EXPANDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GOSSIP_TYPE_CELL_RENDERER_EXPANDER, GossipCellRendererExpanderClass))
+
+typedef struct _GossipCellRendererExpander GossipCellRendererExpander;
+typedef struct _GossipCellRendererExpanderClass GossipCellRendererExpanderClass;
+
+struct _GossipCellRendererExpander {
+ GtkCellRenderer parent;
+};
+
+struct _GossipCellRendererExpanderClass {
+ GtkCellRendererClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+GType gossip_cell_renderer_expander_get_type (void) G_GNUC_CONST;
+GtkCellRenderer *gossip_cell_renderer_expander_new (void);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CELL_RENDERER_EXPANDER_H__ */
diff --git a/libempathy-gtk/gossip-cell-renderer-text.c b/libempathy-gtk/gossip-cell-renderer-text.c
new file mode 100644
index 000000000..2b54eedf5
--- /dev/null
+++ b/libempathy-gtk/gossip-cell-renderer-text.c
@@ -0,0 +1,368 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gossip-cell-renderer-text.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CELL_RENDERER_TEXT, GossipCellRendererTextPriv))
+
+struct _GossipCellRendererTextPriv {
+ gchar *name;
+ gchar *status;
+ gboolean is_group;
+
+ gboolean is_valid;
+ gboolean is_selected;
+
+ gboolean show_status;
+};
+
+static void gossip_cell_renderer_text_class_init (GossipCellRendererTextClass *klass);
+static void gossip_cell_renderer_text_init (GossipCellRendererText *cell);
+static void cell_renderer_text_finalize (GObject *object);
+static void cell_renderer_text_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void cell_renderer_text_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void cell_renderer_text_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height);
+static void cell_renderer_text_render (GtkCellRenderer *cell,
+ GdkDrawable *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags);
+static void cell_renderer_text_update_text (GossipCellRendererText *cell,
+ GtkWidget *widget,
+ gboolean selected);
+
+/* Properties */
+enum {
+ PROP_0,
+ PROP_NAME,
+ PROP_STATUS,
+ PROP_IS_GROUP,
+ PROP_SHOW_STATUS,
+};
+
+G_DEFINE_TYPE (GossipCellRendererText, gossip_cell_renderer_text, GTK_TYPE_CELL_RENDERER_TEXT);
+
+static void
+gossip_cell_renderer_text_class_init (GossipCellRendererTextClass *klass)
+{
+ GObjectClass *object_class;
+ GtkCellRendererClass *cell_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ cell_class = GTK_CELL_RENDERER_CLASS (klass);
+
+ object_class->finalize = cell_renderer_text_finalize;
+
+ object_class->get_property = cell_renderer_text_get_property;
+ object_class->set_property = cell_renderer_text_set_property;
+
+ cell_class->get_size = cell_renderer_text_get_size;
+ cell_class->render = cell_renderer_text_render;
+
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "Name",
+ "Contact name",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_STATUS,
+ g_param_spec_string ("status",
+ "Status",
+ "Contact status string",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_IS_GROUP,
+ g_param_spec_boolean ("is_group",
+ "Is group",
+ "Whether this cell is a group",
+ FALSE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SHOW_STATUS,
+ g_param_spec_boolean ("show-status",
+ "Show status",
+ "Whether to show the status line",
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (GossipCellRendererTextPriv));
+}
+
+static void
+gossip_cell_renderer_text_init (GossipCellRendererText *cell)
+{
+ GossipCellRendererTextPriv *priv;
+
+ priv = GET_PRIV (cell);
+
+ g_object_set (cell,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ NULL);
+
+ priv->name = g_strdup ("");
+ priv->status = g_strdup ("");
+ priv->show_status = TRUE;
+}
+
+static void
+cell_renderer_text_finalize (GObject *object)
+{
+ GossipCellRendererText *cell;
+ GossipCellRendererTextPriv *priv;
+
+ cell = GOSSIP_CELL_RENDERER_TEXT (object);
+ priv = GET_PRIV (cell);
+
+ g_free (priv->name);
+ g_free (priv->status);
+
+ (G_OBJECT_CLASS (gossip_cell_renderer_text_parent_class)->finalize) (object);
+}
+
+static void
+cell_renderer_text_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GossipCellRendererText *cell;
+ GossipCellRendererTextPriv *priv;
+
+ cell = GOSSIP_CELL_RENDERER_TEXT (object);
+ priv = GET_PRIV (cell);
+
+ switch (param_id) {
+ case PROP_NAME:
+ g_value_set_string (value, priv->name);
+ break;
+ case PROP_STATUS:
+ g_value_set_string (value, priv->status);
+ break;
+ case PROP_IS_GROUP:
+ g_value_set_boolean (value, priv->is_group);
+ break;
+ case PROP_SHOW_STATUS:
+ g_value_set_boolean (value, priv->show_status);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+cell_renderer_text_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GossipCellRendererText *cell;
+ GossipCellRendererTextPriv *priv;
+ const gchar *str;
+
+ cell = GOSSIP_CELL_RENDERER_TEXT (object);
+ priv = GET_PRIV (cell);
+
+ switch (param_id) {
+ case PROP_NAME:
+ g_free (priv->name);
+ str = g_value_get_string (value);
+ priv->name = g_strdup (str ? str : "");
+ g_strdelimit (priv->name, "\n\r\t", ' ');
+ priv->is_valid = FALSE;
+ break;
+ case PROP_STATUS:
+ g_free (priv->status);
+ str = g_value_get_string (value);
+ priv->status = g_strdup (str ? str : "");
+ g_strdelimit (priv->status, "\n\r\t", ' ');
+ priv->is_valid = FALSE;
+ break;
+ case PROP_IS_GROUP:
+ priv->is_group = g_value_get_boolean (value);
+ priv->is_valid = FALSE;
+ break;
+ case PROP_SHOW_STATUS:
+ priv->show_status = g_value_get_boolean (value);
+ priv->is_valid = FALSE;
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+cell_renderer_text_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height)
+{
+ GossipCellRendererText *celltext;
+ GossipCellRendererTextPriv *priv;
+
+ celltext = GOSSIP_CELL_RENDERER_TEXT (cell);
+ priv = GET_PRIV (cell);
+
+ /* Only update if not already valid so we get the right size. */
+ cell_renderer_text_update_text (celltext, widget, priv->is_selected);
+
+ (GTK_CELL_RENDERER_CLASS (gossip_cell_renderer_text_parent_class)->get_size) (cell, widget,
+ cell_area,
+ x_offset, y_offset,
+ width, height);
+}
+
+static void
+cell_renderer_text_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags)
+{
+ GossipCellRendererText *celltext;
+
+ celltext = GOSSIP_CELL_RENDERER_TEXT (cell);
+
+ cell_renderer_text_update_text (celltext,
+ widget,
+ (flags & GTK_CELL_RENDERER_SELECTED));
+
+ (GTK_CELL_RENDERER_CLASS (gossip_cell_renderer_text_parent_class)->render) (
+ cell, window,
+ widget,
+ background_area,
+ cell_area,
+ expose_area, flags);
+}
+
+static void
+cell_renderer_text_update_text (GossipCellRendererText *cell,
+ GtkWidget *widget,
+ gboolean selected)
+{
+ GossipCellRendererTextPriv *priv;
+ PangoAttrList *attr_list;
+ PangoAttribute *attr_color, *attr_style, *attr_size;
+ GtkStyle *style;
+ gchar *str;
+
+ priv = GET_PRIV (cell);
+
+ if (priv->is_valid && priv->is_selected == selected) {
+ return;
+ }
+
+ if (priv->is_group) {
+ g_object_set (cell,
+ "visible", TRUE,
+ "weight", PANGO_WEIGHT_BOLD,
+ "text", priv->name,
+ "attributes", NULL,
+ "xpad", 1,
+ "ypad", 1,
+ NULL);
+
+ priv->is_selected = selected;
+ priv->is_valid = TRUE;
+ return;
+ }
+
+ style = gtk_widget_get_style (widget);
+
+ attr_list = pango_attr_list_new ();
+
+ attr_style = pango_attr_style_new (PANGO_STYLE_ITALIC);
+ attr_style->start_index = strlen (priv->name) + 1;
+ attr_style->end_index = -1;
+ pango_attr_list_insert (attr_list, attr_style);
+
+ if (!selected) {
+ GdkColor color;
+
+ color = style->text_aa[GTK_STATE_NORMAL];
+
+ attr_color = pango_attr_foreground_new (color.red, color.green, color.blue);
+ attr_color->start_index = attr_style->start_index;
+ attr_color->end_index = -1;
+ pango_attr_list_insert (attr_list, attr_color);
+ }
+
+ attr_size = pango_attr_size_new (pango_font_description_get_size (style->font_desc) / 1.2);
+
+ attr_size->start_index = attr_style->start_index;
+ attr_size->end_index = -1;
+ pango_attr_list_insert (attr_list, attr_size);
+
+ if (!priv->status || !priv->status[0] || !priv->show_status) {
+ str = g_strdup (priv->name);
+ } else {
+ str = g_strdup_printf ("%s\n%s", priv->name, priv->status);
+ }
+
+ g_object_set (cell,
+ "visible", TRUE,
+ "weight", PANGO_WEIGHT_NORMAL,
+ "text", str,
+ "attributes", attr_list,
+ "xpad", 0,
+ "ypad", 1,
+ NULL);
+
+ g_free (str);
+ pango_attr_list_unref (attr_list);
+
+ priv->is_selected = selected;
+ priv->is_valid = TRUE;
+}
+
+GtkCellRenderer *
+gossip_cell_renderer_text_new (void)
+{
+ return g_object_new (GOSSIP_TYPE_CELL_RENDERER_TEXT, NULL);
+}
diff --git a/libempathy-gtk/gossip-cell-renderer-text.h b/libempathy-gtk/gossip-cell-renderer-text.h
new file mode 100644
index 000000000..9b5a413b7
--- /dev/null
+++ b/libempathy-gtk/gossip-cell-renderer-text.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ */
+
+#ifndef __GOSSIP_CELL_RENDERER_TEXT_H__
+#define __GOSSIP_CELL_RENDERER_TEXT_H__
+
+#include <gtk/gtkcellrenderertext.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CELL_RENDERER_TEXT (gossip_cell_renderer_text_get_type ())
+#define GOSSIP_CELL_RENDERER_TEXT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CELL_RENDERER_TEXT, GossipCellRendererText))
+#define GOSSIP_CELL_RENDERER_TEXT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GOSSIP_TYPE_CELL_RENDERER_TEXT, GossipCellRendererTextClass))
+#define GOSSIP_IS_CELL_RENDERER_TEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CELL_RENDERER_TEXT))
+#define GOSSIP_IS_CELL_RENDERER_TEXT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CELL_RENDERER_TEXT))
+#define GOSSIP_CELL_RENDERER_TEXT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CELL_RENDERER_TEXT, GossipCellRendererTextClass))
+
+typedef struct _GossipCellRendererText GossipCellRendererText;
+typedef struct _GossipCellRendererTextClass GossipCellRendererTextClass;
+typedef struct _GossipCellRendererTextPriv GossipCellRendererTextPriv;
+
+struct _GossipCellRendererText {
+ GtkCellRendererText parent;
+
+ GossipCellRendererTextPriv *priv;
+};
+
+struct _GossipCellRendererTextClass {
+ GtkCellRendererTextClass parent_class;
+};
+
+GType gossip_cell_renderer_text_get_type (void) G_GNUC_CONST;
+GtkCellRenderer * gossip_cell_renderer_text_new (void);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CELL_RENDERER_TEXT_H__ */
diff --git a/libempathy-gtk/gossip-chat-manager.c b/libempathy-gtk/gossip-chat-manager.c
new file mode 100644
index 000000000..86cd0ea3f
--- /dev/null
+++ b/libempathy-gtk/gossip-chat-manager.c
@@ -0,0 +1,327 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include <libgossip/gossip-account.h>
+#include <libgossip/gossip-event.h>
+#include <libgossip/gossip-session.h>
+#include <libgossip/gossip-message.h>
+#include <libgossip/gossip-debug.h>
+#include <libgossip/gossip-event-manager.h>
+
+#include "gossip-app.h"
+#include "gossip-chat.h"
+#include "gossip-chat-manager.h"
+
+#define DEBUG_DOMAIN "ChatManager"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHAT_MANAGER, GossipChatManagerPriv))
+
+typedef struct _GossipChatManagerPriv GossipChatManagerPriv;
+
+struct _GossipChatManagerPriv {
+ GHashTable *chats;
+ GHashTable *events;
+};
+
+static void chat_manager_finalize (GObject *object);
+static void chat_manager_new_message_cb (GossipSession *session,
+ GossipMessage *msg,
+ GossipChatManager *manager);
+static void chat_manager_event_activated_cb (GossipEventManager *event_manager,
+ GossipEvent *event,
+ GObject *object);
+static void chat_manager_get_chats_foreach (GossipContact *contact,
+ GossipPrivateChat *chat,
+ GList **chats);
+static void chat_manager_chat_removed_cb (GossipChatManager *manager,
+ GossipChat *chat,
+ gboolean is_last_ref);
+
+G_DEFINE_TYPE (GossipChatManager, gossip_chat_manager, G_TYPE_OBJECT);
+
+static void
+gossip_chat_manager_class_init (GossipChatManagerClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = chat_manager_finalize;
+
+ g_type_class_add_private (object_class, sizeof (GossipChatManagerPriv));
+}
+
+static void
+gossip_chat_manager_init (GossipChatManager *manager)
+{
+ GossipChatManagerPriv *priv;
+
+ priv = GET_PRIV (manager);
+
+ priv->chats = g_hash_table_new_full (gossip_contact_hash,
+ gossip_contact_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) g_object_unref);
+
+ priv->events = g_hash_table_new_full (gossip_contact_hash,
+ gossip_contact_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) g_object_unref);
+
+ /* Connect to signals on GossipSession to listen for new messages */
+ g_signal_connect (gossip_app_get_session (),
+ "new-message",
+ G_CALLBACK (chat_manager_new_message_cb),
+ manager);
+}
+
+static void
+chat_manager_finalize (GObject *object)
+{
+ GossipChatManagerPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ g_hash_table_destroy (priv->chats);
+ g_hash_table_destroy (priv->events);
+
+ G_OBJECT_CLASS (gossip_chat_manager_parent_class)->finalize (object);
+}
+
+static void
+chat_manager_new_message_cb (GossipSession *session,
+ GossipMessage *message,
+ GossipChatManager *manager)
+{
+ GossipChatManagerPriv *priv;
+ GossipPrivateChat *chat;
+ GossipContact *sender;
+ GossipEvent *event = NULL;
+ GossipEvent *old_event;
+
+ priv = GET_PRIV (manager);
+
+ sender = gossip_message_get_sender (message);
+ chat = g_hash_table_lookup (priv->chats, sender);
+
+ old_event = g_hash_table_lookup (priv->events, sender);
+
+ /* Add event to event manager if one doesn't exist already. */
+ if (!chat) {
+ gossip_debug (DEBUG_DOMAIN, "New chat for: %s",
+ gossip_contact_get_id (sender));
+ chat = gossip_chat_manager_get_chat (manager, sender);
+
+ if (!old_event) {
+ event = gossip_event_new (GOSSIP_EVENT_NEW_MESSAGE);
+ }
+ } else {
+ GossipChatWindow *window;
+
+ window = gossip_chat_get_window (GOSSIP_CHAT (chat));
+
+ if (!window && !old_event) {
+ event = gossip_event_new (GOSSIP_EVENT_NEW_MESSAGE);
+ }
+ }
+
+ gossip_private_chat_append_message (chat, message);
+
+ if (event) {
+ gchar *str;
+
+ str = g_strdup_printf (_("New message from %s"),
+ gossip_contact_get_name (sender));
+ g_object_set (event,
+ "message", str,
+ "data", message,
+ NULL);
+ g_free (str);
+
+ gossip_event_manager_add (gossip_app_get_event_manager (),
+ event,
+ chat_manager_event_activated_cb,
+ G_OBJECT (manager));
+
+ g_hash_table_insert (priv->events,
+ g_object_ref (sender),
+ g_object_ref (event));
+ }
+}
+
+static void
+chat_manager_event_activated_cb (GossipEventManager *event_manager,
+ GossipEvent *event,
+ GObject *object)
+{
+ GossipMessage *message;
+ GossipContact *contact;
+
+ message = GOSSIP_MESSAGE (gossip_event_get_data (event));
+ contact = gossip_message_get_sender (message);
+
+ gossip_chat_manager_show_chat (GOSSIP_CHAT_MANAGER (object), contact);
+}
+
+static void
+chat_manager_get_chats_foreach (GossipContact *contact,
+ GossipPrivateChat *chat,
+ GList **chats)
+{
+ const gchar *contact_id;
+
+ contact_id = gossip_contact_get_id (contact);
+ *chats = g_list_prepend (*chats, g_strdup (contact_id));
+}
+
+GossipChatManager *
+gossip_chat_manager_new (void)
+{
+ return g_object_new (GOSSIP_TYPE_CHAT_MANAGER, NULL);
+}
+
+static void
+chat_manager_chat_removed_cb (GossipChatManager *manager,
+ GossipChat *chat,
+ gboolean is_last_ref)
+{
+ GossipChatManagerPriv *priv;
+ GossipContact *contact;
+
+ if (!is_last_ref) {
+ return;
+ }
+
+ priv = GET_PRIV (manager);
+
+ contact = gossip_chat_get_contact (chat);
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Removing an old chat:'%s'",
+ gossip_contact_get_id (contact));
+
+ g_hash_table_remove (priv->chats, contact);
+}
+
+GossipPrivateChat *
+gossip_chat_manager_get_chat (GossipChatManager *manager,
+ GossipContact *contact)
+{
+ GossipChatManagerPriv *priv;
+ GossipPrivateChat *chat;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_MANAGER (manager), NULL);
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ priv = GET_PRIV (manager);
+
+ chat = g_hash_table_lookup (priv->chats, contact);
+
+ if (!chat) {
+ GossipSession *session;
+ GossipAccount *account;
+ GossipContact *own_contact;
+
+ session = gossip_app_get_session ();
+ account = gossip_contact_get_account (contact);
+ own_contact = gossip_session_get_own_contact (session, account);
+
+ chat = gossip_private_chat_new (own_contact, contact);
+ g_hash_table_insert (priv->chats,
+ g_object_ref (contact),
+ chat);
+ g_object_add_toggle_ref (G_OBJECT (chat),
+ (GToggleNotify) chat_manager_chat_removed_cb,
+ manager);
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Creating a new chat:'%s'",
+ gossip_contact_get_id (contact));
+ }
+
+ return chat;
+}
+
+GList *
+gossip_chat_manager_get_chats (GossipChatManager *manager)
+{
+ GossipChatManagerPriv *priv;
+ GList *chats = NULL;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_MANAGER (manager), NULL);
+
+ priv = GET_PRIV (manager);
+
+ g_hash_table_foreach (priv->chats,
+ (GHFunc) chat_manager_get_chats_foreach,
+ &chats);
+
+ chats = g_list_sort (chats, (GCompareFunc) strcmp);
+
+ return chats;
+}
+
+void
+gossip_chat_manager_remove_events (GossipChatManager *manager,
+ GossipContact *contact)
+{
+ GossipChatManagerPriv *priv;
+ GossipEvent *event;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_MANAGER (manager));
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Removing events for contact:'%s'",
+ gossip_contact_get_id (contact));
+
+ priv = GET_PRIV (manager);
+
+ event = g_hash_table_lookup (priv->events, contact);
+ if (event) {
+ gossip_event_manager_remove (gossip_app_get_event_manager (),
+ event, G_OBJECT (manager));
+ g_hash_table_remove (priv->events, contact);
+ }
+}
+
+void
+gossip_chat_manager_show_chat (GossipChatManager *manager,
+ GossipContact *contact)
+{
+ GossipPrivateChat *chat;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_MANAGER (manager));
+ g_return_if_fail (GOSSIP_IS_CONTACT (contact));
+
+ chat = gossip_chat_manager_get_chat (manager, contact);
+
+ gossip_chat_present (GOSSIP_CHAT (chat));
+
+ gossip_chat_manager_remove_events(manager, contact);
+}
diff --git a/libempathy-gtk/gossip-chat-manager.h b/libempathy-gtk/gossip-chat-manager.h
new file mode 100644
index 000000000..7500b594a
--- /dev/null
+++ b/libempathy-gtk/gossip-chat-manager.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_CHAT_MANAGER_H__
+#define __GOSSIP_CHAT_MANAGER_H__
+
+#include <glib-object.h>
+
+#include <libempathy/gossip-contact.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CHAT_MANAGER (gossip_chat_manager_get_type ())
+#define GOSSIP_CHAT_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CHAT_MANAGER, GossipChatManager))
+#define GOSSIP_CHAT_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_CHAT_MANAGER, GossipChatManagerClass))
+#define GOSSIP_IS_CHAT_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CHAT_MANAGER))
+#define GOSSIP_IS_CHAT_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CHAT_MANAGER))
+#define GOSSIP_CHAT_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CHAT_MANAGER, GossipChatManagerClass))
+
+typedef struct _GossipChatManager GossipChatManager;
+typedef struct _GossipChatManagerClass GossipChatManagerClass;
+
+#include "gossip-private-chat.h"
+
+struct _GossipChatManager {
+ GObject parent;
+};
+
+struct _GossipChatManagerClass {
+ GObjectClass parent_class;
+};
+
+GType gossip_chat_manager_get_type (void) G_GNUC_CONST;
+GossipChatManager *gossip_chat_manager_new (void);
+GossipPrivateChat *gossip_chat_manager_get_chat (GossipChatManager *manager,
+ GossipContact *contact);
+GList * gossip_chat_manager_get_chats (GossipChatManager *manager);
+void gossip_chat_manager_remove_events (GossipChatManager *manager,
+ GossipContact *contact);
+void gossip_chat_manager_show_chat (GossipChatManager *manager,
+ GossipContact *contact);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CHAT_MANAGER_H__ */
+
diff --git a/libempathy-gtk/gossip-chat-manager.loT b/libempathy-gtk/gossip-chat-manager.loT
new file mode 100644
index 000000000..3d0d3ab0d
--- /dev/null
+++ b/libempathy-gtk/gossip-chat-manager.loT
@@ -0,0 +1,7 @@
+# gossip-chat-manager.lo - a libtool object file
+# Generated by ltmain.sh - GNU libtool 1.5.22 Debian 1.5.22-4 (1.1220.2.365 2005/12/18 22:14:06)
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# Name of the PIC object.
diff --git a/libempathy-gtk/gossip-chat-view.c b/libempathy-gtk/gossip-chat-view.c
new file mode 100644
index 000000000..cc6482ee8
--- /dev/null
+++ b/libempathy-gtk/gossip-chat-view.c
@@ -0,0 +1,2151 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <string.h>
+#include <time.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkimage.h>
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkimagemenuitem.h>
+#include <gtk/gtkstock.h>
+#include <gtk/gtkscrolledwindow.h>
+#include <gtk/gtksizegroup.h>
+#include <glade/glade.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+#include <libempathy/gossip-utils.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-conf.h>
+
+#include "gossip-chat-view.h"
+#include "gossip-chat.h"
+#include "gossip-preferences.h"
+#include "gossip-theme-manager.h"
+#include "gossip-ui-utils.h"
+
+#define DEBUG_DOMAIN "ChatView"
+
+/* Number of seconds between timestamps when using normal mode, 5 minutes. */
+#define TIMESTAMP_INTERVAL 300
+
+#define MAX_LINES 800
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHAT_VIEW, GossipChatViewPriv))
+
+typedef enum {
+ BLOCK_TYPE_NONE,
+ BLOCK_TYPE_SELF,
+ BLOCK_TYPE_OTHER,
+ BLOCK_TYPE_EVENT,
+ BLOCK_TYPE_TIME,
+ BLOCK_TYPE_INVITE
+} BlockType;
+
+struct _GossipChatViewPriv {
+ GtkTextBuffer *buffer;
+
+ gboolean irc_style;
+ time_t last_timestamp;
+ BlockType last_block_type;
+
+ gboolean allow_scrolling;
+ gboolean is_group_chat;
+
+ GtkTextMark *find_mark_previous;
+ GtkTextMark *find_mark_next;
+ gboolean find_wrapped;
+ gboolean find_last_direction;
+
+ /* This is for the group chat so we know if the "other" last contact
+ * changed, so we know whether to insert a header or not.
+ */
+ GossipContact *last_contact;
+
+ guint notify_system_fonts_id;
+ guint notify_show_avatars_id;
+};
+
+typedef struct {
+ GossipSmiley smiley;
+ const gchar *pattern;
+} GossipSmileyPattern;
+
+static const GossipSmileyPattern smileys[] = {
+ /* Forward smileys. */
+ { GOSSIP_SMILEY_NORMAL, ":)" },
+ { GOSSIP_SMILEY_WINK, ";)" },
+ { GOSSIP_SMILEY_WINK, ";-)" },
+ { GOSSIP_SMILEY_BIGEYE, "=)" },
+ { GOSSIP_SMILEY_NOSE, ":-)" },
+ { GOSSIP_SMILEY_CRY, ":'(" },
+ { GOSSIP_SMILEY_SAD, ":(" },
+ { GOSSIP_SMILEY_SAD, ":-(" },
+ { GOSSIP_SMILEY_SCEPTICAL, ":/" },
+ { GOSSIP_SMILEY_SCEPTICAL, ":\\" },
+ { GOSSIP_SMILEY_BIGSMILE, ":D" },
+ { GOSSIP_SMILEY_BIGSMILE, ":-D" },
+ { GOSSIP_SMILEY_INDIFFERENT, ":|" },
+ { GOSSIP_SMILEY_TOUNGE, ":p" },
+ { GOSSIP_SMILEY_TOUNGE, ":-p" },
+ { GOSSIP_SMILEY_TOUNGE, ":P" },
+ { GOSSIP_SMILEY_TOUNGE, ":-P" },
+ { GOSSIP_SMILEY_TOUNGE, ";p" },
+ { GOSSIP_SMILEY_TOUNGE, ";-p" },
+ { GOSSIP_SMILEY_TOUNGE, ";P" },
+ { GOSSIP_SMILEY_TOUNGE, ";-P" },
+ { GOSSIP_SMILEY_SHOCKED, ":o" },
+ { GOSSIP_SMILEY_SHOCKED, ":-o" },
+ { GOSSIP_SMILEY_SHOCKED, ":O" },
+ { GOSSIP_SMILEY_SHOCKED, ":-O" },
+ { GOSSIP_SMILEY_COOL, "8)" },
+ { GOSSIP_SMILEY_COOL, "B)" },
+ { GOSSIP_SMILEY_SORRY, "*|" },
+ { GOSSIP_SMILEY_KISS, ":*" },
+ { GOSSIP_SMILEY_SHUTUP, ":#" },
+ { GOSSIP_SMILEY_SHUTUP, ":-#" },
+ { GOSSIP_SMILEY_YAWN, "|O" },
+ { GOSSIP_SMILEY_CONFUSED, ":S" },
+ { GOSSIP_SMILEY_CONFUSED, ":s" },
+ { GOSSIP_SMILEY_ANGEL, "<)" },
+ { GOSSIP_SMILEY_OOOH, ":x" },
+ { GOSSIP_SMILEY_LOOKAWAY, "*)" },
+ { GOSSIP_SMILEY_LOOKAWAY, "*-)" },
+ { GOSSIP_SMILEY_BLUSH, "*S" },
+ { GOSSIP_SMILEY_BLUSH, "*s" },
+ { GOSSIP_SMILEY_BLUSH, "*$" },
+ { GOSSIP_SMILEY_COOLBIGSMILE, "8D" },
+ { GOSSIP_SMILEY_ANGRY, ":@" },
+ { GOSSIP_SMILEY_BOSS, "@)" },
+ { GOSSIP_SMILEY_MONKEY, "#)" },
+ { GOSSIP_SMILEY_SILLY, "O)" },
+ { GOSSIP_SMILEY_SICK, "+o(" },
+
+ /* Backward smileys. */
+ { GOSSIP_SMILEY_NORMAL, "(:" },
+ { GOSSIP_SMILEY_WINK, "(;" },
+ { GOSSIP_SMILEY_WINK, "(-;" },
+ { GOSSIP_SMILEY_BIGEYE, "(=" },
+ { GOSSIP_SMILEY_NOSE, "(-:" },
+ { GOSSIP_SMILEY_CRY, ")':" },
+ { GOSSIP_SMILEY_SAD, "):" },
+ { GOSSIP_SMILEY_SAD, ")-:" },
+ { GOSSIP_SMILEY_SCEPTICAL, "/:" },
+ { GOSSIP_SMILEY_SCEPTICAL, "//:" },
+ { GOSSIP_SMILEY_INDIFFERENT, "|:" },
+ { GOSSIP_SMILEY_TOUNGE, "d:" },
+ { GOSSIP_SMILEY_TOUNGE, "d-:" },
+ { GOSSIP_SMILEY_TOUNGE, "d;" },
+ { GOSSIP_SMILEY_TOUNGE, "d-;" },
+ { GOSSIP_SMILEY_SHOCKED, "o:" },
+ { GOSSIP_SMILEY_SHOCKED, "O:" },
+ { GOSSIP_SMILEY_COOL, "(8" },
+ { GOSSIP_SMILEY_COOL, "(B" },
+ { GOSSIP_SMILEY_SORRY, "|*" },
+ { GOSSIP_SMILEY_KISS, "*:" },
+ { GOSSIP_SMILEY_SHUTUP, "#:" },
+ { GOSSIP_SMILEY_SHUTUP, "#-:" },
+ { GOSSIP_SMILEY_YAWN, "O|" },
+ { GOSSIP_SMILEY_CONFUSED, "S:" },
+ { GOSSIP_SMILEY_CONFUSED, "s:" },
+ { GOSSIP_SMILEY_ANGEL, "(>" },
+ { GOSSIP_SMILEY_OOOH, "x:" },
+ { GOSSIP_SMILEY_LOOKAWAY, "(*" },
+ { GOSSIP_SMILEY_LOOKAWAY, "(-*" },
+ { GOSSIP_SMILEY_BLUSH, "S*" },
+ { GOSSIP_SMILEY_BLUSH, "s*" },
+ { GOSSIP_SMILEY_BLUSH, "$*" },
+ { GOSSIP_SMILEY_ANGRY, "@:" },
+ { GOSSIP_SMILEY_BOSS, "(@" },
+ { GOSSIP_SMILEY_MONKEY, "#)" },
+ { GOSSIP_SMILEY_SILLY, "(O" },
+ { GOSSIP_SMILEY_SICK, ")o+" }
+};
+
+static void gossip_chat_view_class_init (GossipChatViewClass *klass);
+static void gossip_chat_view_init (GossipChatView *view);
+static void chat_view_finalize (GObject *object);
+static gboolean chat_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time);
+static void chat_view_size_allocate (GtkWidget *widget,
+ GtkAllocation *alloc);
+static void chat_view_setup_tags (GossipChatView *view);
+static void chat_view_system_font_update (GossipChatView *view);
+static void chat_view_notify_system_font_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void chat_view_notify_show_avatars_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void chat_view_populate_popup (GossipChatView *view,
+ GtkMenu *menu,
+ gpointer user_data);
+static gboolean chat_view_event_cb (GossipChatView *view,
+ GdkEventMotion *event,
+ GtkTextTag *tag);
+static gboolean chat_view_url_event_cb (GtkTextTag *tag,
+ GObject *object,
+ GdkEvent *event,
+ GtkTextIter *iter,
+ GtkTextBuffer *buffer);
+static void chat_view_open_address_cb (GtkMenuItem *menuitem,
+ const gchar *url);
+static void chat_view_copy_address_cb (GtkMenuItem *menuitem,
+ const gchar *url);
+static void chat_view_clear_view_cb (GtkMenuItem *menuitem,
+ GossipChatView *view);
+static void chat_view_insert_text_with_emoticons (GtkTextBuffer *buf,
+ GtkTextIter *iter,
+ const gchar *str);
+static gboolean chat_view_is_scrolled_down (GossipChatView *view);
+static void chat_view_theme_changed_cb (GossipThemeManager *manager,
+ GossipChatView *view);
+static void chat_view_maybe_append_date_and_time (GossipChatView *view,
+ GossipMessage *msg);
+static void chat_view_append_spacing (GossipChatView *view);
+static void chat_view_append_text (GossipChatView *view,
+ const gchar *body,
+ const gchar *tag);
+static void chat_view_maybe_append_fancy_header (GossipChatView *view,
+ GossipMessage *msg);
+static void chat_view_append_irc_action (GossipChatView *view,
+ GossipMessage *msg);
+static void chat_view_append_fancy_action (GossipChatView *view,
+ GossipMessage *msg);
+static void chat_view_append_irc_message (GossipChatView *view,
+ GossipMessage *msg);
+static void chat_view_append_fancy_message (GossipChatView *view,
+ GossipMessage *msg);
+
+G_DEFINE_TYPE (GossipChatView, gossip_chat_view, GTK_TYPE_TEXT_VIEW);
+
+static void
+gossip_chat_view_class_init (GossipChatViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = chat_view_finalize;
+ widget_class->size_allocate = chat_view_size_allocate;
+ widget_class->drag_motion = chat_view_drag_motion;
+
+ g_type_class_add_private (object_class, sizeof (GossipChatViewPriv));
+}
+
+static void
+gossip_chat_view_init (GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+ gboolean show_avatars;
+
+ priv = GET_PRIV (view);
+
+ priv->buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ priv->last_block_type = BLOCK_TYPE_NONE;
+ priv->last_timestamp = 0;
+
+ priv->allow_scrolling = TRUE;
+
+ priv->is_group_chat = FALSE;
+
+ g_object_set (view,
+ "wrap-mode", GTK_WRAP_WORD_CHAR,
+ "editable", FALSE,
+ "cursor-visible", FALSE,
+ NULL);
+
+ priv->notify_system_fonts_id =
+ gossip_conf_notify_add (gossip_conf_get (),
+ "/desktop/gnome/interface/document_font_name",
+ chat_view_notify_system_font_cb,
+ view);
+ chat_view_system_font_update (view);
+
+ priv->notify_show_avatars_id =
+ gossip_conf_notify_add (gossip_conf_get (),
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ chat_view_notify_show_avatars_cb,
+ view);
+
+ chat_view_setup_tags (view);
+
+ gossip_theme_manager_apply_saved (gossip_theme_manager_get (), view);
+
+ show_avatars = FALSE;
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ &show_avatars);
+
+ gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
+ view, show_avatars);
+
+ g_signal_connect (view,
+ "populate-popup",
+ G_CALLBACK (chat_view_populate_popup),
+ NULL);
+
+ g_signal_connect_object (gossip_theme_manager_get (),
+ "theme-changed",
+ G_CALLBACK (chat_view_theme_changed_cb),
+ view,
+ 0);
+}
+
+static void
+chat_view_finalize (GObject *object)
+{
+ GossipChatView *view;
+ GossipChatViewPriv *priv;
+
+ view = GOSSIP_CHAT_VIEW (object);
+ priv = GET_PRIV (view);
+
+ gossip_debug (DEBUG_DOMAIN, "finalize: %p", object);
+
+ gossip_conf_notify_remove (gossip_conf_get (), priv->notify_system_fonts_id);
+ gossip_conf_notify_remove (gossip_conf_get (), priv->notify_show_avatars_id);
+
+ if (priv->last_contact) {
+ g_object_unref (priv->last_contact);
+ }
+
+ G_OBJECT_CLASS (gossip_chat_view_parent_class)->finalize (object);
+}
+
+static gboolean
+chat_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time)
+{
+ /* Don't handle drag motion, since we don't want the view to scroll as
+ * the result of dragging something across it.
+ */
+
+ return FALSE;
+}
+
+static void
+chat_view_size_allocate (GtkWidget *widget,
+ GtkAllocation *alloc)
+{
+ gboolean down;
+
+ down = chat_view_is_scrolled_down (GOSSIP_CHAT_VIEW (widget));
+
+ GTK_WIDGET_CLASS (gossip_chat_view_parent_class)->size_allocate (widget, alloc);
+
+ if (down) {
+ gossip_chat_view_scroll_down (GOSSIP_CHAT_VIEW (widget));
+ }
+}
+
+static void
+chat_view_setup_tags (GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+ GtkTextTag *tag;
+
+ priv = GET_PRIV (view);
+
+ gtk_text_buffer_create_tag (priv->buffer,
+ "cut",
+ NULL);
+
+ /* FIXME: Move to the theme and come up with something that looks a bit
+ * nicer.
+ */
+ gtk_text_buffer_create_tag (priv->buffer,
+ "highlight",
+ "background", "yellow",
+ NULL);
+
+ tag = gtk_text_buffer_create_tag (priv->buffer,
+ "link",
+ NULL);
+
+ g_signal_connect (tag,
+ "event",
+ G_CALLBACK (chat_view_url_event_cb),
+ priv->buffer);
+
+ g_signal_connect (view,
+ "motion-notify-event",
+ G_CALLBACK (chat_view_event_cb),
+ tag);
+}
+
+static void
+chat_view_system_font_update (GossipChatView *view)
+{
+ PangoFontDescription *font_description = NULL;
+ gchar *font_name;
+
+ if (gossip_conf_get_string (gossip_conf_get (),
+ "/desktop/gnome/interface/document_font_name",
+ &font_name) && font_name) {
+ font_description = pango_font_description_from_string (font_name);
+ g_free (font_name);
+ } else {
+ font_description = NULL;
+ }
+
+ gtk_widget_modify_font (GTK_WIDGET (view), font_description);
+
+ if (font_description) {
+ pango_font_description_free (font_description);
+ }
+}
+
+static void
+chat_view_notify_system_font_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ GossipChatView *view;
+ gboolean show_avatars = FALSE;
+
+ view = user_data;
+
+ chat_view_system_font_update (view);
+
+ /* Ugly, again, to adjust the vertical position of the nick... Will fix
+ * this when reworking the theme manager so that view register
+ * themselves with it instead of the other way around.
+ */
+ gossip_conf_get_bool (conf,
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ &show_avatars);
+
+ gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
+ view, show_avatars);
+}
+
+static void
+chat_view_notify_show_avatars_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ GossipChatView *view;
+ GossipChatViewPriv *priv;
+ gboolean show_avatars = FALSE;
+
+ view = user_data;
+ priv = GET_PRIV (view);
+
+ gossip_conf_get_bool (conf, key, &show_avatars);
+
+ gossip_theme_manager_update_show_avatars (gossip_theme_manager_get (),
+ view, show_avatars);
+}
+
+static void
+chat_view_populate_popup (GossipChatView *view,
+ GtkMenu *menu,
+ gpointer user_data)
+{
+ GossipChatViewPriv *priv;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+ gint x, y;
+ GtkTextIter iter, start, end;
+ GtkWidget *item;
+ gchar *str = NULL;
+
+ priv = GET_PRIV (view);
+
+ /* Clear menu item */
+ if (gtk_text_buffer_get_char_count (priv->buffer) > 0) {
+ item = gtk_menu_item_new ();
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (chat_view_clear_view_cb),
+ view);
+ }
+
+ /* Link context menu items */
+ table = gtk_text_buffer_get_tag_table (priv->buffer);
+ tag = gtk_text_tag_table_lookup (table, "link");
+
+ gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
+
+ gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
+ GTK_TEXT_WINDOW_WIDGET,
+ x, y,
+ &x, &y);
+
+ gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
+
+ start = end = iter;
+
+ if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
+ gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
+ str = gtk_text_buffer_get_text (priv->buffer,
+ &start, &end, FALSE);
+ }
+
+ if (G_STR_EMPTY (str)) {
+ g_free (str);
+ return;
+ }
+
+ /* NOTE: Set data just to get the string freed when not needed. */
+ g_object_set_data_full (G_OBJECT (menu),
+ "url", str,
+ (GDestroyNotify) g_free);
+
+ item = gtk_menu_item_new ();
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Copy Link Address"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (chat_view_copy_address_cb),
+ str);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Open Link"));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (chat_view_open_address_cb),
+ str);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+}
+
+static gboolean
+chat_view_event_cb (GossipChatView *view,
+ GdkEventMotion *event,
+ GtkTextTag *tag)
+{
+ static GdkCursor *hand = NULL;
+ static GdkCursor *beam = NULL;
+ GtkTextWindowType type;
+ GtkTextIter iter;
+ GdkWindow *win;
+ gint x, y, buf_x, buf_y;
+
+ type = gtk_text_view_get_window_type (GTK_TEXT_VIEW (view),
+ event->window);
+
+ if (type != GTK_TEXT_WINDOW_TEXT) {
+ return FALSE;
+ }
+
+ /* Get where the pointer really is. */
+ win = gtk_text_view_get_window (GTK_TEXT_VIEW (view), type);
+ if (!win) {
+ return FALSE;
+ }
+
+ gdk_window_get_pointer (win, &x, &y, NULL);
+
+ /* Get the iter where the cursor is at */
+ gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view), type,
+ x, y,
+ &buf_x, &buf_y);
+
+ gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view),
+ &iter,
+ buf_x, buf_y);
+
+ if (gtk_text_iter_has_tag (&iter, tag)) {
+ if (!hand) {
+ hand = gdk_cursor_new (GDK_HAND2);
+ beam = gdk_cursor_new (GDK_XTERM);
+ }
+ gdk_window_set_cursor (win, hand);
+ } else {
+ if (!beam) {
+ beam = gdk_cursor_new (GDK_XTERM);
+ }
+ gdk_window_set_cursor (win, beam);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+chat_view_url_event_cb (GtkTextTag *tag,
+ GObject *object,
+ GdkEvent *event,
+ GtkTextIter *iter,
+ GtkTextBuffer *buffer)
+{
+ GtkTextIter start, end;
+ gchar *str;
+
+ /* If the link is being selected, don't do anything. */
+ gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
+ if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) {
+ return FALSE;
+ }
+
+ if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) {
+ start = end = *iter;
+
+ if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
+ gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
+ str = gtk_text_buffer_get_text (buffer,
+ &start,
+ &end,
+ FALSE);
+
+ gossip_url_show (str);
+ g_free (str);
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+chat_view_open_address_cb (GtkMenuItem *menuitem, const gchar *url)
+{
+ gossip_url_show (url);
+}
+
+static void
+chat_view_copy_address_cb (GtkMenuItem *menuitem, const gchar *url)
+{
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ gtk_clipboard_set_text (clipboard, url, -1);
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
+ gtk_clipboard_set_text (clipboard, url, -1);
+}
+
+static void
+chat_view_clear_view_cb (GtkMenuItem *menuitem, GossipChatView *view)
+{
+ gossip_chat_view_clear (view);
+}
+
+static void
+chat_view_insert_text_with_emoticons (GtkTextBuffer *buf,
+ GtkTextIter *iter,
+ const gchar *str)
+{
+ const gchar *p;
+ gunichar c, prev_c;
+ gint i;
+ gint match;
+ gint submatch;
+ gboolean use_smileys = FALSE;
+
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_SHOW_SMILEYS,
+ &use_smileys);
+
+ if (!use_smileys) {
+ gtk_text_buffer_insert (buf, iter, str, -1);
+ return;
+ }
+
+ while (*str) {
+ gint smileys_index[G_N_ELEMENTS (smileys)];
+ GdkPixbuf *pixbuf;
+ gint len;
+ const gchar *start;
+
+ memset (smileys_index, 0, sizeof (smileys_index));
+
+ match = -1;
+ submatch = -1;
+ p = str;
+ prev_c = 0;
+
+ while (*p) {
+ c = g_utf8_get_char (p);
+
+ if (match != -1 && g_unichar_isspace (c)) {
+ break;
+ } else {
+ match = -1;
+ }
+
+ if (submatch != -1 || prev_c == 0 || g_unichar_isspace (prev_c)) {
+ submatch = -1;
+
+ for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
+ /* Only try to match if we already have
+ * a beginning match for the pattern, or
+ * if it's the first character in the
+ * pattern, if it's not in the middle of
+ * a word.
+ */
+ if (((smileys_index[i] == 0 && (prev_c == 0 || g_unichar_isspace (prev_c))) ||
+ smileys_index[i] > 0) &&
+ smileys[i].pattern[smileys_index[i]] == c) {
+ submatch = i;
+
+ smileys_index[i]++;
+ if (!smileys[i].pattern[smileys_index[i]]) {
+ match = i;
+ }
+ } else {
+ smileys_index[i] = 0;
+ }
+ }
+ }
+
+ prev_c = c;
+ p = g_utf8_next_char (p);
+ }
+
+ if (match == -1) {
+ gtk_text_buffer_insert (buf, iter, str, -1);
+ return;
+ }
+
+ start = p - strlen (smileys[match].pattern);
+
+ if (start > str) {
+ len = start - str;
+ gtk_text_buffer_insert (buf, iter, str, len);
+ }
+
+ pixbuf = gossip_chat_view_get_smiley_image (smileys[match].smiley);
+ gtk_text_buffer_insert_pixbuf (buf, iter, pixbuf);
+
+ gtk_text_buffer_insert (buf, iter, " ", 1);
+
+ str = g_utf8_find_next_char (p, NULL);
+ }
+}
+
+static gboolean
+chat_view_is_scrolled_down (GossipChatView *view)
+{
+ GtkWidget *sw;
+
+ sw = gtk_widget_get_parent (GTK_WIDGET (view));
+ if (GTK_IS_SCROLLED_WINDOW (sw)) {
+ GtkAdjustment *vadj;
+
+ vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (sw));
+
+ if (vadj->value + vadj->page_size / 2 < vadj->upper - vadj->page_size) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+chat_view_maybe_trim_buffer (GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+ GtkTextIter top, bottom;
+ gint line;
+ gint remove;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+
+ priv = GET_PRIV (view);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &bottom);
+ line = gtk_text_iter_get_line (&bottom);
+ if (line < MAX_LINES) {
+ return;
+ }
+
+ remove = line - MAX_LINES;
+ gtk_text_buffer_get_start_iter (priv->buffer, &top);
+
+ bottom = top;
+ if (!gtk_text_iter_forward_lines (&bottom, remove)) {
+ return;
+ }
+
+ /* Track backwords to a place where we can safely cut, we don't do it in
+ * the middle of a tag.
+ */
+ table = gtk_text_buffer_get_tag_table (priv->buffer);
+ tag = gtk_text_tag_table_lookup (table, "cut");
+ if (!tag) {
+ return;
+ }
+
+ if (!gtk_text_iter_forward_to_tag_toggle (&bottom, tag)) {
+ return;
+ }
+
+ if (!gtk_text_iter_equal (&top, &bottom)) {
+ gtk_text_buffer_delete (priv->buffer, &top, &bottom);
+ }
+}
+
+static void
+chat_view_maybe_append_date_and_time (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ const gchar *tag;
+ time_t timestamp;
+ GDate *date, *last_date;
+ GtkTextIter iter;
+ gboolean append_date, append_time;
+ GString *str;
+
+ priv = GET_PRIV (view);
+
+ if (priv->irc_style) {
+ tag = "irc-time";
+ } else {
+ tag = "fancy-time";
+ }
+
+ if (priv->last_block_type == BLOCK_TYPE_TIME) {
+ return;
+ }
+
+ str = g_string_new (NULL);
+
+ timestamp = 0;
+ if (msg) {
+ timestamp = gossip_message_get_timestamp (msg);
+ }
+
+ if (timestamp <= 0) {
+ timestamp = gossip_time_get_current ();
+ }
+
+ date = g_date_new ();
+ g_date_set_time (date, timestamp);
+
+ last_date = g_date_new ();
+ g_date_set_time (last_date, priv->last_timestamp);
+
+ append_date = FALSE;
+ append_time = FALSE;
+
+ if (g_date_compare (date, last_date) > 0) {
+ append_date = TRUE;
+ append_time = TRUE;
+ }
+
+ if (priv->last_timestamp + TIMESTAMP_INTERVAL < timestamp) {
+ append_time = TRUE;
+ }
+
+ if (append_time || append_date) {
+ chat_view_append_spacing (view);
+
+ g_string_append (str, "- ");
+ }
+
+ if (append_date) {
+ gchar buf[256];
+
+ g_date_strftime (buf, 256, _("%A %d %B %Y"), date);
+ g_string_append (str, buf);
+
+ if (append_time) {
+ g_string_append (str, ", ");
+ }
+ }
+
+ g_date_free (date);
+ g_date_free (last_date);
+
+ if (append_time) {
+ gchar *tmp;
+
+ tmp = gossip_time_to_string_local (timestamp, GOSSIP_TIME_FORMAT_DISPLAY_SHORT);
+ g_string_append (str, tmp);
+ g_free (tmp);
+ }
+
+ if (append_time || append_date) {
+ g_string_append (str, " -\n");
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ str->str, -1,
+ tag,
+ NULL);
+
+ priv->last_block_type = BLOCK_TYPE_TIME;
+ priv->last_timestamp = timestamp;
+ }
+
+ g_string_free (str, TRUE);
+}
+
+static void
+chat_view_append_spacing (GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+ const gchar *tag;
+ GtkTextIter iter;
+
+ priv = GET_PRIV (view);
+
+ if (priv->irc_style) {
+ tag = "irc-spacing";
+ } else {
+ tag = "fancy-spacing";
+ }
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ "\n",
+ -1,
+ "cut",
+ tag,
+ NULL);
+}
+
+static void
+chat_view_append_text (GossipChatView *view,
+ const gchar *body,
+ const gchar *tag)
+{
+ GossipChatViewPriv *priv;
+ GtkTextIter start_iter, end_iter;
+ GtkTextMark *mark;
+ GtkTextIter iter;
+ gint num_matches, i;
+ GArray *start, *end;
+ const gchar *link_tag;
+
+ priv = GET_PRIV (view);
+
+ if (priv->irc_style) {
+ link_tag = "irc-link";
+ } else {
+ link_tag = "fancy-link";
+ }
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &start_iter);
+ mark = gtk_text_buffer_create_mark (priv->buffer, NULL, &start_iter, TRUE);
+
+ start = g_array_new (FALSE, FALSE, sizeof (gint));
+ end = g_array_new (FALSE, FALSE, sizeof (gint));
+
+ num_matches = gossip_regex_match (GOSSIP_REGEX_ALL, body, start, end);
+
+ if (num_matches == 0) {
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ chat_view_insert_text_with_emoticons (priv->buffer, &iter, body);
+ } else {
+ gint last = 0;
+ gint s = 0, e = 0;
+ gchar *tmp;
+
+ for (i = 0; i < num_matches; i++) {
+ s = g_array_index (start, gint, i);
+ e = g_array_index (end, gint, i);
+
+ if (s > last) {
+ tmp = gossip_substring (body, last, s);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ chat_view_insert_text_with_emoticons (priv->buffer,
+ &iter,
+ tmp);
+ g_free (tmp);
+ }
+
+ tmp = gossip_substring (body, s, e);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ tmp,
+ -1,
+ link_tag,
+ "link",
+ NULL);
+
+ g_free (tmp);
+
+ last = e;
+ }
+
+ if (e < strlen (body)) {
+ tmp = gossip_substring (body, e, strlen (body));
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ chat_view_insert_text_with_emoticons (priv->buffer,
+ &iter,
+ tmp);
+ g_free (tmp);
+ }
+ }
+
+ g_array_free (start, TRUE);
+ g_array_free (end, TRUE);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert (priv->buffer, &iter, "\n", 1);
+
+ /* Apply the style to the inserted text. */
+ gtk_text_buffer_get_iter_at_mark (priv->buffer, &start_iter, mark);
+ gtk_text_buffer_get_end_iter (priv->buffer, &end_iter);
+
+ gtk_text_buffer_apply_tag_by_name (priv->buffer,
+ tag,
+ &start_iter,
+ &end_iter);
+
+ gtk_text_buffer_delete_mark (priv->buffer, mark);
+}
+
+static void
+chat_view_maybe_append_fancy_header (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ GossipContact *sender;
+ GossipContact *my_contact;
+ const gchar *name;
+ gboolean header;
+ GtkTextIter iter;
+ gchar *tmp;
+ const gchar *tag;
+ const gchar *avatar_tag;
+ const gchar *line_top_tag;
+ const gchar *line_bottom_tag;
+ gboolean from_self;
+ GdkPixbuf *avatar;
+
+ priv = GET_PRIV (view);
+
+ sender = gossip_message_get_sender (msg);
+ my_contact = gossip_get_own_contact_from_contact (sender);
+ name = gossip_contact_get_name (sender);
+ from_self = gossip_contact_equal (sender, my_contact);
+
+ gossip_debug (DEBUG_DOMAIN, "Maybe add fancy header");
+
+ if (from_self) {
+ tag = "fancy-header-self";
+ line_top_tag = "fancy-line-top-self";
+ line_bottom_tag = "fancy-line-bottom-self";
+ } else {
+ tag = "fancy-header-other";
+ line_top_tag = "fancy-line-top-other";
+ line_bottom_tag = "fancy-line-bottom-other";
+ }
+
+ header = FALSE;
+
+ /* Only insert a header if the previously inserted block is not the same
+ * as this one. This catches all the different cases:
+ */
+ if (priv->last_block_type != BLOCK_TYPE_SELF &&
+ priv->last_block_type != BLOCK_TYPE_OTHER) {
+ header = TRUE;
+ }
+ else if (from_self && priv->last_block_type == BLOCK_TYPE_OTHER) {
+ header = TRUE;
+ }
+ else if (!from_self && priv->last_block_type == BLOCK_TYPE_SELF) {
+ header = TRUE;
+ }
+ else if (!from_self &&
+ (!priv->last_contact ||
+ !gossip_contact_equal (sender, priv->last_contact))) {
+ header = TRUE;
+ }
+
+ if (!header) {
+ return;
+ }
+
+ chat_view_append_spacing (view);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ "\n",
+ -1,
+ line_top_tag,
+ NULL);
+
+ avatar = gossip_pixbuf_avatar_from_contact_scaled (sender, 32, 32);
+
+ if (avatar) {
+ GtkTextIter start;
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_pixbuf (priv->buffer, &iter, avatar);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ start = iter;
+ gtk_text_iter_backward_char (&start);
+
+ if (from_self) {
+ gtk_text_buffer_apply_tag_by_name (priv->buffer,
+ "fancy-avatar-self",
+ &start, &iter);
+ avatar_tag = "fancy-header-self-avatar";
+ } else {
+ gtk_text_buffer_apply_tag_by_name (priv->buffer,
+ "fancy-avatar-other",
+ &start, &iter);
+ avatar_tag = "fancy-header-other-avatar";
+ }
+
+ g_object_unref (avatar);
+ } else {
+ avatar_tag = NULL;
+ }
+
+ tmp = g_strdup_printf ("%s\n", name);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ tmp,
+ -1,
+ tag,
+ avatar_tag,
+ NULL);
+ g_free (tmp);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ "\n",
+ -1,
+ line_bottom_tag,
+ NULL);
+}
+
+static void
+chat_view_append_irc_action (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ GossipContact *my_contact;
+ GossipContact *sender;
+ const gchar *name;
+ GtkTextIter iter;
+ const gchar *body;
+ gchar *tmp;
+ const gchar *tag;
+
+ priv = GET_PRIV (view);
+
+ gossip_debug (DEBUG_DOMAIN, "Add IRC action");
+
+ sender = gossip_message_get_sender (msg);
+ my_contact = gossip_get_own_contact_from_contact (sender);
+ name = gossip_contact_get_name (sender);
+
+ /* Skip the "/me ". */
+ if (gossip_contact_equal (sender, my_contact)) {
+ tag = "irc-action-self";
+ } else {
+ tag = "irc-action-other";
+ }
+
+ if (priv->last_block_type != BLOCK_TYPE_SELF &&
+ priv->last_block_type != BLOCK_TYPE_OTHER) {
+ chat_view_append_spacing (view);
+ }
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+
+ tmp = g_strdup_printf (" * %s ", name);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ tmp,
+ -1,
+ "cut",
+ tag,
+ NULL);
+ g_free (tmp);
+
+ body = gossip_message_get_body (msg);
+ chat_view_append_text (view, body, tag);
+}
+
+static void
+chat_view_append_fancy_action (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ GossipContact *sender;
+ GossipContact *my_contact;
+ const gchar *name;
+ const gchar *body;
+ GtkTextIter iter;
+ gchar *tmp;
+ const gchar *tag;
+ const gchar *line_tag;
+
+ priv = GET_PRIV (view);
+
+ gossip_debug (DEBUG_DOMAIN, "Add fancy action");
+
+ sender = gossip_message_get_sender (msg);
+ my_contact = gossip_get_own_contact_from_contact (sender);
+ name = gossip_contact_get_name (sender);
+
+ if (gossip_contact_equal (sender, my_contact)) {
+ tag = "fancy-action-self";
+ line_tag = "fancy-line-self";
+ } else {
+ tag = "fancy-action-other";
+ line_tag = "fancy-line-other";
+ }
+
+ tmp = g_strdup_printf (" * %s ", name);
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ tmp,
+ -1,
+ tag,
+ NULL);
+ g_free (tmp);
+
+ body = gossip_message_get_body (msg);
+ chat_view_append_text (view, body, tag);
+}
+
+static void
+chat_view_append_irc_message (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ GossipContact *sender;
+ GossipContact *my_contact;
+ const gchar *name;
+ const gchar *body;
+ const gchar *nick_tag;
+ const gchar *body_tag;
+ GtkTextIter iter;
+ gchar *tmp;
+
+ priv = GET_PRIV (view);
+
+ gossip_debug (DEBUG_DOMAIN, "Add IRC message");
+
+ body = gossip_message_get_body (msg);
+ sender = gossip_message_get_sender (msg);
+ my_contact = gossip_get_own_contact_from_contact (sender);
+ name = gossip_contact_get_name (sender);
+
+ if (gossip_contact_equal (sender, my_contact)) {
+ nick_tag = "irc-nick-self";
+ body_tag = "irc-body-self";
+ } else {
+ if (gossip_chat_should_highlight_nick (msg)) {
+ nick_tag = "irc-nick-highlight";
+ } else {
+ nick_tag = "irc-nick-other";
+ }
+
+ body_tag = "irc-body-other";
+ }
+
+ if (priv->last_block_type != BLOCK_TYPE_SELF &&
+ priv->last_block_type != BLOCK_TYPE_OTHER) {
+ chat_view_append_spacing (view);
+ }
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+
+ /* The nickname. */
+ tmp = g_strdup_printf ("%s: ", name);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ tmp,
+ -1,
+ "cut",
+ nick_tag,
+ NULL);
+ g_free (tmp);
+
+ /* The text body. */
+ chat_view_append_text (view, body, body_tag);
+}
+
+static void
+chat_view_append_fancy_message (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ GossipContact *sender;
+ GossipContact *my_contact;
+ const gchar *body;
+ const gchar *tag;
+
+ priv = GET_PRIV (view);
+
+ sender = gossip_message_get_sender (msg);
+ my_contact = gossip_get_own_contact_from_contact (sender);
+
+ if (gossip_contact_equal (sender, my_contact)) {
+ tag = "fancy-body-self";
+ } else {
+ tag = "fancy-body-other";
+
+ /* FIXME: Might want to support nick highlighting here... */
+ }
+
+ body = gossip_message_get_body (msg);
+ chat_view_append_text (view, body, tag);
+}
+
+static void
+chat_view_theme_changed_cb (GossipThemeManager *manager,
+ GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+ gboolean show_avatars = FALSE;
+ gboolean theme_rooms = FALSE;
+
+ priv = GET_PRIV (view);
+
+ priv->last_block_type = BLOCK_TYPE_NONE;
+
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
+ &theme_rooms);
+ if (!theme_rooms && priv->is_group_chat) {
+ gossip_theme_manager_apply (manager, view, NULL);
+ } else {
+ gossip_theme_manager_apply_saved (manager, view);
+ }
+
+ /* Needed for now to update the "rise" property of the names to get it
+ * vertically centered.
+ */
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ &show_avatars);
+ gossip_theme_manager_update_show_avatars (manager, view, show_avatars);
+}
+
+GossipChatView *
+gossip_chat_view_new (void)
+{
+ return g_object_new (GOSSIP_TYPE_CHAT_VIEW, NULL);
+}
+
+/* The name is optional, if NULL, the sender for msg is used. */
+void
+gossip_chat_view_append_message (GossipChatView *view,
+ GossipMessage *msg)
+{
+ GossipChatViewPriv *priv;
+ GossipContact *sender;
+ const gchar *body;
+ gboolean scroll_down;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+ g_return_if_fail (GOSSIP_IS_MESSAGE (msg));
+
+ priv = GET_PRIV (view);
+
+ body = gossip_message_get_body (msg);
+ if (!body) {
+ return;
+ }
+
+ scroll_down = chat_view_is_scrolled_down (view);
+
+ chat_view_maybe_trim_buffer (view);
+ chat_view_maybe_append_date_and_time (view, msg);
+
+ sender = gossip_message_get_sender (msg);
+
+ if (!priv->irc_style) {
+ chat_view_maybe_append_fancy_header (view, msg);
+ }
+
+ if (gossip_message_get_type (msg) == GOSSIP_MESSAGE_TYPE_ACTION) {
+ if (priv->irc_style) {
+ chat_view_append_irc_action (view, msg);
+ } else {
+ chat_view_append_fancy_action (view, msg);
+ }
+ } else {
+ if (priv->irc_style) {
+ chat_view_append_irc_message (view, msg);
+ } else {
+ chat_view_append_fancy_message (view, msg);
+ }
+ }
+
+ priv->last_block_type = BLOCK_TYPE_SELF;
+
+ /* Reset the last inserted contact, since it was from self. */
+ if (priv->last_contact) {
+ g_object_unref (priv->last_contact);
+ priv->last_contact = NULL;
+ }
+
+ if (scroll_down) {
+ gossip_chat_view_scroll_down (view);
+ }
+}
+
+void
+gossip_chat_view_append_event (GossipChatView *view,
+ const gchar *str)
+{
+ GossipChatViewPriv *priv;
+ gboolean bottom;
+ GtkTextIter iter;
+ gchar *msg;
+ const gchar *tag;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+ g_return_if_fail (!G_STR_EMPTY (str));
+
+ priv = GET_PRIV (view);
+
+ bottom = chat_view_is_scrolled_down (view);
+
+ chat_view_maybe_trim_buffer (view);
+
+ if (priv->irc_style) {
+ tag = "irc-event";
+ msg = g_strdup_printf (" - %s\n", str);
+ } else {
+ tag = "fancy-event";
+ msg = g_strdup_printf (" - %s\n", str);
+ }
+
+ if (priv->last_block_type != BLOCK_TYPE_EVENT) {
+ /* Comment out for now. */
+ /*chat_view_append_spacing (view);*/
+ }
+
+ chat_view_maybe_append_date_and_time (view, NULL);
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer, &iter,
+ msg, -1,
+ tag,
+ NULL);
+ g_free (msg);
+
+ if (bottom) {
+ gossip_chat_view_scroll_down (view);
+ }
+
+ priv->last_block_type = BLOCK_TYPE_EVENT;
+}
+
+void
+gossip_chat_view_append_button (GossipChatView *view,
+ const gchar *message,
+ GtkWidget *button1,
+ GtkWidget *button2)
+{
+ GossipChatViewPriv *priv;
+ GtkTextChildAnchor *anchor;
+ GtkTextIter iter;
+ gboolean bottom;
+ const gchar *tag;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+ g_return_if_fail (button1 != NULL);
+
+ priv = GET_PRIV (view);
+
+ if (priv->irc_style) {
+ tag = "irc-invite";
+ } else {
+ tag = "fancy-invite";
+ }
+
+ bottom = chat_view_is_scrolled_down (view);
+
+ chat_view_maybe_append_date_and_time (view, NULL);
+
+ if (message) {
+ chat_view_append_text (view, message, tag);
+ }
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+
+ anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
+ gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button1, anchor);
+ gtk_widget_show (button1);
+
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ " ",
+ 1,
+ tag,
+ NULL);
+
+ if (button2) {
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+
+ anchor = gtk_text_buffer_create_child_anchor (priv->buffer, &iter);
+ gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (view), button2, anchor);
+ gtk_widget_show (button2);
+
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ " ",
+ 1,
+ tag,
+ NULL);
+ }
+
+ gtk_text_buffer_get_end_iter (priv->buffer, &iter);
+ gtk_text_buffer_insert_with_tags_by_name (priv->buffer,
+ &iter,
+ "\n\n",
+ 2,
+ tag,
+ NULL);
+
+ if (bottom) {
+ gossip_chat_view_scroll_down (view);
+ }
+
+ priv->last_block_type = BLOCK_TYPE_INVITE;
+}
+
+void
+gossip_chat_view_scroll (GossipChatView *view,
+ gboolean allow_scrolling)
+{
+ GossipChatViewPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ priv = GET_PRIV (view);
+
+ priv->allow_scrolling = allow_scrolling;
+
+ gossip_debug (DEBUG_DOMAIN, "Scrolling %s",
+ allow_scrolling ? "enabled" : "disabled");
+}
+
+void
+gossip_chat_view_scroll_down (GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ GtkTextMark *mark;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ priv = GET_PRIV (view);
+
+ if (!priv->allow_scrolling) {
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Scrolling down");
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+ mark = gtk_text_buffer_create_mark (buffer,
+ NULL,
+ &iter,
+ FALSE);
+
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ mark,
+ 0.0,
+ FALSE,
+ 0,
+ 0);
+
+ gtk_text_buffer_delete_mark (buffer, mark);
+}
+
+gboolean
+gossip_chat_view_get_selection_bounds (GossipChatView *view,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ GtkTextBuffer *buffer;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ return gtk_text_buffer_get_selection_bounds (buffer, start, end);
+}
+
+void
+gossip_chat_view_clear (GossipChatView *view)
+{
+ GtkTextBuffer *buffer;
+ GossipChatViewPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ gtk_text_buffer_set_text (buffer, "", -1);
+
+ /* We set these back to the initial values so we get
+ * timestamps when clearing the window to know when
+ * conversations start.
+ */
+ priv = GET_PRIV (view);
+
+ priv->last_block_type = BLOCK_TYPE_NONE;
+ priv->last_timestamp = 0;
+}
+
+gboolean
+gossip_chat_view_find_previous (GossipChatView *view,
+ const gchar *search_criteria,
+ gboolean new_search)
+{
+ GossipChatViewPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter_at_mark;
+ GtkTextIter iter_match_start;
+ GtkTextIter iter_match_end;
+ gboolean found;
+ gboolean from_start = FALSE;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
+ g_return_val_if_fail (search_criteria != NULL, FALSE);
+
+ priv = GET_PRIV (view);
+
+ buffer = priv->buffer;
+
+ if (G_STR_EMPTY (search_criteria)) {
+ if (priv->find_mark_previous) {
+ gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
+
+ gtk_text_buffer_move_mark (buffer,
+ priv->find_mark_previous,
+ &iter_at_mark);
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ priv->find_mark_previous,
+ 0.0,
+ TRUE,
+ 0.0,
+ 0.0);
+ gtk_text_buffer_select_range (buffer,
+ &iter_at_mark,
+ &iter_at_mark);
+ }
+
+ return FALSE;
+ }
+
+ if (new_search) {
+ from_start = TRUE;
+ }
+
+ if (priv->find_mark_previous) {
+ gtk_text_buffer_get_iter_at_mark (buffer,
+ &iter_at_mark,
+ priv->find_mark_previous);
+ } else {
+ gtk_text_buffer_get_end_iter (buffer, &iter_at_mark);
+ from_start = TRUE;
+ }
+
+ priv->find_last_direction = FALSE;
+
+ found = gossip_text_iter_backward_search (&iter_at_mark,
+ search_criteria,
+ &iter_match_start,
+ &iter_match_end,
+ NULL);
+
+ if (!found) {
+ gboolean result = FALSE;
+
+ if (from_start) {
+ return result;
+ }
+
+ /* Here we wrap around. */
+ if (!new_search && !priv->find_wrapped) {
+ priv->find_wrapped = TRUE;
+ result = gossip_chat_view_find_previous (view,
+ search_criteria,
+ FALSE);
+ priv->find_wrapped = FALSE;
+ }
+
+ return result;
+ }
+
+ /* Set new mark and show on screen */
+ if (!priv->find_mark_previous) {
+ priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
+ &iter_match_start,
+ TRUE);
+ } else {
+ gtk_text_buffer_move_mark (buffer,
+ priv->find_mark_previous,
+ &iter_match_start);
+ }
+
+ if (!priv->find_mark_next) {
+ priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
+ &iter_match_end,
+ TRUE);
+ } else {
+ gtk_text_buffer_move_mark (buffer,
+ priv->find_mark_next,
+ &iter_match_end);
+ }
+
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ priv->find_mark_previous,
+ 0.0,
+ TRUE,
+ 0.5,
+ 0.5);
+
+ gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
+ gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
+
+ return TRUE;
+}
+
+gboolean
+gossip_chat_view_find_next (GossipChatView *view,
+ const gchar *search_criteria,
+ gboolean new_search)
+{
+ GossipChatViewPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter_at_mark;
+ GtkTextIter iter_match_start;
+ GtkTextIter iter_match_end;
+ gboolean found;
+ gboolean from_start = FALSE;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
+ g_return_val_if_fail (search_criteria != NULL, FALSE);
+
+ priv = GET_PRIV (view);
+
+ buffer = priv->buffer;
+
+ if (G_STR_EMPTY (search_criteria)) {
+ if (priv->find_mark_next) {
+ gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
+
+ gtk_text_buffer_move_mark (buffer,
+ priv->find_mark_next,
+ &iter_at_mark);
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ priv->find_mark_next,
+ 0.0,
+ TRUE,
+ 0.0,
+ 0.0);
+ gtk_text_buffer_select_range (buffer,
+ &iter_at_mark,
+ &iter_at_mark);
+ }
+
+ return FALSE;
+ }
+
+ if (new_search) {
+ from_start = TRUE;
+ }
+
+ if (priv->find_mark_next) {
+ gtk_text_buffer_get_iter_at_mark (buffer,
+ &iter_at_mark,
+ priv->find_mark_next);
+ } else {
+ gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
+ from_start = TRUE;
+ }
+
+ priv->find_last_direction = TRUE;
+
+ found = gossip_text_iter_forward_search (&iter_at_mark,
+ search_criteria,
+ &iter_match_start,
+ &iter_match_end,
+ NULL);
+
+ if (!found) {
+ gboolean result = FALSE;
+
+ if (from_start) {
+ return result;
+ }
+
+ /* Here we wrap around. */
+ if (!new_search && !priv->find_wrapped) {
+ priv->find_wrapped = TRUE;
+ result = gossip_chat_view_find_next (view,
+ search_criteria,
+ FALSE);
+ priv->find_wrapped = FALSE;
+ }
+
+ return result;
+ }
+
+ /* Set new mark and show on screen */
+ if (!priv->find_mark_next) {
+ priv->find_mark_next = gtk_text_buffer_create_mark (buffer, NULL,
+ &iter_match_end,
+ TRUE);
+ } else {
+ gtk_text_buffer_move_mark (buffer,
+ priv->find_mark_next,
+ &iter_match_end);
+ }
+
+ if (!priv->find_mark_previous) {
+ priv->find_mark_previous = gtk_text_buffer_create_mark (buffer, NULL,
+ &iter_match_start,
+ TRUE);
+ } else {
+ gtk_text_buffer_move_mark (buffer,
+ priv->find_mark_previous,
+ &iter_match_start);
+ }
+
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (view),
+ priv->find_mark_next,
+ 0.0,
+ TRUE,
+ 0.5,
+ 0.5);
+
+ gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &iter_match_start);
+ gtk_text_buffer_move_mark_by_name (buffer, "insert", &iter_match_end);
+
+ return TRUE;
+}
+
+
+void
+gossip_chat_view_find_abilities (GossipChatView *view,
+ const gchar *search_criteria,
+ gboolean *can_do_previous,
+ gboolean *can_do_next)
+{
+ GossipChatViewPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter_at_mark;
+ GtkTextIter iter_match_start;
+ GtkTextIter iter_match_end;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+ g_return_if_fail (search_criteria != NULL);
+ g_return_if_fail (can_do_previous != NULL && can_do_next != NULL);
+
+ priv = GET_PRIV (view);
+
+ buffer = priv->buffer;
+
+ if (can_do_previous) {
+ if (priv->find_mark_previous) {
+ gtk_text_buffer_get_iter_at_mark (buffer,
+ &iter_at_mark,
+ priv->find_mark_previous);
+ } else {
+ gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
+ }
+
+ *can_do_previous = gossip_text_iter_backward_search (&iter_at_mark,
+ search_criteria,
+ &iter_match_start,
+ &iter_match_end,
+ NULL);
+ }
+
+ if (can_do_next) {
+ if (priv->find_mark_next) {
+ gtk_text_buffer_get_iter_at_mark (buffer,
+ &iter_at_mark,
+ priv->find_mark_next);
+ } else {
+ gtk_text_buffer_get_start_iter (buffer, &iter_at_mark);
+ }
+
+ *can_do_next = gossip_text_iter_forward_search (&iter_at_mark,
+ search_criteria,
+ &iter_match_start,
+ &iter_match_end,
+ NULL);
+ }
+}
+
+void
+gossip_chat_view_highlight (GossipChatView *view,
+ const gchar *text)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ GtkTextIter iter_start;
+ GtkTextIter iter_end;
+ GtkTextIter iter_match_start;
+ GtkTextIter iter_match_end;
+ gboolean found;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ gtk_text_buffer_get_start_iter (buffer, &iter);
+
+ gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
+ gtk_text_buffer_remove_tag_by_name (buffer, "highlight",
+ &iter_start,
+ &iter_end);
+
+ if (G_STR_EMPTY (text)) {
+ return;
+ }
+
+ while (1) {
+ found = gossip_text_iter_forward_search (&iter,
+ text,
+ &iter_match_start,
+ &iter_match_end,
+ NULL);
+
+ if (!found) {
+ break;
+ }
+
+ gtk_text_buffer_apply_tag_by_name (buffer, "highlight",
+ &iter_match_start,
+ &iter_match_end);
+
+ iter = iter_match_end;
+ gtk_text_iter_forward_char (&iter);
+ }
+}
+
+void
+gossip_chat_view_copy_clipboard (GossipChatView *view)
+{
+ GtkTextBuffer *buffer;
+ GtkClipboard *clipboard;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+
+ gtk_text_buffer_copy_clipboard (buffer, clipboard);
+}
+
+gboolean
+gossip_chat_view_get_irc_style (GossipChatView *view)
+{
+ GossipChatViewPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_VIEW (view), FALSE);
+
+ priv = GET_PRIV (view);
+
+ return priv->irc_style;
+}
+
+void
+gossip_chat_view_set_irc_style (GossipChatView *view,
+ gboolean irc_style)
+{
+ GossipChatViewPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ priv = GET_PRIV (view);
+
+ priv->irc_style = irc_style;
+}
+
+void
+gossip_chat_view_set_margin (GossipChatView *view,
+ gint margin)
+{
+ GossipChatViewPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ priv = GET_PRIV (view);
+
+ g_object_set (view,
+ "left-margin", margin,
+ "right-margin", margin,
+ NULL);
+}
+
+GdkPixbuf *
+gossip_chat_view_get_smiley_image (GossipSmiley smiley)
+{
+ static GdkPixbuf *pixbufs[GOSSIP_SMILEY_COUNT];
+ static gboolean inited = FALSE;
+
+ if (!inited) {
+ gint i;
+
+ for (i = 0; i < GOSSIP_SMILEY_COUNT; i++) {
+ pixbufs[i] = gossip_pixbuf_from_smiley (i, GTK_ICON_SIZE_MENU);
+ }
+
+ inited = TRUE;
+ }
+
+ return pixbufs[smiley];
+}
+
+const gchar *
+gossip_chat_view_get_smiley_text (GossipSmiley smiley)
+{
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (smileys); i++) {
+ if (smileys[i].smiley != smiley) {
+ continue;
+ }
+
+ return smileys[i].pattern;
+ }
+
+ return NULL;
+}
+
+GtkWidget *
+gossip_chat_view_get_smiley_menu (GCallback callback,
+ gpointer user_data,
+ GtkTooltips *tooltips)
+{
+ GtkWidget *menu;
+ gint x;
+ gint y;
+ gint i;
+
+ g_return_val_if_fail (callback != NULL, NULL);
+
+ menu = gtk_menu_new ();
+
+ for (i = 0, x = 0, y = 0; i < GOSSIP_SMILEY_COUNT; i++) {
+ GtkWidget *item;
+ GtkWidget *image;
+ GdkPixbuf *pixbuf;
+ const gchar *smiley_text;
+
+ pixbuf = gossip_chat_view_get_smiley_image (i);
+ if (!pixbuf) {
+ continue;
+ }
+
+ image = gtk_image_new_from_pixbuf (pixbuf);
+
+ item = gtk_image_menu_item_new_with_label ("");
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+
+ gtk_menu_attach (GTK_MENU (menu), item,
+ x, x + 1, y, y + 1);
+
+ smiley_text = gossip_chat_view_get_smiley_text (i);
+
+ gtk_tooltips_set_tip (tooltips,
+ item,
+ smiley_text,
+ NULL);
+
+ g_object_set_data (G_OBJECT (item), "smiley_text", (gpointer) smiley_text);
+ g_signal_connect (item, "activate", callback, user_data);
+
+ if (x > 3) {
+ y++;
+ x = 0;
+ } else {
+ x++;
+ }
+ }
+
+ gtk_widget_show_all (menu);
+
+ return menu;
+}
+
+/* FIXME: Do we really need this? Better to do it internally only at setup time,
+ * we will never change it on the fly.
+ */
+void
+gossip_chat_view_set_is_group_chat (GossipChatView *view,
+ gboolean is_group_chat)
+{
+ GossipChatViewPriv *priv;
+ gboolean theme_rooms = FALSE;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_VIEW (view));
+
+ priv = GET_PRIV (view);
+
+ priv->is_group_chat = is_group_chat;
+
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
+ &theme_rooms);
+
+ if (!theme_rooms && is_group_chat) {
+ gossip_theme_manager_apply (gossip_theme_manager_get (),
+ view,
+ NULL);
+ } else {
+ gossip_theme_manager_apply_saved (gossip_theme_manager_get (),
+ view);
+ }
+}
diff --git a/libempathy-gtk/gossip-chat-view.h b/libempathy-gtk/gossip-chat-view.h
new file mode 100644
index 000000000..2a7b11472
--- /dev/null
+++ b/libempathy-gtk/gossip-chat-view.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_CHAT_VIEW_H__
+#define __GOSSIP_CHAT_VIEW_H__
+
+#include <gtk/gtktextview.h>
+#include <gtk/gtktooltips.h>
+
+#include <libempathy/gossip-contact.h>
+#include <libempathy/gossip-message.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CHAT_VIEW (gossip_chat_view_get_type ())
+#define GOSSIP_CHAT_VIEW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CHAT_VIEW, GossipChatView))
+#define GOSSIP_CHAT_VIEW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GOSSIP_TYPE_CHAT_VIEW, GossipChatViewClass))
+#define GOSSIP_IS_CHAT_VIEW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CHAT_VIEW))
+#define GOSSIP_IS_CHAT_VIEW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CHAT_VIEW))
+#define GOSSIP_CHAT_VIEW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CHAT_VIEW, GossipChatViewClass))
+
+typedef struct _GossipChatView GossipChatView;
+typedef struct _GossipChatViewClass GossipChatViewClass;
+typedef struct _GossipChatViewPriv GossipChatViewPriv;
+
+struct _GossipChatView {
+ GtkTextView parent;
+};
+
+struct _GossipChatViewClass {
+ GtkTextViewClass parent_class;
+};
+
+typedef enum {
+ GOSSIP_SMILEY_NORMAL, /* :) */
+ GOSSIP_SMILEY_WINK, /* ;) */
+ GOSSIP_SMILEY_BIGEYE, /* =) */
+ GOSSIP_SMILEY_NOSE, /* :-) */
+ GOSSIP_SMILEY_CRY, /* :'( */
+ GOSSIP_SMILEY_SAD, /* :( */
+ GOSSIP_SMILEY_SCEPTICAL, /* :/ */
+ GOSSIP_SMILEY_BIGSMILE, /* :D */
+ GOSSIP_SMILEY_INDIFFERENT, /* :| */
+ GOSSIP_SMILEY_TOUNGE, /* :p */
+ GOSSIP_SMILEY_SHOCKED, /* :o */
+ GOSSIP_SMILEY_COOL, /* 8) */
+ GOSSIP_SMILEY_SORRY, /* *| */
+ GOSSIP_SMILEY_KISS, /* :* */
+ GOSSIP_SMILEY_SHUTUP, /* :# */
+ GOSSIP_SMILEY_YAWN, /* |O */
+ GOSSIP_SMILEY_CONFUSED, /* :$ */
+ GOSSIP_SMILEY_ANGEL, /* <) */
+ GOSSIP_SMILEY_OOOH, /* :x */
+ GOSSIP_SMILEY_LOOKAWAY, /* *) */
+ GOSSIP_SMILEY_BLUSH, /* *S */
+ GOSSIP_SMILEY_COOLBIGSMILE, /* 8D */
+ GOSSIP_SMILEY_ANGRY, /* :@ */
+ GOSSIP_SMILEY_BOSS, /* @) */
+ GOSSIP_SMILEY_MONKEY, /* #) */
+ GOSSIP_SMILEY_SILLY, /* O) */
+ GOSSIP_SMILEY_SICK, /* +o( */
+
+ GOSSIP_SMILEY_COUNT
+} GossipSmiley;
+
+GType gossip_chat_view_get_type (void) G_GNUC_CONST;
+GossipChatView *gossip_chat_view_new (void);
+void gossip_chat_view_append_message (GossipChatView *view,
+ GossipMessage *msg);
+void gossip_chat_view_append_event (GossipChatView *view,
+ const gchar *str);
+void gossip_chat_view_append_button (GossipChatView *view,
+ const gchar *message,
+ GtkWidget *button1,
+ GtkWidget *button2);
+void gossip_chat_view_set_margin (GossipChatView *view,
+ gint margin);
+void gossip_chat_view_scroll (GossipChatView *view,
+ gboolean allow_scrolling);
+void gossip_chat_view_scroll_down (GossipChatView *view);
+gboolean gossip_chat_view_get_selection_bounds (GossipChatView *view,
+ GtkTextIter *start,
+ GtkTextIter *end);
+void gossip_chat_view_clear (GossipChatView *view);
+gboolean gossip_chat_view_find_previous (GossipChatView *view,
+ const gchar *search_criteria,
+ gboolean new_search);
+gboolean gossip_chat_view_find_next (GossipChatView *view,
+ const gchar *search_criteria,
+ gboolean new_search);
+void gossip_chat_view_find_abilities (GossipChatView *view,
+ const gchar *search_criteria,
+ gboolean *can_do_previous,
+ gboolean *can_do_next);
+void gossip_chat_view_highlight (GossipChatView *view,
+ const gchar *text);
+void gossip_chat_view_copy_clipboard (GossipChatView *view);
+gboolean gossip_chat_view_get_irc_style (GossipChatView *view);
+void gossip_chat_view_set_irc_style (GossipChatView *view,
+ gboolean irc_style);
+void gossip_chat_view_set_margin (GossipChatView *view,
+ gint margin);
+GdkPixbuf * gossip_chat_view_get_smiley_image (GossipSmiley smiley);
+const gchar * gossip_chat_view_get_smiley_text (GossipSmiley smiley);
+GtkWidget * gossip_chat_view_get_smiley_menu (GCallback callback,
+ gpointer user_data,
+ GtkTooltips *tooltips);
+void gossip_chat_view_set_is_group_chat (GossipChatView *view,
+ gboolean is_group_chat);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CHAT_VIEW_H__ */
diff --git a/libempathy-gtk/gossip-chat-window.c b/libempathy-gtk/gossip-chat-window.c
new file mode 100644
index 000000000..da305e60b
--- /dev/null
+++ b/libempathy-gtk/gossip-chat-window.c
@@ -0,0 +1,1894 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+
+#include <libempathy/gossip-contact.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-message.h>
+#include <libempathy/gossip-conf.h>
+
+#include "gossip-chat-window.h"
+//#include "gossip-add-contact-dialog.h"
+//#include "gossip-chat-invite.h"
+//#include "gossip-contact-info-dialog.h"
+//#include "gossip-log-window.h"
+//#include "gossip-new-chatroom-dialog.h"
+#include "gossip-preferences.h"
+#include "gossip-private-chat.h"
+//#include "gossip-sound.h"
+#include "gossip-stock.h"
+#include "gossip-ui-utils.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHAT_WINDOW, GossipChatWindowPriv))
+
+#define DEBUG_DOMAIN "ChatWindow"
+
+#define URGENCY_TIMEOUT 60*1000
+
+struct _GossipChatWindowPriv {
+ GList *chats;
+ GList *chats_new_msg;
+ GList *chats_composing;
+
+ GossipChat *current_chat;
+
+ gboolean page_added;
+ gboolean dnd_same_window;
+
+ guint urgency_timeout_id;
+
+ GtkWidget *dialog;
+ GtkWidget *notebook;
+
+ GtkTooltips *tooltips;
+
+ /* Menu items. */
+ GtkWidget *menu_conv_clear;
+ GtkWidget *menu_conv_insert_smiley;
+ GtkWidget *menu_conv_log;
+ GtkWidget *menu_conv_separator;
+ GtkWidget *menu_conv_add_contact;
+ GtkWidget *menu_conv_info;
+ GtkWidget *menu_conv_close;
+
+ GtkWidget *menu_room;
+ GtkWidget *menu_room_set_topic;
+ GtkWidget *menu_room_join_new;
+ GtkWidget *menu_room_invite;
+ GtkWidget *menu_room_add;
+ GtkWidget *menu_room_show_contacts;
+
+ GtkWidget *menu_edit_cut;
+ GtkWidget *menu_edit_copy;
+ GtkWidget *menu_edit_paste;
+
+ GtkWidget *menu_tabs_next;
+ GtkWidget *menu_tabs_prev;
+ GtkWidget *menu_tabs_left;
+ GtkWidget *menu_tabs_right;
+ GtkWidget *menu_tabs_detach;
+
+ guint save_geometry_id;
+};
+
+static void gossip_chat_window_class_init (GossipChatWindowClass *klass);
+static void gossip_chat_window_init (GossipChatWindow *window);
+static void gossip_chat_window_finalize (GObject *object);
+static GdkPixbuf *chat_window_get_status_pixbuf (GossipChatWindow *window,
+ GossipChat *chat);
+static void chat_window_accel_cb (GtkAccelGroup *accelgroup,
+ GObject *object,
+ guint key,
+ GdkModifierType mod,
+ GossipChatWindow *window);
+static void chat_window_close_clicked_cb (GtkWidget *button,
+ GossipChat *chat);
+static GtkWidget *chat_window_create_label (GossipChatWindow *window,
+ GossipChat *chat);
+static void chat_window_update_status (GossipChatWindow *window,
+ GossipChat *chat);
+static void chat_window_update_title (GossipChatWindow *window,
+ GossipChat *chat);
+static void chat_window_update_menu (GossipChatWindow *window);
+static gboolean chat_window_save_geometry_timeout_cb (GossipChatWindow *window);
+static gboolean chat_window_configure_event_cb (GtkWidget *widget,
+ GdkEventConfigure *event,
+ GossipChatWindow *window);
+static void chat_window_conv_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_clear_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_info_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_add_contact_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_log_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_show_contacts_toggled_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_edit_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_insert_smiley_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_close_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_room_set_topic_activate_cb(GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_room_join_new_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_room_invite_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_room_add_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_cut_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_copy_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_paste_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_tabs_left_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_tabs_right_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static void chat_window_detach_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window);
+static gboolean chat_window_delete_event_cb (GtkWidget *dialog,
+ GdkEvent *event,
+ GossipChatWindow *window);
+static void chat_window_status_changed_cb (GossipChat *chat,
+ GossipChatWindow *window);
+static void chat_window_update_tooltip (GossipChatWindow *window,
+ GossipChat *chat);
+static void chat_window_name_changed_cb (GossipChat *chat,
+ const gchar *name,
+ GossipChatWindow *window);
+static void chat_window_composing_cb (GossipChat *chat,
+ gboolean is_composing,
+ GossipChatWindow *window);
+static void chat_window_new_message_cb (GossipChat *chat,
+ GossipMessage *message,
+ GossipChatWindow *window);
+static GtkNotebook* chat_window_detach_hook (GtkNotebook *source,
+ GtkWidget *page,
+ gint x,
+ gint y,
+ gpointer user_data);
+static void chat_window_page_switched_cb (GtkNotebook *notebook,
+ GtkNotebookPage *page,
+ gint page_num,
+ GossipChatWindow *window);
+static void chat_window_page_reordered_cb (GtkNotebook *notebook,
+ GtkWidget *widget,
+ guint page_num,
+ GossipChatWindow *window);
+static void chat_window_page_added_cb (GtkNotebook *notebook,
+ GtkWidget *child,
+ guint page_num,
+ GossipChatWindow *window);
+static void chat_window_page_removed_cb (GtkNotebook *notebook,
+ GtkWidget *child,
+ guint page_num,
+ GossipChatWindow *window);
+static gboolean chat_window_focus_in_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ GossipChatWindow *window);
+static void chat_window_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ GossipChatWindow *window);
+static void chat_window_set_urgency_hint (GossipChatWindow *window,
+ gboolean urgent);
+
+
+static GList *chat_windows = NULL;
+
+static const guint tab_accel_keys[] = {
+ GDK_1, GDK_2, GDK_3, GDK_4, GDK_5,
+ GDK_6, GDK_7, GDK_8, GDK_9, GDK_0
+};
+
+typedef enum {
+ DND_DRAG_TYPE_CONTACT_ID,
+ DND_DRAG_TYPE_TAB
+} DndDragType;
+
+static const GtkTargetEntry drag_types_dest[] = {
+ { "text/contact-id", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_CONTACT_ID },
+ { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
+};
+
+G_DEFINE_TYPE (GossipChatWindow, gossip_chat_window, G_TYPE_OBJECT);
+
+static void
+gossip_chat_window_class_init (GossipChatWindowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gossip_chat_window_finalize;
+
+ g_type_class_add_private (object_class, sizeof (GossipChatWindowPriv));
+
+ /* Set up a style for the close button with no focus padding. */
+ gtk_rc_parse_string (
+ "style \"gossip-close-button-style\"\n"
+ "{\n"
+ " GtkWidget::focus-padding = 0\n"
+ " xthickness = 0\n"
+ " ythickness = 0\n"
+ "}\n"
+ "widget \"*.gossip-close-button\" style \"gossip-close-button-style\"");
+
+ gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL);
+}
+
+static void
+gossip_chat_window_init (GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GladeXML *glade;
+ GtkAccelGroup *accel_group;
+ GtkWidget *image;
+ GClosure *closure;
+ GtkWidget *menu_conv;
+ GtkWidget *menu;
+ gint i;
+ GtkWidget *chat_vbox;
+
+ priv = GET_PRIV (window);
+
+ priv->tooltips = g_object_ref (gtk_tooltips_new ());
+ gtk_object_sink (GTK_OBJECT (priv->tooltips));
+
+ glade = gossip_glade_get_file ("empathy-chat.glade",
+ "chat_window",
+ NULL,
+ "chat_window", &priv->dialog,
+ "chat_vbox", &chat_vbox,
+ "menu_conv", &menu_conv,
+ "menu_conv_clear", &priv->menu_conv_clear,
+ "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
+ "menu_conv_log", &priv->menu_conv_log,
+ "menu_conv_separator", &priv->menu_conv_separator,
+ "menu_conv_add_contact", &priv->menu_conv_add_contact,
+ "menu_conv_info", &priv->menu_conv_info,
+ "menu_conv_close", &priv->menu_conv_close,
+ "menu_room", &priv->menu_room,
+ "menu_room_set_topic", &priv->menu_room_set_topic,
+ "menu_room_join_new", &priv->menu_room_join_new,
+ "menu_room_invite", &priv->menu_room_invite,
+ "menu_room_add", &priv->menu_room_add,
+ "menu_room_show_contacts", &priv->menu_room_show_contacts,
+ "menu_edit_cut", &priv->menu_edit_cut,
+ "menu_edit_copy", &priv->menu_edit_copy,
+ "menu_edit_paste", &priv->menu_edit_paste,
+ "menu_tabs_next", &priv->menu_tabs_next,
+ "menu_tabs_prev", &priv->menu_tabs_prev,
+ "menu_tabs_left", &priv->menu_tabs_left,
+ "menu_tabs_right", &priv->menu_tabs_right,
+ "menu_tabs_detach", &priv->menu_tabs_detach,
+ NULL);
+
+ gossip_glade_connect (glade,
+ window,
+ "chat_window", "configure-event", chat_window_configure_event_cb,
+ "menu_conv", "activate", chat_window_conv_activate_cb,
+ "menu_conv_clear", "activate", chat_window_clear_activate_cb,
+ "menu_conv_log", "activate", chat_window_log_activate_cb,
+ "menu_conv_add_contact", "activate", chat_window_add_contact_activate_cb,
+ "menu_conv_info", "activate", chat_window_info_activate_cb,
+ "menu_conv_close", "activate", chat_window_close_activate_cb,
+ "menu_room_set_topic", "activate", chat_window_room_set_topic_activate_cb,
+ "menu_room_join_new", "activate", chat_window_room_join_new_activate_cb,
+ "menu_room_invite", "activate", chat_window_room_invite_activate_cb,
+ "menu_room_add", "activate", chat_window_room_add_activate_cb,
+ "menu_edit", "activate", chat_window_edit_activate_cb,
+ "menu_edit_cut", "activate", chat_window_cut_activate_cb,
+ "menu_edit_copy", "activate", chat_window_copy_activate_cb,
+ "menu_edit_paste", "activate", chat_window_paste_activate_cb,
+ "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
+ "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
+ "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
+ NULL);
+
+ g_object_unref (glade);
+
+ priv->notebook = gtk_notebook_new ();
+ gtk_notebook_set_group_id (GTK_NOTEBOOK (priv->notebook), 1);
+ gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
+ gtk_widget_show (priv->notebook);
+
+ /* Set up accels */
+ accel_group = gtk_accel_group_new ();
+ gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
+
+ for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
+ closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
+ window,
+ NULL);
+ gtk_accel_group_connect (accel_group,
+ tab_accel_keys[i],
+ GDK_MOD1_MASK,
+ 0,
+ closure);
+ }
+
+ g_object_unref (accel_group);
+
+ /* Set the contact information menu item image to the Gossip
+ * stock image
+ */
+ image = gtk_image_menu_item_get_image (GTK_IMAGE_MENU_ITEM (priv->menu_conv_info));
+ gtk_image_set_from_stock (GTK_IMAGE (image),
+ GOSSIP_STOCK_CONTACT_INFORMATION,
+ GTK_ICON_SIZE_MENU);
+
+ /* Set up smiley menu */
+ menu = gossip_chat_view_get_smiley_menu (
+ G_CALLBACK (chat_window_insert_smiley_activate_cb),
+ window,
+ priv->tooltips);
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (priv->menu_conv_insert_smiley), menu);
+
+ /* Set up signals we can't do with glade since we may need to
+ * block/unblock them at some later stage.
+ */
+
+ g_signal_connect (priv->dialog,
+ "delete_event",
+ G_CALLBACK (chat_window_delete_event_cb),
+ window);
+
+ g_signal_connect (priv->menu_room_show_contacts,
+ "toggled",
+ G_CALLBACK (chat_window_show_contacts_toggled_cb),
+ window);
+
+ g_signal_connect_swapped (priv->menu_tabs_prev,
+ "activate",
+ G_CALLBACK (gtk_notebook_prev_page),
+ priv->notebook);
+ g_signal_connect_swapped (priv->menu_tabs_next,
+ "activate",
+ G_CALLBACK (gtk_notebook_next_page),
+ priv->notebook);
+
+ g_signal_connect (priv->dialog,
+ "focus_in_event",
+ G_CALLBACK (chat_window_focus_in_event_cb),
+ window);
+ g_signal_connect_after (priv->notebook,
+ "switch_page",
+ G_CALLBACK (chat_window_page_switched_cb),
+ window);
+ g_signal_connect (priv->notebook,
+ "page_reordered",
+ G_CALLBACK (chat_window_page_reordered_cb),
+ window);
+ g_signal_connect (priv->notebook,
+ "page_added",
+ G_CALLBACK (chat_window_page_added_cb),
+ window);
+ g_signal_connect (priv->notebook,
+ "page_removed",
+ G_CALLBACK (chat_window_page_removed_cb),
+ window);
+
+ /* Set up drag and drop */
+ gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
+ GTK_DEST_DEFAULT_ALL,
+ drag_types_dest,
+ G_N_ELEMENTS (drag_types_dest),
+ GDK_ACTION_MOVE);
+
+ g_signal_connect (priv->notebook,
+ "drag-data-received",
+ G_CALLBACK (chat_window_drag_data_received),
+ window);
+
+ chat_windows = g_list_prepend (chat_windows, window);
+
+ /* Set up private details */
+ priv->chats = NULL;
+ priv->chats_new_msg = NULL;
+ priv->chats_composing = NULL;
+ priv->current_chat = NULL;
+}
+
+/* Returns the window to open a new tab in if there is only one window
+ * visble, otherwise, returns NULL indicating that a new window should
+ * be added.
+ */
+GossipChatWindow *
+gossip_chat_window_get_default (void)
+{
+ GList *l;
+ gboolean separate_windows = TRUE;
+
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_UI_SEPARATE_CHAT_WINDOWS,
+ &separate_windows);
+
+ if (separate_windows) {
+ /* Always create a new window */
+ return NULL;
+ }
+
+ for (l = chat_windows; l; l = l->next) {
+ GossipChatWindow *chat_window;
+ GtkWidget *dialog;
+ GdkWindow *window;
+ gboolean visible;
+
+ chat_window = l->data;
+
+ dialog = gossip_chat_window_get_dialog (chat_window);
+ window = dialog->window;
+
+ g_object_get (dialog,
+ "visible", &visible,
+ NULL);
+
+ visible = visible && !(gdk_window_get_state (window) & GDK_WINDOW_STATE_ICONIFIED);
+
+ if (visible) {
+ /* Found a visible window on this desktop */
+ return chat_window;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+gossip_chat_window_finalize (GObject *object)
+{
+ GossipChatWindow *window;
+ GossipChatWindowPriv *priv;
+
+ window = GOSSIP_CHAT_WINDOW (object);
+ priv = GET_PRIV (window);
+
+ gossip_debug (DEBUG_DOMAIN, "Finalized: %p", object);
+
+ if (priv->save_geometry_id != 0) {
+ g_source_remove (priv->save_geometry_id);
+ }
+
+ if (priv->urgency_timeout_id != 0) {
+ g_source_remove (priv->urgency_timeout_id);
+ }
+
+ chat_windows = g_list_remove (chat_windows, window);
+ gtk_widget_destroy (priv->dialog);
+
+ g_object_unref (priv->tooltips);
+
+ G_OBJECT_CLASS (gossip_chat_window_parent_class)->finalize (object);
+}
+
+static GdkPixbuf *
+chat_window_get_status_pixbuf (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ GdkPixbuf *pixbuf;
+
+ priv = GET_PRIV (window);
+
+ if (g_list_find (priv->chats_new_msg, chat)) {
+ pixbuf = gossip_stock_render (GOSSIP_STOCK_MESSAGE,
+ GTK_ICON_SIZE_MENU);
+ }
+ else if (g_list_find (priv->chats_composing, chat)) {
+ pixbuf = gossip_stock_render (GOSSIP_STOCK_TYPING,
+ GTK_ICON_SIZE_MENU);
+ }
+ else {
+ pixbuf = gossip_chat_get_status_pixbuf (chat);
+ }
+
+ return pixbuf;
+}
+
+static void
+chat_window_accel_cb (GtkAccelGroup *accelgroup,
+ GObject *object,
+ guint key,
+ GdkModifierType mod,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gint num = -1;
+ gint i;
+
+ priv = GET_PRIV (window);
+
+ for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
+ if (tab_accel_keys[i] == key) {
+ num = i;
+ break;
+ }
+ }
+
+ if (num != -1) {
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
+ }
+}
+
+static void
+chat_window_close_clicked_cb (GtkWidget *button,
+ GossipChat *chat)
+{
+ GossipChatWindow *window;
+
+ window = gossip_chat_get_window (chat);
+ gossip_chat_window_remove_chat (window, chat);
+}
+
+static void
+chat_window_close_button_style_set_cb (GtkWidget *button,
+ GtkStyle *previous_style,
+ gpointer user_data)
+{
+ gint h, w;
+
+ gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
+ GTK_ICON_SIZE_MENU, &w, &h);
+
+ gtk_widget_set_size_request (button, w, h);
+}
+
+static GtkWidget *
+chat_window_create_label (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ GtkWidget *hbox;
+ GtkWidget *name_label;
+ GtkWidget *status_image;
+ GtkWidget *close_button;
+ GtkWidget *close_image;
+ GtkWidget *event_box;
+ GtkWidget *event_box_hbox;
+ PangoAttrList *attr_list;
+ PangoAttribute *attr;
+
+ priv = GET_PRIV (window);
+
+ /* The spacing between the button and the label. */
+ hbox = gtk_hbox_new (FALSE, 0);
+
+ event_box = gtk_event_box_new ();
+ gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
+
+ name_label = gtk_label_new (gossip_chat_get_name (chat));
+ gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
+
+ attr_list = pango_attr_list_new ();
+ attr = pango_attr_scale_new (1/1.2);
+ attr->start_index = 0;
+ attr->end_index = -1;
+ pango_attr_list_insert (attr_list, attr);
+ gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
+ pango_attr_list_unref (attr_list);
+
+ gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
+ gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
+ g_object_set_data (G_OBJECT (chat), "chat-window-tab-label", name_label);
+
+ status_image = gtk_image_new ();
+
+ /* Spacing between the icon and label. */
+ event_box_hbox = gtk_hbox_new (FALSE, 0);
+
+ gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
+
+ g_object_set_data (G_OBJECT (chat), "chat-window-tab-image", status_image);
+ g_object_set_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget", event_box);
+
+ close_button = gtk_button_new ();
+ gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
+
+ /* We don't want focus/keynav for the button to avoid clutter, and
+ * Ctrl-W works anyway.
+ */
+ GTK_WIDGET_UNSET_FLAGS (close_button, GTK_CAN_FOCUS);
+ GTK_WIDGET_UNSET_FLAGS (close_button, GTK_CAN_DEFAULT);
+
+ /* Set the name to make the special rc style match. */
+ gtk_widget_set_name (close_button, "gossip-close-button");
+
+ close_image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
+
+ gtk_container_add (GTK_CONTAINER (close_button), close_image);
+
+ gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
+ gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
+ gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
+
+ /* React to theme changes and also used to setup the initial size
+ * correctly.
+ */
+ g_signal_connect (close_button,
+ "style-set",
+ G_CALLBACK (chat_window_close_button_style_set_cb),
+ chat);
+
+ g_signal_connect (close_button,
+ "clicked",
+ G_CALLBACK (chat_window_close_clicked_cb),
+ chat);
+
+ /* Set up tooltip */
+ chat_window_update_tooltip (window, chat);
+
+ gtk_widget_show_all (hbox);
+
+ return hbox;
+}
+
+static void
+chat_window_update_status (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GtkImage *image;
+ GdkPixbuf *pixbuf;
+
+ pixbuf = chat_window_get_status_pixbuf (window, chat);
+ image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
+ gtk_image_set_from_pixbuf (image, pixbuf);
+
+ g_object_unref (pixbuf);
+
+ chat_window_update_title (window, chat);
+ chat_window_update_tooltip (window, chat);
+}
+
+static void
+chat_window_update_title (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ GdkPixbuf *pixbuf = NULL;
+ const gchar *str;
+ gchar *title;
+ gint n_chats;
+
+ priv = GET_PRIV (window);
+
+ n_chats = g_list_length (priv->chats);
+ if (n_chats == 1) {
+ if (priv->chats_new_msg) {
+ title = g_strdup_printf (
+ "%s - %s",
+ gossip_chat_get_name (priv->current_chat),
+ _("New Message"));
+ }
+ else if (gossip_chat_is_group_chat (priv->current_chat)) {
+ title = g_strdup_printf (
+ "%s - %s",
+ gossip_chat_get_name (priv->current_chat),
+ _("Chat Room"));
+ } else {
+ title = g_strdup_printf (
+ "%s - %s",
+ gossip_chat_get_name (priv->current_chat),
+ _("Conversation"));
+ }
+ } else {
+ if (priv->chats_new_msg) {
+ GString *names;
+ GList *l;
+ gint n_messages = 0;
+
+ names = g_string_new (NULL);
+
+ for (l = priv->chats_new_msg; l; l = l->next) {
+ n_messages++;
+ g_string_append (names,
+ gossip_chat_get_name (l->data));
+ if (l->next) {
+ g_string_append (names, ", ");
+ }
+ }
+
+ str = ngettext ("New Message", "New Messages", n_messages);
+ title = g_strdup_printf ("%s - %s", names->str, str);
+ g_string_free (names, TRUE);
+ } else {
+ str = ngettext ("Conversation", "Conversations (%d)", n_chats);
+ title = g_strdup_printf (str, n_chats);
+ }
+ }
+
+ gtk_window_set_title (GTK_WINDOW (priv->dialog), title);
+ g_free (title);
+
+ if (priv->chats_new_msg) {
+ pixbuf = gossip_stock_render (GOSSIP_STOCK_MESSAGE,
+ GTK_ICON_SIZE_MENU);
+ } else {
+ pixbuf = NULL;
+ }
+
+ gtk_window_set_icon (GTK_WINDOW (priv->dialog), pixbuf);
+
+ if (pixbuf) {
+ g_object_unref (pixbuf);
+ }
+}
+
+static void
+chat_window_update_menu (GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gboolean first_page;
+ gboolean last_page;
+ gboolean is_connected;
+ gint num_pages;
+ gint page_num;
+
+ priv = GET_PRIV (window);
+
+ /* Notebook pages */
+ page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
+ num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
+ first_page = (page_num == 0);
+ last_page = (page_num == (num_pages - 1));
+
+ gtk_widget_set_sensitive (priv->menu_tabs_next, !last_page);
+ gtk_widget_set_sensitive (priv->menu_tabs_prev, !first_page);
+ gtk_widget_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
+ gtk_widget_set_sensitive (priv->menu_tabs_left, !first_page);
+ gtk_widget_set_sensitive (priv->menu_tabs_right, !last_page);
+
+ is_connected = gossip_chat_is_connected (priv->current_chat);
+
+ if (gossip_chat_is_group_chat (priv->current_chat)) {
+#if 0
+FIXME:
+ GossipGroupChat *group_chat;
+ GossipChatroomManager *manager;
+ GossipChatroom *chatroom;
+ GossipChatroomId id;
+ gboolean saved;
+
+ group_chat = GOSSIP_GROUP_CHAT (priv->current_chat);
+ chatroom = gossip_group_chat_get_chatroom (group_chat);
+
+ /* Show / Hide widgets */
+ gtk_widget_show (priv->menu_room);
+
+ gtk_widget_hide (priv->menu_conv_add_contact);
+ gtk_widget_hide (priv->menu_conv_info);
+ gtk_widget_hide (priv->menu_conv_separator);
+
+ /* Can we add this room to our favourites and are we
+ * connected to the room?
+ */
+ manager = gossip_app_get_chatroom_manager ();
+ id = gossip_chatroom_get_id (chatroom);
+ saved = gossip_chatroom_manager_find (manager, id) != NULL;
+
+ gtk_widget_set_sensitive (priv->menu_room_add, !saved);
+ gtk_widget_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
+ gtk_widget_set_sensitive (priv->menu_room_join_new, is_connected);
+ gtk_widget_set_sensitive (priv->menu_room_invite, is_connected);
+
+ /* We need to block the signal here because all we are
+ * really trying to do is check or uncheck the menu
+ * item. If we don't do this we get funny behaviour
+ * with 2 or more group chat windows where showing
+ * contacts doesn't do anything.
+ */
+ show_contacts = gossip_chat_get_show_contacts (priv->current_chat);
+
+ g_signal_handlers_block_by_func (priv->menu_room_show_contacts,
+ chat_window_show_contacts_toggled_cb,
+ window);
+
+ g_object_set (priv->menu_room_show_contacts,
+ "active", show_contacts,
+ NULL);
+
+ g_signal_handlers_unblock_by_func (priv->menu_room_show_contacts,
+ chat_window_show_contacts_toggled_cb,
+ window);
+#endif
+ } else {
+ GossipSubscription subscription;
+ GossipContact *contact;
+
+ /* Show / Hide widgets */
+ gtk_widget_hide (priv->menu_room);
+
+ contact = gossip_chat_get_contact (priv->current_chat);
+ subscription = gossip_contact_get_subscription (contact);
+ if (!(subscription & GOSSIP_SUBSCRIPTION_FROM)) {
+ gtk_widget_show (priv->menu_conv_add_contact);
+ } else {
+ gtk_widget_hide (priv->menu_conv_add_contact);
+ }
+
+ gtk_widget_show (priv->menu_conv_separator);
+ gtk_widget_show (priv->menu_conv_info);
+
+ /* Are we connected? */
+ gtk_widget_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
+ gtk_widget_set_sensitive (priv->menu_conv_add_contact, is_connected);
+ gtk_widget_set_sensitive (priv->menu_conv_info, is_connected);
+ }
+}
+
+static void
+chat_window_insert_smiley_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChat *chat;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ const gchar *smiley;
+
+ priv = GET_PRIV (window);
+
+ chat = priv->current_chat;
+
+ smiley = g_object_get_data (G_OBJECT (menuitem), "smiley_text");
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+ gtk_text_buffer_insert (buffer, &iter,
+ smiley, -1);
+}
+
+static void
+chat_window_clear_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ gossip_chat_clear (priv->current_chat);
+}
+
+static void
+chat_window_add_contact_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipContact *contact;
+
+ priv = GET_PRIV (window);
+
+ contact = gossip_chat_get_contact (priv->current_chat);
+
+ // FIXME: gossip_add_contact_dialog_show (NULL, contact);
+}
+
+static void
+chat_window_log_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+/* FIXME:
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ if (gossip_chat_is_group_chat (priv->current_chat)) {
+ GossipGroupChat *group_chat;
+ GossipChatroom *chatroom;
+
+ group_chat = GOSSIP_GROUP_CHAT (priv->current_chat);
+ chatroom = gossip_group_chat_get_chatroom (group_chat);
+ gossip_log_window_show (NULL, chatroom);
+ } else {
+ GossipContact *contact;
+
+ contact = gossip_chat_get_contact (priv->current_chat);
+ gossip_log_window_show (contact, NULL);
+ }
+*/
+}
+
+static void
+chat_window_info_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipContact *contact;
+
+ priv = GET_PRIV (window);
+
+ contact = gossip_chat_get_contact (priv->current_chat);
+
+/*FIXME: gossip_contact_info_dialog_show (contact,
+ GTK_WINDOW (priv->dialog));*/
+}
+
+static gboolean
+chat_window_save_geometry_timeout_cb (GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gint x, y, w, h;
+
+ priv = GET_PRIV (window);
+
+ gtk_window_get_size (GTK_WINDOW (priv->dialog), &w, &h);
+ gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
+
+ gossip_chat_save_geometry (priv->current_chat, x, y, w, h);
+
+ priv->save_geometry_id = 0;
+
+ return FALSE;
+}
+
+static gboolean
+chat_window_configure_event_cb (GtkWidget *widget,
+ GdkEventConfigure *event,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ /* Only save geometry information if there is ONE chat visible. */
+ if (g_list_length (priv->chats) > 1) {
+ return FALSE;
+ }
+
+ if (priv->save_geometry_id != 0) {
+ g_source_remove (priv->save_geometry_id);
+ }
+
+ priv->save_geometry_id =
+ g_timeout_add (500,
+ (GSourceFunc) chat_window_save_geometry_timeout_cb,
+ window);
+
+ return FALSE;
+}
+
+static void
+chat_window_conv_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gboolean log_exists = FALSE;
+
+ priv = GET_PRIV (window);
+/* FIXME:
+ if (gossip_chat_is_group_chat (priv->current_chat)) {
+ GossipGroupChat *group_chat;
+ GossipChatroom *chatroom;
+
+ group_chat = GOSSIP_GROUP_CHAT (priv->current_chat);
+ chatroom = gossip_group_chat_get_chatroom (group_chat);
+ if (chatroom) {
+ log_exists = gossip_log_exists_for_chatroom (chatroom);
+ }
+ } else {
+ GossipContact *contact;
+
+ contact = gossip_chat_get_contact (priv->current_chat);
+ if (contact) {
+ log_exists = gossip_log_exists_for_contact (contact);
+ }
+ }
+*/
+ gtk_widget_set_sensitive (priv->menu_conv_log, log_exists);
+}
+
+static void
+chat_window_show_contacts_toggled_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gboolean show;
+
+ priv = GET_PRIV (window);
+
+ g_return_if_fail (priv->current_chat != NULL);
+
+ show = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (priv->menu_room_show_contacts));
+ gossip_chat_set_show_contacts (priv->current_chat, show);
+}
+
+static void
+chat_window_close_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ g_return_if_fail (priv->current_chat != NULL);
+
+ gossip_chat_window_remove_chat (window, priv->current_chat);
+}
+
+static void
+chat_window_room_set_topic_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+/*FIXME
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ if (gossip_chat_is_group_chat (priv->current_chat)) {
+ GossipGroupChat *group_chat;
+
+ group_chat = GOSSIP_GROUP_CHAT (priv->current_chat);
+ gossip_group_chat_set_topic (group_chat);
+ }*/
+}
+
+static void
+chat_window_room_join_new_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ // FIXME: gossip_new_chatroom_dialog_show (GTK_WINDOW (priv->dialog));
+}
+
+static void
+chat_window_room_invite_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+/* FIXME:
+ GossipChatWindowPriv *priv;
+ GossipContact *own_contact;
+ GossipChatroomId id = 0;
+
+ priv = GET_PRIV (window);
+ own_contact = gossip_chat_get_own_contact (priv->current_chat);
+
+ if (gossip_chat_is_group_chat (priv->current_chat)) {
+ GossipGroupChat *group_chat;
+
+ group_chat = GOSSIP_GROUP_CHAT (priv->current_chat);
+ id = gossip_group_chat_get_chatroom_id (group_chat);
+ }
+
+ gossip_chat_invite_dialog_show (own_contact, id);
+*/
+}
+
+static void
+chat_window_room_add_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+/* FIXME:
+ GossipChatWindowPriv *priv;
+ GossipGroupChat *group_chat;
+ GossipChatroomManager *manager;
+ GossipChatroom *chatroom;
+
+ priv = GET_PRIV (window);
+
+ g_return_if_fail (priv->current_chat != NULL);
+
+ if (!gossip_chat_is_group_chat (priv->current_chat)) {
+ return;
+ }
+
+ group_chat = GOSSIP_GROUP_CHAT (priv->current_chat);
+ chatroom = gossip_group_chat_get_chatroom (group_chat);
+ gossip_chatroom_set_favourite (chatroom, TRUE);
+
+ manager = gossip_app_get_chatroom_manager ();
+ gossip_chatroom_manager_add (manager, chatroom);
+ gossip_chatroom_manager_store (manager);
+
+ chat_window_update_menu (window);
+*/
+}
+
+static void
+chat_window_edit_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GtkClipboard *clipboard;
+ GtkTextBuffer *buffer;
+ gboolean text_available;
+
+ priv = GET_PRIV (window);
+
+ g_return_if_fail (priv->current_chat != NULL);
+
+ if (!gossip_chat_is_connected (priv->current_chat)) {
+ gtk_widget_set_sensitive (priv->menu_edit_copy, FALSE);
+ gtk_widget_set_sensitive (priv->menu_edit_cut, FALSE);
+ gtk_widget_set_sensitive (priv->menu_edit_paste, FALSE);
+ return;
+ }
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
+ if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
+ gtk_widget_set_sensitive (priv->menu_edit_copy, TRUE);
+ gtk_widget_set_sensitive (priv->menu_edit_cut, TRUE);
+ } else {
+ gboolean selection;
+
+ selection = gossip_chat_view_get_selection_bounds (priv->current_chat->view,
+ NULL, NULL);
+
+ gtk_widget_set_sensitive (priv->menu_edit_cut, FALSE);
+ gtk_widget_set_sensitive (priv->menu_edit_copy, selection);
+ }
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ text_available = gtk_clipboard_wait_is_text_available (clipboard);
+ gtk_widget_set_sensitive (priv->menu_edit_paste, text_available);
+}
+
+static void
+chat_window_cut_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_WINDOW (window));
+
+ priv = GET_PRIV (window);
+
+ gossip_chat_cut (priv->current_chat);
+}
+
+static void
+chat_window_copy_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_WINDOW (window));
+
+ priv = GET_PRIV (window);
+
+ gossip_chat_copy (priv->current_chat);
+}
+
+static void
+chat_window_paste_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_WINDOW (window));
+
+ priv = GET_PRIV (window);
+
+ gossip_chat_paste (priv->current_chat);
+}
+
+static void
+chat_window_tabs_left_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChat *chat;
+ gint index;
+
+ priv = GET_PRIV (window);
+
+ chat = priv->current_chat;
+ index = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
+ if (index <= 0) {
+ return;
+ }
+
+ gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
+ gossip_chat_get_widget (chat),
+ index - 1);
+
+ chat_window_update_menu (window);
+ chat_window_update_status (window, chat);
+}
+
+static void
+chat_window_tabs_right_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChat *chat;
+ gint index;
+
+ priv = GET_PRIV (window);
+
+ chat = priv->current_chat;
+ index = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
+
+ gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
+ gossip_chat_get_widget (chat),
+ index + 1);
+
+ chat_window_update_menu (window);
+ chat_window_update_status (window, chat);
+}
+
+static void
+chat_window_detach_activate_cb (GtkWidget *menuitem,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChatWindow *new_window;
+ GossipChat *chat;
+
+ priv = GET_PRIV (window);
+
+ chat = priv->current_chat;
+ new_window = gossip_chat_window_new ();
+
+ gossip_chat_window_move_chat (window, new_window, chat);
+
+ priv = GET_PRIV (new_window);
+ gtk_widget_show (priv->dialog);
+}
+
+static gboolean
+chat_window_delete_event_cb (GtkWidget *dialog,
+ GdkEvent *event,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GList *list;
+ GList *l;
+
+ priv = GET_PRIV (window);
+
+ gossip_debug (DEBUG_DOMAIN, "Delete event received");
+
+ list = g_list_copy (priv->chats);
+
+ for (l = list; l; l = l->next) {
+ gossip_chat_window_remove_chat (window, l->data);
+ }
+
+ g_list_free (list);
+
+ return TRUE;
+}
+
+static void
+chat_window_status_changed_cb (GossipChat *chat,
+ GossipChatWindow *window)
+{
+ chat_window_update_menu (window);
+ chat_window_update_status (window, chat);
+}
+
+static void
+chat_window_update_tooltip (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ GtkWidget *widget;
+ gchar *current_tooltip;
+ gchar *str;
+
+ priv = GET_PRIV (window);
+
+ current_tooltip = gossip_chat_get_tooltip (chat);
+
+ if (g_list_find (priv->chats_composing, chat)) {
+ str = g_strconcat (current_tooltip, "\n", _("Typing a message."), NULL);
+ g_free (current_tooltip);
+ } else {
+ str = current_tooltip;
+ }
+
+ widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
+ gtk_tooltips_set_tip (priv->tooltips,
+ widget,
+ str,
+ NULL);
+
+ g_free (str);
+}
+
+static void
+chat_window_name_changed_cb (GossipChat *chat,
+ const gchar *name,
+ GossipChatWindow *window)
+{
+ GtkLabel *label;
+
+ label = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
+
+ gtk_label_set_text (label, name);
+}
+
+static void
+chat_window_composing_cb (GossipChat *chat,
+ gboolean is_composing,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ if (is_composing && !g_list_find (priv->chats_composing, chat)) {
+ priv->chats_composing = g_list_prepend (priv->chats_composing, chat);
+ } else {
+ priv->chats_composing = g_list_remove (priv->chats_composing, chat);
+ }
+
+ chat_window_update_status (window, chat);
+}
+
+static void
+chat_window_new_message_cb (GossipChat *chat,
+ GossipMessage *message,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gboolean has_focus;
+ gboolean needs_urgency;
+
+ priv = GET_PRIV (window);
+
+ has_focus = gossip_chat_window_has_focus (window);
+
+ if (has_focus && priv->current_chat == chat) {
+ gossip_debug (DEBUG_DOMAIN, "New message, we have focus");
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "New message, no focus");
+
+ needs_urgency = FALSE;
+ if (gossip_chat_is_group_chat (chat)) {
+ if (gossip_chat_should_highlight_nick (message)) {
+ gossip_debug (DEBUG_DOMAIN, "Highlight this nick");
+ needs_urgency = TRUE;
+ }
+ } else {
+ needs_urgency = TRUE;
+ }
+
+ if (needs_urgency && !has_focus) {
+ chat_window_set_urgency_hint (window, TRUE);
+ }
+
+ if (!g_list_find (priv->chats_new_msg, chat)) {
+ priv->chats_new_msg = g_list_prepend (priv->chats_new_msg, chat);
+ chat_window_update_status (window, chat);
+ }
+}
+
+static GtkNotebook *
+chat_window_detach_hook (GtkNotebook *source,
+ GtkWidget *page,
+ gint x,
+ gint y,
+ gpointer user_data)
+{
+ GossipChatWindowPriv *priv;
+ GossipChatWindow *window, *new_window;
+ GossipChat *chat;
+
+ chat = g_object_get_data (G_OBJECT (page), "chat");
+ window = gossip_chat_get_window (chat);
+
+ new_window = gossip_chat_window_new ();
+ priv = GET_PRIV (new_window);
+
+ gossip_debug (DEBUG_DOMAIN, "Detach hook called");
+
+ gossip_chat_window_move_chat (window, new_window, chat);
+
+ gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
+ gtk_widget_show (priv->dialog);
+
+ return NULL;
+}
+
+static void
+chat_window_page_switched_cb (GtkNotebook *notebook,
+ GtkNotebookPage *page,
+ gint page_num,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChat *chat;
+ GtkWidget *child;
+
+ gossip_debug (DEBUG_DOMAIN, "Page switched");
+
+ priv = GET_PRIV (window);
+
+ child = gtk_notebook_get_nth_page (notebook, page_num);
+ chat = g_object_get_data (G_OBJECT (child), "chat");
+
+ if (priv->page_added) {
+ priv->page_added = FALSE;
+ gossip_chat_scroll_down (chat);
+ }
+ else if (priv->current_chat == chat) {
+ return;
+ }
+
+ priv->current_chat = chat;
+ priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
+
+ chat_window_update_menu (window);
+ chat_window_update_status (window, chat);
+}
+
+static void
+chat_window_page_reordered_cb (GtkNotebook *notebook,
+ GtkWidget *widget,
+ guint page_num,
+ GossipChatWindow *window)
+{
+ gossip_debug (DEBUG_DOMAIN, "Page reordered");
+
+ chat_window_update_menu (window);
+}
+
+static void
+chat_window_page_added_cb (GtkNotebook *notebook,
+ GtkWidget *child,
+ guint page_num,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChat *chat;
+
+ priv = GET_PRIV (window);
+
+ /* If we just received DND to the same window, we don't want
+ * to do anything here like removing the tab and then readding
+ * it, so we return here and in "page-added".
+ */
+ if (priv->dnd_same_window) {
+ gossip_debug (DEBUG_DOMAIN, "Page added (back to the same window)");
+ priv->dnd_same_window = FALSE;
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Page added");
+
+ /* Get chat object */
+ chat = g_object_get_data (G_OBJECT (child), "chat");
+
+ /* Set the chat window */
+ gossip_chat_set_window (chat, window);
+
+ /* Connect chat signals for this window */
+ g_signal_connect (chat, "status_changed",
+ G_CALLBACK (chat_window_status_changed_cb),
+ window);
+ g_signal_connect (chat, "name_changed",
+ G_CALLBACK (chat_window_name_changed_cb),
+ window);
+ g_signal_connect (chat, "composing",
+ G_CALLBACK (chat_window_composing_cb),
+ window);
+ g_signal_connect (chat, "new_message",
+ G_CALLBACK (chat_window_new_message_cb),
+ window);
+
+ /* Set flag so we know to perform some special operations on
+ * switch page due to the new page being added.
+ */
+ priv->page_added = TRUE;
+
+ /* Get list of chats up to date */
+ priv->chats = g_list_append (priv->chats, chat);
+}
+
+static void
+chat_window_page_removed_cb (GtkNotebook *notebook,
+ GtkWidget *child,
+ guint page_num,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ GossipChat *chat;
+
+ priv = GET_PRIV (window);
+
+ /* If we just received DND to the same window, we don't want
+ * to do anything here like removing the tab and then readding
+ * it, so we return here and in "page-added".
+ */
+ if (priv->dnd_same_window) {
+ gossip_debug (DEBUG_DOMAIN, "Page removed (and will be readded to same window)");
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Page removed");
+
+ /* Get chat object */
+ chat = g_object_get_data (G_OBJECT (child), "chat");
+
+ /* Unset the window associated with a chat */
+ gossip_chat_set_window (chat, NULL);
+
+ /* Disconnect all signal handlers for this chat and this window */
+ g_signal_handlers_disconnect_by_func (chat,
+ G_CALLBACK (chat_window_status_changed_cb),
+ window);
+ g_signal_handlers_disconnect_by_func (chat,
+ G_CALLBACK (chat_window_name_changed_cb),
+ window);
+ g_signal_handlers_disconnect_by_func (chat,
+ G_CALLBACK (chat_window_composing_cb),
+ window);
+ g_signal_handlers_disconnect_by_func (chat,
+ G_CALLBACK (chat_window_new_message_cb),
+ window);
+
+ /* Keep list of chats up to date */
+ priv->chats = g_list_remove (priv->chats, chat);
+ priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
+ priv->chats_composing = g_list_remove (priv->chats_composing, chat);
+
+ if (priv->chats == NULL) {
+ g_object_unref (window);
+ } else {
+ chat_window_update_menu (window);
+ chat_window_update_title (window, NULL);
+ }
+}
+
+static gboolean
+chat_window_focus_in_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ gossip_debug (DEBUG_DOMAIN, "Focus in event, updating title");
+
+ priv = GET_PRIV (window);
+
+ priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat);
+
+ chat_window_set_urgency_hint (window, FALSE);
+
+ /* Update the title, since we now mark all unread messages as read. */
+ chat_window_update_status (window, priv->current_chat);
+
+ return FALSE;
+}
+
+static void
+chat_window_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ GossipChatWindow *window)
+{
+ if (info == DND_DRAG_TYPE_CONTACT_ID) {
+#if 0
+FIXME:
+ GossipChatManager *manager;
+ GossipContact *contact;
+ GossipChat *chat;
+ GossipChatWindow *old_window;
+ const gchar *id = NULL;
+
+ if (selection) {
+ id = (const gchar*) selection->data;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "DND contact from roster with id:'%s'", id);
+
+ contact = gossip_session_find_contact (gossip_app_get_session (), id);
+ if (!contact) {
+ gossip_debug (DEBUG_DOMAIN, "DND contact from roster not found");
+ return;
+ }
+
+ manager = gossip_app_get_chat_manager ();
+ chat = GOSSIP_CHAT (gossip_chat_manager_get_chat (manager, contact));
+ old_window = gossip_chat_get_window (chat);
+
+ if (old_window) {
+ if (old_window == window) {
+ gtk_drag_finish (context, TRUE, FALSE, time);
+ return;
+ }
+
+ gossip_chat_window_move_chat (old_window, window, chat);
+ } else {
+ gossip_chat_window_add_chat (window, chat);
+ }
+
+ /* Added to take care of any outstanding chat events */
+ gossip_chat_manager_show_chat (manager, contact);
+
+ /* We should return TRUE to remove the data when doing
+ * GDK_ACTION_MOVE, but we don't here otherwise it has
+ * weird consequences, and we handle that internally
+ * anyway with add_chat() and remove_chat().
+ */
+ gtk_drag_finish (context, TRUE, FALSE, time);
+#endif
+ }
+ else if (info == DND_DRAG_TYPE_TAB) {
+ GossipChat *chat = NULL;
+ GossipChatWindow *old_window;
+ GtkWidget **child = NULL;
+
+ gossip_debug (DEBUG_DOMAIN, "DND tab");
+
+ if (selection) {
+ child = (void*) selection->data;
+ }
+
+ if (child) {
+ chat = g_object_get_data (G_OBJECT (*child), "chat");
+ }
+
+ old_window = gossip_chat_get_window (chat);
+ if (old_window) {
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ if (old_window == window) {
+ gossip_debug (DEBUG_DOMAIN, "DND tab (within same window)");
+ priv->dnd_same_window = TRUE;
+ gtk_drag_finish (context, TRUE, FALSE, time);
+ return;
+ }
+
+ priv->dnd_same_window = FALSE;
+ }
+
+ /* We should return TRUE to remove the data when doing
+ * GDK_ACTION_MOVE, but we don't here otherwise it has
+ * weird consequences, and we handle that internally
+ * anyway with add_chat() and remove_chat().
+ */
+ gtk_drag_finish (context, TRUE, FALSE, time);
+ } else {
+ gossip_debug (DEBUG_DOMAIN, "DND from unknown source");
+ gtk_drag_finish (context, FALSE, FALSE, time);
+ }
+}
+
+static gboolean
+chat_window_urgency_timeout_func (GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ gossip_debug (DEBUG_DOMAIN, "Turning off urgency hint");
+ gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), FALSE);
+
+ priv->urgency_timeout_id = 0;
+
+ return FALSE;
+}
+
+static void
+chat_window_set_urgency_hint (GossipChatWindow *window,
+ gboolean urgent)
+{
+ GossipChatWindowPriv *priv;
+
+ priv = GET_PRIV (window);
+
+ if (!urgent) {
+ /* Remove any existing hint and timeout. */
+ if (priv->urgency_timeout_id) {
+ gossip_debug (DEBUG_DOMAIN, "Turning off urgency hint");
+ gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), FALSE);
+ g_source_remove (priv->urgency_timeout_id);
+ priv->urgency_timeout_id = 0;
+ }
+ return;
+ }
+
+ /* Add a new hint and renew any exising timeout or add a new one. */
+ if (priv->urgency_timeout_id) {
+ g_source_remove (priv->urgency_timeout_id);
+ } else {
+ gossip_debug (DEBUG_DOMAIN, "Turning on urgency hint");
+ gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), TRUE);
+ }
+
+ priv->urgency_timeout_id = g_timeout_add (
+ URGENCY_TIMEOUT,
+ (GSourceFunc) chat_window_urgency_timeout_func,
+ window);
+}
+
+GossipChatWindow *
+gossip_chat_window_new (void)
+{
+ return GOSSIP_CHAT_WINDOW (g_object_new (GOSSIP_TYPE_CHAT_WINDOW, NULL));
+}
+
+GtkWidget *
+gossip_chat_window_get_dialog (GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+
+ g_return_val_if_fail (window != NULL, NULL);
+
+ priv = GET_PRIV (window);
+
+ return priv->dialog;
+}
+
+void
+gossip_chat_window_add_chat (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ GtkWidget *label;
+ GtkWidget *child;
+
+ priv = GET_PRIV (window);
+
+ /* Reference the chat object */
+ g_object_ref (chat);
+
+ /* Set the chat window */
+ gossip_chat_set_window (chat, window);
+
+ if (g_list_length (priv->chats) == 0) {
+ gint x, y, w, h;
+
+ gossip_chat_load_geometry (chat, &x, &y, &w, &h);
+
+ if (x >= 0 && y >= 0) {
+ /* Let the window manager position it if we don't have
+ * good x, y coordinates.
+ */
+ gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
+ }
+
+ if (w > 0 && h > 0) {
+ /* Use the defaults from the glade file if we don't have
+ * good w, h geometry.
+ */
+ gtk_window_resize (GTK_WINDOW (priv->dialog), w, h);
+ }
+ }
+
+ child = gossip_chat_get_widget (chat);
+ label = chat_window_create_label (window, chat);
+
+ gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), child, label);
+ gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
+ gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
+ gtk_notebook_set_tab_label_packing (GTK_NOTEBOOK (priv->notebook), child,
+ TRUE, TRUE, GTK_PACK_START);
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Chat added (%d references)",
+ G_OBJECT (chat)->ref_count);
+}
+
+void
+gossip_chat_window_remove_chat (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ gint position;
+
+ priv = GET_PRIV (window);
+
+ position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
+ gossip_chat_get_widget (chat));
+ gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Chat removed (%d references)",
+ G_OBJECT (chat)->ref_count - 1);
+
+ g_object_unref (chat);
+}
+
+void
+gossip_chat_window_move_chat (GossipChatWindow *old_window,
+ GossipChatWindow *new_window,
+ GossipChat *chat)
+{
+ GtkWidget *widget;
+
+ g_return_if_fail (GOSSIP_IS_CHAT_WINDOW (old_window));
+ g_return_if_fail (GOSSIP_IS_CHAT_WINDOW (new_window));
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ widget = gossip_chat_get_widget (chat);
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Chat moving with widget:%p (%d references)",
+ widget,
+ G_OBJECT (widget)->ref_count);
+
+ /* We reference here to make sure we don't loose the widget
+ * and the GossipChat object during the move.
+ */
+ g_object_ref (chat);
+ g_object_ref (widget);
+
+ gossip_chat_window_remove_chat (old_window, chat);
+ gossip_chat_window_add_chat (new_window, chat);
+
+ g_object_unref (widget);
+ g_object_unref (chat);
+}
+
+void
+gossip_chat_window_switch_to_chat (GossipChatWindow *window,
+ GossipChat *chat)
+{
+ GossipChatWindowPriv *priv;
+ gint page_num;
+
+ priv = GET_PRIV (window);
+
+ page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
+ gossip_chat_get_widget (chat));
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
+ page_num);
+}
+
+gboolean
+gossip_chat_window_has_focus (GossipChatWindow *window)
+{
+ GossipChatWindowPriv *priv;
+ gboolean has_focus;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT_WINDOW (window), FALSE);
+
+ priv = GET_PRIV (window);
+
+ g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
+
+ return has_focus;
+}
diff --git a/libempathy-gtk/gossip-chat-window.h b/libempathy-gtk/gossip-chat-window.h
new file mode 100644
index 000000000..8cf582d8a
--- /dev/null
+++ b/libempathy-gtk/gossip-chat-window.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ */
+
+#ifndef __GOSSIP_CHAT_WINDOW_H__
+#define __GOSSIP_CHAT_WINDOW_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CHAT_WINDOW (gossip_chat_window_get_type ())
+#define GOSSIP_CHAT_WINDOW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CHAT_WINDOW, GossipChatWindow))
+#define GOSSIP_CHAT_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_CHAT_WINDOW, GossipChatWindowClass))
+#define GOSSIP_IS_CHAT_WINDOW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CHAT_WINDOW))
+#define GOSSIP_IS_CHAT_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CHAT_WINDOW))
+#define GOSSIP_CHAT_WINDOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CHAT_WINDOW, GossipChatWindowClass))
+
+typedef struct _GossipChatWindow GossipChatWindow;
+typedef struct _GossipChatWindowClass GossipChatWindowClass;
+typedef struct _GossipChatWindowPriv GossipChatWindowPriv;
+
+#include "gossip-chat.h"
+
+struct _GossipChatWindow {
+ GObject parent;
+};
+
+struct _GossipChatWindowClass {
+ GObjectClass parent_class;
+};
+
+GType gossip_chat_window_get_type (void);
+GossipChatWindow *gossip_chat_window_get_default (void);
+
+GossipChatWindow *gossip_chat_window_new (void);
+
+GtkWidget * gossip_chat_window_get_dialog (GossipChatWindow *window);
+
+void gossip_chat_window_add_chat (GossipChatWindow *window,
+ GossipChat *chat);
+void gossip_chat_window_remove_chat (GossipChatWindow *window,
+ GossipChat *chat);
+void gossip_chat_window_move_chat (GossipChatWindow *old_window,
+ GossipChatWindow *new_window,
+ GossipChat *chat);
+void gossip_chat_window_switch_to_chat (GossipChatWindow *window,
+ GossipChat *chat);
+gboolean gossip_chat_window_has_focus (GossipChatWindow *window);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CHAT_WINDOW_H__ */
diff --git a/libempathy-gtk/gossip-chat.c b/libempathy-gtk/gossip-chat.c
new file mode 100644
index 000000000..616b3abe3
--- /dev/null
+++ b/libempathy-gtk/gossip-chat.c
@@ -0,0 +1,1295 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <libempathy/empathy-session.h>
+#include <libempathy/empathy-contact-manager.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-utils.h>
+#include <libempathy/gossip-conf.h>
+
+#include "gossip-chat.h"
+#include "gossip-chat-window.h"
+//#include "gossip-geometry.h"
+#include "gossip-preferences.h"
+#include "gossip-spell.h"
+//#include "gossip-spell-dialog.h"
+#include "gossip-ui-utils.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CHAT, GossipChatPriv))
+
+#define DEBUG_DOMAIN "Chat"
+
+#define CHAT_DIR_CREATE_MODE (S_IRUSR | S_IWUSR | S_IXUSR)
+#define CHAT_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
+
+#define IS_ENTER(v) (v == GDK_Return || v == GDK_ISO_Enter || v == GDK_KP_Enter)
+
+#define MAX_INPUT_HEIGHT 150
+
+#define COMPOSING_STOP_TIMEOUT 5
+
+struct _GossipChatPriv {
+ EmpathyTpChat *tp_chat;
+ GossipChatWindow *window;
+
+ GtkTooltips *tooltips;
+ guint composing_stop_timeout_id;
+ gboolean sensitive;
+ /* Used to automatically shrink a window that has temporarily
+ * grown due to long input.
+ */
+ gint padding_height;
+ gint default_window_height;
+ gint last_input_height;
+ gboolean vscroll_visible;
+};
+
+typedef struct {
+ GossipChat *chat;
+ gchar *word;
+
+ GtkTextIter start;
+ GtkTextIter end;
+} GossipChatSpell;
+
+static void gossip_chat_class_init (GossipChatClass *klass);
+static void gossip_chat_init (GossipChat *chat);
+static void chat_finalize (GObject *object);
+static void chat_destroy_cb (EmpathyTpChat *tp_chat,
+ GossipChat *chat);
+static void chat_send (GossipChat *chat,
+ const gchar *msg);
+static void chat_input_text_view_send (GossipChat *chat);
+static void chat_message_received_cb (EmpathyTpChat *tp_chat,
+ GossipMessage *message,
+ GossipChat *chat);
+static gboolean chat_input_key_press_event_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ GossipChat *chat);
+static void chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
+ GossipChat *chat);
+static gboolean chat_text_view_focus_in_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ GossipChat *chat);
+static void chat_text_view_scroll_hide_cb (GtkWidget *widget,
+ GossipChat *chat);
+static void chat_text_view_size_allocate_cb (GtkWidget *widget,
+ GtkAllocation *allocation,
+ GossipChat *chat);
+static void chat_text_view_realize_cb (GtkWidget *widget,
+ GossipChat *chat);
+static void chat_text_populate_popup_cb (GtkTextView *view,
+ GtkMenu *menu,
+ GossipChat *chat);
+static void chat_text_check_word_spelling_cb (GtkMenuItem *menuitem,
+ GossipChatSpell *chat_spell);
+static GossipChatSpell *chat_spell_new (GossipChat *chat,
+ const gchar *word,
+ GtkTextIter start,
+ GtkTextIter end);
+static void chat_spell_free (GossipChatSpell *chat_spell);
+static void chat_composing_start (GossipChat *chat);
+static void chat_composing_stop (GossipChat *chat);
+static void chat_composing_remove_timeout (GossipChat *chat);
+static gboolean chat_composing_stop_timeout_cb (GossipChat *chat);
+
+enum {
+ COMPOSING,
+ NEW_MESSAGE,
+ NAME_CHANGED,
+ STATUS_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint chat_signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GossipChat, gossip_chat, G_TYPE_OBJECT);
+
+static void
+gossip_chat_class_init (GossipChatClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = chat_finalize;
+
+ chat_signals[COMPOSING] =
+ g_signal_new ("composing",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE,
+ 1, G_TYPE_BOOLEAN);
+
+ chat_signals[NEW_MESSAGE] =
+ g_signal_new ("new-message",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_MESSAGE);
+
+ chat_signals[NAME_CHANGED] =
+ g_signal_new ("name-changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1, G_TYPE_POINTER);
+
+ chat_signals[STATUS_CHANGED] =
+ g_signal_new ("status-changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_type_class_add_private (object_class, sizeof (GossipChatPriv));
+}
+
+static void
+gossip_chat_init (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkTextBuffer *buffer;
+
+ chat->view = gossip_chat_view_new ();
+ chat->input_text_view = gtk_text_view_new ();
+
+ chat->is_first_char = TRUE;
+
+ g_object_set (chat->input_text_view,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ "pixels-inside-wrap", 1,
+ "right-margin", 2,
+ "left-margin", 2,
+ "wrap-mode", GTK_WRAP_WORD_CHAR,
+ NULL);
+
+ priv = GET_PRIV (chat);
+
+ priv->tooltips = gtk_tooltips_new ();
+
+ priv->default_window_height = -1;
+ priv->vscroll_visible = FALSE;
+ priv->sensitive = TRUE;
+
+ g_signal_connect (chat->input_text_view,
+ "key_press_event",
+ G_CALLBACK (chat_input_key_press_event_cb),
+ chat);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+ g_signal_connect (buffer,
+ "changed",
+ G_CALLBACK (chat_input_text_buffer_changed_cb),
+ chat);
+ g_signal_connect (GOSSIP_CHAT (chat)->view,
+ "focus_in_event",
+ G_CALLBACK (chat_text_view_focus_in_event_cb),
+ chat);
+
+ g_signal_connect (chat->input_text_view,
+ "size_allocate",
+ G_CALLBACK (chat_text_view_size_allocate_cb),
+ chat);
+
+ g_signal_connect (chat->input_text_view,
+ "realize",
+ G_CALLBACK (chat_text_view_realize_cb),
+ chat);
+
+ g_signal_connect (GTK_TEXT_VIEW (chat->input_text_view),
+ "populate_popup",
+ G_CALLBACK (chat_text_populate_popup_cb),
+ chat);
+
+ /* create misspelt words identification tag */
+ gtk_text_buffer_create_tag (buffer,
+ "misspelled",
+ "underline", PANGO_UNDERLINE_ERROR,
+ NULL);
+}
+
+static void
+chat_finalize (GObject *object)
+{
+ GossipChat *chat;
+ GossipChatPriv *priv;
+
+ chat = GOSSIP_CHAT (object);
+ priv = GET_PRIV (chat);
+
+ gossip_debug (DEBUG_DOMAIN, "Finalized: %p", object);
+
+ chat_composing_remove_timeout (chat);
+ g_object_unref (GOSSIP_CHAT (object)->account);
+
+ if (priv->tp_chat) {
+ g_object_unref (priv->tp_chat);
+ }
+
+ G_OBJECT_CLASS (gossip_chat_parent_class)->finalize (object);
+}
+
+static void
+chat_destroy_cb (EmpathyTpChat *tp_chat,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkWidget *widget;
+
+ priv = GET_PRIV (chat);
+
+ if (priv->tp_chat) {
+ g_object_unref (priv->tp_chat);
+ priv->tp_chat = NULL;
+ }
+
+ gossip_chat_view_append_event (chat->view, _("Disconnected"));
+
+ widget = gossip_chat_get_widget (chat);
+ gtk_widget_set_sensitive (widget, FALSE);
+ priv->sensitive = FALSE;
+}
+
+static void
+chat_send (GossipChat *chat,
+ const gchar *msg)
+{
+ GossipChatPriv *priv;
+ //GossipLogManager *log_manager;
+ GossipMessage *message;
+ GossipContact *own_contact;
+
+ priv = GET_PRIV (chat);
+
+ if (msg == NULL || msg[0] == '\0') {
+ return;
+ }
+
+ if (g_str_has_prefix (msg, "/clear")) {
+ gossip_chat_view_clear (chat->view);
+ return;
+ }
+
+ /* FIXME: gossip_app_set_not_away ();*/
+
+ own_contact = gossip_chat_get_own_contact (chat);
+ message = gossip_message_new (msg);
+ gossip_message_set_sender (message, own_contact);
+
+ //FIXME: log_manager = gossip_session_get_log_manager (gossip_app_get_session ());
+ //gossip_log_message_for_contact (log_manager, message, FALSE);
+
+ empathy_tp_chat_send (priv->tp_chat, message);
+
+ g_object_unref (message);
+}
+
+static void
+chat_input_text_view_send (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextIter start, end;
+ gchar *msg;
+
+ priv = GET_PRIV (chat);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+ msg = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+ /* clear the input field */
+ gtk_text_buffer_set_text (buffer, "", -1);
+
+ chat_send (chat, msg);
+
+ g_free (msg);
+
+ chat->is_first_char = TRUE;
+}
+
+static void
+chat_message_received_cb (EmpathyTpChat *tp_chat,
+ GossipMessage *message,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ //GossipLogManager *log_manager;
+ GossipContact *sender;
+
+ priv = GET_PRIV (chat);
+
+ sender = gossip_message_get_sender (message);
+ gossip_debug (DEBUG_DOMAIN, "Appending message ('%s')",
+ gossip_contact_get_name (sender));
+
+/*FIXME:
+ log_manager = gossip_session_get_log_manager (gossip_app_get_session ());
+ gossip_log_message_for_contact (log_manager, message, TRUE);
+*/
+ gossip_chat_view_append_message (chat->view, message);
+
+ if (gossip_chat_should_play_sound (chat)) {
+ // FIXME: gossip_sound_play (GOSSIP_SOUND_CHAT);
+ }
+
+ g_signal_emit_by_name (chat, "new-message", message);
+}
+
+static gboolean
+chat_input_key_press_event_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkAdjustment *adj;
+ gdouble val;
+ GtkWidget *text_view_sw;
+
+ priv = GET_PRIV (chat);
+
+ if (event->keyval == GDK_Tab && !(event->state & GDK_CONTROL_MASK)) {
+ return TRUE;
+ }
+
+ /* Catch enter but not ctrl/shift-enter */
+ if (IS_ENTER (event->keyval) && !(event->state & GDK_SHIFT_MASK)) {
+ GtkTextView *view;
+
+ /* This is to make sure that kinput2 gets the enter. And if
+ * it's handled there we shouldn't send on it. This is because
+ * kinput2 uses Enter to commit letters. See:
+ * http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=104299
+ */
+
+ view = GTK_TEXT_VIEW (chat->input_text_view);
+ if (gtk_im_context_filter_keypress (view->im_context, event)) {
+ GTK_TEXT_VIEW (chat->input_text_view)->need_im_reset = TRUE;
+ return TRUE;
+ }
+
+ chat_input_text_view_send (chat);
+ return TRUE;
+ }
+
+ text_view_sw = gtk_widget_get_parent (GTK_WIDGET (chat->view));
+ if (IS_ENTER (event->keyval) && (event->state & GDK_SHIFT_MASK)) {
+ /* Newline for shift-enter. */
+ return FALSE;
+ }
+ else if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
+ event->keyval == GDK_Page_Up) {
+ adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
+ gtk_adjustment_set_value (adj, adj->value - adj->page_size);
+
+ return TRUE;
+ }
+ else if ((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK &&
+ event->keyval == GDK_Page_Down) {
+ adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (text_view_sw));
+ val = MIN (adj->value + adj->page_size, adj->upper - adj->page_size);
+ gtk_adjustment_set_value (adj, val);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+chat_text_view_focus_in_event_cb (GtkWidget *widget,
+ GdkEvent *event,
+ GossipChat *chat)
+{
+ gtk_widget_grab_focus (chat->input_text_view);
+
+ return TRUE;
+}
+
+static void
+chat_input_text_buffer_changed_cb (GtkTextBuffer *buffer,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkTextIter start, end;
+ gchar *str;
+ gboolean spell_checker = FALSE;
+
+ priv = GET_PRIV (chat);
+
+ if (gtk_text_buffer_get_char_count (buffer) == 0) {
+ chat_composing_stop (chat);
+ } else {
+ chat_composing_start (chat);
+ }
+
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_ENABLED,
+ &spell_checker);
+
+ if (chat->is_first_char) {
+ GtkRequisition req;
+ gint window_height;
+ GtkWidget *dialog;
+ GtkAllocation *allocation;
+
+ /* Save the window's size */
+ dialog = gossip_chat_window_get_dialog (priv->window);
+ gtk_window_get_size (GTK_WINDOW (dialog),
+ NULL, &window_height);
+
+ gtk_widget_size_request (chat->input_text_view, &req);
+
+ allocation = &GTK_WIDGET (chat->view)->allocation;
+
+ priv->default_window_height = window_height;
+ priv->last_input_height = req.height;
+ priv->padding_height = window_height - req.height - allocation->height;
+
+ chat->is_first_char = FALSE;
+ }
+
+ gtk_text_buffer_get_start_iter (buffer, &start);
+
+ if (!spell_checker) {
+ gtk_text_buffer_get_end_iter (buffer, &end);
+ gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
+ return;
+ }
+
+ if (!gossip_spell_supported ()) {
+ return;
+ }
+
+ /* NOTE: this is really inefficient, we shouldn't have to
+ reiterate the whole buffer each time and check each work
+ every time. */
+ while (TRUE) {
+ gboolean correct = FALSE;
+
+ /* if at start */
+ if (gtk_text_iter_is_start (&start)) {
+ end = start;
+
+ if (!gtk_text_iter_forward_word_end (&end)) {
+ /* no whole word yet */
+ break;
+ }
+ } else {
+ if (!gtk_text_iter_forward_word_end (&end)) {
+ /* must be the end of the buffer */
+ break;
+ }
+
+ start = end;
+ gtk_text_iter_backward_word_start (&start);
+ }
+
+ str = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+ /* spell check string */
+ if (!gossip_chat_get_is_command (str)) {
+ correct = gossip_spell_check (str);
+ } else {
+ correct = TRUE;
+ }
+
+ if (!correct) {
+ gtk_text_buffer_apply_tag_by_name (buffer, "misspelled", &start, &end);
+ } else {
+ gtk_text_buffer_remove_tag_by_name (buffer, "misspelled", &start, &end);
+ }
+
+ g_free (str);
+
+ /* set start iter to the end iters position */
+ start = end;
+ }
+}
+
+typedef struct {
+ GtkWidget *window;
+ gint width;
+ gint height;
+} ChangeSizeData;
+
+static gboolean
+chat_change_size_in_idle_cb (ChangeSizeData *data)
+{
+ gtk_window_resize (GTK_WINDOW (data->window),
+ data->width, data->height);
+
+ return FALSE;
+}
+
+static void
+chat_text_view_scroll_hide_cb (GtkWidget *widget,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkWidget *sw;
+
+ priv = GET_PRIV (chat);
+
+ priv->vscroll_visible = FALSE;
+ g_signal_handlers_disconnect_by_func (widget, chat_text_view_scroll_hide_cb, chat);
+
+ sw = gtk_widget_get_parent (chat->input_text_view);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_NEVER);
+ g_object_set (sw, "height-request", -1, NULL);
+}
+
+static void
+chat_text_view_size_allocate_cb (GtkWidget *widget,
+ GtkAllocation *allocation,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ gint width;
+ GtkWidget *dialog;
+ ChangeSizeData *data;
+ gint window_height;
+ gint new_height;
+ GtkAllocation *view_allocation;
+ gint current_height;
+ gint diff;
+ GtkWidget *sw;
+
+ priv = GET_PRIV (chat);
+
+ if (priv->default_window_height <= 0) {
+ return;
+ }
+
+ sw = gtk_widget_get_parent (widget);
+ if (sw->allocation.height >= MAX_INPUT_HEIGHT && !priv->vscroll_visible) {
+ GtkWidget *vscroll;
+
+ priv->vscroll_visible = TRUE;
+ gtk_widget_set_size_request (sw, sw->allocation.width, MAX_INPUT_HEIGHT);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ vscroll = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (sw));
+ g_signal_connect (vscroll, "hide",
+ G_CALLBACK (chat_text_view_scroll_hide_cb),
+ chat);
+ }
+
+ if (priv->last_input_height <= allocation->height) {
+ priv->last_input_height = allocation->height;
+ return;
+ }
+
+ diff = priv->last_input_height - allocation->height;
+ priv->last_input_height = allocation->height;
+
+ view_allocation = &GTK_WIDGET (chat->view)->allocation;
+
+ dialog = gossip_chat_window_get_dialog (priv->window);
+ gtk_window_get_size (GTK_WINDOW (dialog), NULL, &current_height);
+
+ new_height = view_allocation->height + priv->padding_height + allocation->height - diff;
+
+ if (new_height <= priv->default_window_height) {
+ window_height = priv->default_window_height;
+ } else {
+ window_height = new_height;
+ }
+
+ if (current_height <= window_height) {
+ return;
+ }
+
+ /* Restore the window's size */
+ gtk_window_get_size (GTK_WINDOW (dialog), &width, NULL);
+
+ data = g_new0 (ChangeSizeData, 1);
+ data->window = dialog;
+ data->width = width;
+ data->height = window_height;
+
+ g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+ (GSourceFunc) chat_change_size_in_idle_cb,
+ data, g_free);
+}
+
+static void
+chat_text_view_realize_cb (GtkWidget *widget,
+ GossipChat *chat)
+{
+ gossip_debug (DEBUG_DOMAIN, "Setting focus to the input text view");
+ gtk_widget_grab_focus (widget);
+}
+
+static void
+chat_insert_smiley_activate_cb (GtkWidget *menuitem,
+ GossipChat *chat)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ const gchar *smiley;
+
+ smiley = g_object_get_data (G_OBJECT (menuitem), "smiley_text");
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+ gtk_text_buffer_insert (buffer, &iter, smiley, -1);
+
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+ gtk_text_buffer_insert (buffer, &iter, " ", -1);
+}
+
+static void
+chat_text_populate_popup_cb (GtkTextView *view,
+ GtkMenu *menu,
+ GossipChat *chat)
+{
+ GossipChatPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+ gint x, y;
+ GtkTextIter iter, start, end;
+ GtkWidget *item;
+ gchar *str = NULL;
+ GossipChatSpell *chat_spell;
+ GtkWidget *smiley_menu;
+
+ priv = GET_PRIV (chat);
+
+ /* Add the emoticon menu. */
+ item = gtk_separator_menu_item_new ();
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("Insert Smiley"));
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ smiley_menu = gossip_chat_view_get_smiley_menu (
+ G_CALLBACK (chat_insert_smiley_activate_cb),
+ chat,
+ priv->tooltips);
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), smiley_menu);
+
+ /* Add the spell check menu item. */
+ buffer = gtk_text_view_get_buffer (view);
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ tag = gtk_text_tag_table_lookup (table, "misspelled");
+
+ gtk_widget_get_pointer (GTK_WIDGET (view), &x, &y);
+
+ gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (view),
+ GTK_TEXT_WINDOW_WIDGET,
+ x, y,
+ &x, &y);
+
+ gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (view), &iter, x, y);
+
+ start = end = iter;
+
+ if (gtk_text_iter_backward_to_tag_toggle (&start, tag) &&
+ gtk_text_iter_forward_to_tag_toggle (&end, tag)) {
+
+ str = gtk_text_buffer_get_text (buffer,
+ &start, &end, FALSE);
+ }
+
+ if (G_STR_EMPTY (str)) {
+ return;
+ }
+
+ chat_spell = chat_spell_new (chat, str, start, end);
+
+ g_object_set_data_full (G_OBJECT (menu),
+ "chat_spell", chat_spell,
+ (GDestroyNotify) chat_spell_free);
+
+ item = gtk_separator_menu_item_new ();
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+
+ item = gtk_menu_item_new_with_mnemonic (_("_Check Word Spelling..."));
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (chat_text_check_word_spelling_cb),
+ chat_spell);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
+ gtk_widget_show (item);
+}
+
+static void
+chat_text_check_word_spelling_cb (GtkMenuItem *menuitem,
+ GossipChatSpell *chat_spell)
+{
+/*FIXME: gossip_spell_dialog_show (chat_spell->chat,
+ chat_spell->start,
+ chat_spell->end,
+ chat_spell->word);*/
+}
+
+static GossipChatSpell *
+chat_spell_new (GossipChat *chat,
+ const gchar *word,
+ GtkTextIter start,
+ GtkTextIter end)
+{
+ GossipChatSpell *chat_spell;
+
+ chat_spell = g_new0 (GossipChatSpell, 1);
+
+ chat_spell->chat = g_object_ref (chat);
+ chat_spell->word = g_strdup (word);
+ chat_spell->start = start;
+ chat_spell->end = end;
+
+ return chat_spell;
+}
+
+static void
+chat_spell_free (GossipChatSpell *chat_spell)
+{
+ g_object_unref (chat_spell->chat);
+ g_free (chat_spell->word);
+ g_free (chat_spell);
+}
+
+static void
+chat_composing_start (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ if (priv->composing_stop_timeout_id) {
+ /* Just restart the timeout */
+ chat_composing_remove_timeout (chat);
+ } else {
+ /* FIXME:
+ gossip_session_send_composing (gossip_app_get_session (),
+ priv->contact, TRUE);
+ */
+ }
+
+ priv->composing_stop_timeout_id = g_timeout_add (
+ 1000 * COMPOSING_STOP_TIMEOUT,
+ (GSourceFunc) chat_composing_stop_timeout_cb,
+ chat);
+}
+
+static void
+chat_composing_stop (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ chat_composing_remove_timeout (chat);
+ /* FIXME:
+ gossip_session_send_composing (gossip_app_get_session (),
+ priv->contact, FALSE);*/
+}
+
+static void
+chat_composing_remove_timeout (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ if (priv->composing_stop_timeout_id) {
+ g_source_remove (priv->composing_stop_timeout_id);
+ priv->composing_stop_timeout_id = 0;
+ }
+}
+
+static gboolean
+chat_composing_stop_timeout_cb (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ priv->composing_stop_timeout_id = 0;
+ /* FIXME:
+ gossip_session_send_composing (gossip_app_get_session (),
+ priv->contact, FALSE);*/
+
+ return FALSE;
+}
+
+gboolean
+gossip_chat_get_is_command (const gchar *str)
+{
+ g_return_val_if_fail (str != NULL, FALSE);
+
+ if (str[0] != '/') {
+ return FALSE;
+ }
+
+ if (g_str_has_prefix (str, "/me")) {
+ return TRUE;
+ }
+ else if (g_str_has_prefix (str, "/nick")) {
+ return TRUE;
+ }
+ else if (g_str_has_prefix (str, "/topic")) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gossip_chat_correct_word (GossipChat *chat,
+ GtkTextIter start,
+ GtkTextIter end,
+ const gchar *new_word)
+{
+ GtkTextBuffer *buffer;
+
+ g_return_if_fail (chat != NULL);
+ g_return_if_fail (new_word != NULL);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+
+ gtk_text_buffer_delete (buffer, &start, &end);
+ gtk_text_buffer_insert (buffer, &start,
+ new_word,
+ -1);
+}
+
+const gchar *
+gossip_chat_get_name (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_name) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_name (chat);
+ }
+
+ return NULL;
+}
+
+gchar *
+gossip_chat_get_tooltip (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_tooltip) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_tooltip (chat);
+ }
+
+ return NULL;
+}
+
+GdkPixbuf *
+gossip_chat_get_status_pixbuf (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_status_pixbuf) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_status_pixbuf (chat);
+ }
+
+ return NULL;
+}
+
+GossipContact *
+gossip_chat_get_contact (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_contact) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_contact (chat);
+ }
+
+ return NULL;
+}
+GossipContact *
+gossip_chat_get_own_contact (GossipChat *chat)
+{
+ EmpathyContactManager *manager;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ manager = empathy_session_get_contact_manager ();
+
+ return empathy_contact_manager_get_own (manager, chat->account);
+}
+
+GtkWidget *
+gossip_chat_get_widget (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), NULL);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_widget) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_widget (chat);
+ }
+
+ return NULL;
+}
+
+gboolean
+gossip_chat_is_group_chat (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->is_group_chat) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->is_group_chat (chat);
+ }
+
+ return FALSE;
+}
+
+gboolean
+gossip_chat_is_connected (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
+
+ priv = GET_PRIV (chat);
+
+ return (priv->tp_chat != NULL);
+}
+
+gboolean
+gossip_chat_get_show_contacts (GossipChat *chat)
+{
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->get_show_contacts) {
+ return GOSSIP_CHAT_GET_CLASS (chat)->get_show_contacts (chat);
+ }
+
+ return FALSE;
+}
+
+void
+gossip_chat_set_show_contacts (GossipChat *chat,
+ gboolean show)
+{
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ if (GOSSIP_CHAT_GET_CLASS (chat)->set_show_contacts) {
+ GOSSIP_CHAT_GET_CLASS (chat)->set_show_contacts (chat, show);
+ }
+}
+
+void
+gossip_chat_save_geometry (GossipChat *chat,
+ gint x,
+ gint y,
+ gint w,
+ gint h)
+{
+ //FIXME: gossip_geometry_save_for_chat (chat, x, y, w, h);
+}
+
+void
+gossip_chat_load_geometry (GossipChat *chat,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h)
+{
+ //FIXME: gossip_geometry_load_for_chat (chat, x, y, w, h);
+}
+
+void
+gossip_chat_set_tp_chat (GossipChat *chat,
+ EmpathyTpChat *tp_chat)
+{
+ GossipChatPriv *priv;
+ GtkWidget *widget;
+
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+ g_return_if_fail (EMPATHY_IS_TP_CHAT (tp_chat));
+
+ priv = GET_PRIV (chat);
+
+ if (tp_chat == priv->tp_chat) {
+ return;
+ }
+
+ if (priv->tp_chat) {
+ g_signal_handlers_disconnect_by_func (priv->tp_chat,
+ chat_message_received_cb,
+ chat);
+ g_signal_handlers_disconnect_by_func (priv->tp_chat,
+ chat_destroy_cb,
+ chat);
+ g_object_unref (priv->tp_chat);
+ }
+
+ priv->tp_chat = g_object_ref (tp_chat);
+
+ g_signal_connect (tp_chat, "message-received",
+ G_CALLBACK (chat_message_received_cb),
+ chat);
+ g_signal_connect (tp_chat, "destroy",
+ G_CALLBACK (chat_destroy_cb),
+ chat);
+
+ empathy_tp_chat_request_pending (tp_chat);
+
+ if (!priv->sensitive) {
+ widget = gossip_chat_get_widget (chat);
+ gtk_widget_set_sensitive (widget, TRUE);
+ gossip_chat_view_append_event (chat->view, _("Connected"));
+ priv->sensitive = TRUE;
+ }
+}
+
+void
+gossip_chat_clear (GossipChat *chat)
+{
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ gossip_chat_view_clear (chat->view);
+}
+
+void
+gossip_chat_set_window (GossipChat *chat,
+ GossipChatWindow *window)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+ priv->window = window;
+}
+
+GossipChatWindow *
+gossip_chat_get_window (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ return priv->window;
+}
+
+void
+gossip_chat_scroll_down (GossipChat *chat)
+{
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ gossip_chat_view_scroll_down (chat->view);
+}
+
+void
+gossip_chat_cut (GossipChat *chat)
+{
+ GtkTextBuffer *buffer;
+
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+ if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+
+ gtk_text_buffer_cut_clipboard (buffer, clipboard, TRUE);
+ }
+}
+
+void
+gossip_chat_copy (GossipChat *chat)
+{
+ GtkTextBuffer *buffer;
+
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ if (gossip_chat_view_get_selection_bounds (chat->view, NULL, NULL)) {
+ gossip_chat_view_copy_clipboard (chat->view);
+ return;
+ }
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+ if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) {
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+
+ gtk_text_buffer_copy_clipboard (buffer, clipboard);
+ }
+}
+
+void
+gossip_chat_paste (GossipChat *chat)
+{
+ GtkTextBuffer *buffer;
+ GtkClipboard *clipboard;
+
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+
+ gtk_text_buffer_paste_clipboard (buffer, clipboard, NULL, TRUE);
+}
+
+void
+gossip_chat_present (GossipChat *chat)
+{
+ GossipChatPriv *priv;
+
+ g_return_if_fail (GOSSIP_IS_CHAT (chat));
+
+ priv = GET_PRIV (chat);
+
+ if (priv->window == NULL) {
+ GossipChatWindow *window;
+
+ window = gossip_chat_window_get_default ();
+ if (!window) {
+ window = gossip_chat_window_new ();
+ }
+
+ gossip_chat_window_add_chat (window, chat);
+ }
+
+ gossip_chat_window_switch_to_chat (priv->window, chat);
+ gossip_window_present (
+ GTK_WINDOW (gossip_chat_window_get_dialog (priv->window)),
+ TRUE);
+
+ gtk_widget_grab_focus (chat->input_text_view);
+}
+
+gboolean
+gossip_chat_should_play_sound (GossipChat *chat)
+{
+ GossipChatWindow *window;
+ gboolean play = TRUE;
+
+ g_return_val_if_fail (GOSSIP_IS_CHAT (chat), FALSE);
+
+ window = gossip_chat_get_window (GOSSIP_CHAT (chat));
+ if (!window) {
+ return TRUE;
+ }
+
+ play = !gossip_chat_window_has_focus (window);
+
+ return play;
+}
+
+gboolean
+gossip_chat_should_highlight_nick (GossipMessage *message)
+{
+ GossipContact *my_contact;
+ const gchar *msg, *to;
+ gchar *cf_msg, *cf_to;
+ gchar *ch;
+ gboolean ret_val;
+
+ g_return_val_if_fail (GOSSIP_IS_MESSAGE (message), FALSE);
+
+ gossip_debug (DEBUG_DOMAIN, "Highlighting nickname");
+
+ ret_val = FALSE;
+
+ msg = gossip_message_get_body (message);
+ if (!msg) {
+ return FALSE;
+ }
+
+ my_contact = gossip_get_own_contact_from_contact (gossip_message_get_sender (message));
+ to = gossip_contact_get_name (my_contact);
+ if (!to) {
+ return FALSE;
+ }
+
+ cf_msg = g_utf8_casefold (msg, -1);
+ cf_to = g_utf8_casefold (to, -1);
+
+ ch = strstr (cf_msg, cf_to);
+ if (ch == NULL) {
+ goto finished;
+ }
+
+ if (ch != cf_msg) {
+ /* Not first in the message */
+ if ((*(ch - 1) != ' ') &&
+ (*(ch - 1) != ',') &&
+ (*(ch - 1) != '.')) {
+ goto finished;
+ }
+ }
+
+ ch = ch + strlen (cf_to);
+ if (ch >= cf_msg + strlen (cf_msg)) {
+ ret_val = TRUE;
+ goto finished;
+ }
+
+ if ((*ch == ' ') ||
+ (*ch == ',') ||
+ (*ch == '.') ||
+ (*ch == ':')) {
+ ret_val = TRUE;
+ goto finished;
+ }
+
+finished:
+ g_free (cf_msg);
+ g_free (cf_to);
+
+ return ret_val;
+}
+
diff --git a/libempathy-gtk/gossip-chat.h b/libempathy-gtk/gossip-chat.h
new file mode 100644
index 000000000..9a0a0c317
--- /dev/null
+++ b/libempathy-gtk/gossip-chat.h
@@ -0,0 +1,139 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ */
+
+#ifndef __GOSSIP_CHAT_H__
+#define __GOSSIP_CHAT_H__
+
+#include <glib-object.h>
+
+#include <libempathy/gossip-contact.h>
+#include <libempathy/gossip-message.h>
+#include <libempathy/empathy-tp-chat.h>
+
+#include "gossip-chat-view.h"
+#include "gossip-spell.h"
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CHAT (gossip_chat_get_type ())
+#define GOSSIP_CHAT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CHAT, GossipChat))
+#define GOSSIP_CHAT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_CHAT, GossipChatClass))
+#define GOSSIP_IS_CHAT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CHAT))
+#define GOSSIP_IS_CHAT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CHAT))
+#define GOSSIP_CHAT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CHAT, GossipChatClass))
+
+typedef struct _GossipChat GossipChat;
+typedef struct _GossipChatClass GossipChatClass;
+typedef struct _GossipChatPriv GossipChatPriv;
+
+#include "gossip-chat-window.h"
+
+struct _GossipChat {
+ GObject parent;
+
+ /* Protected */
+ GossipChatView *view;
+ GtkWidget *input_text_view;
+ gboolean is_first_char;
+ McAccount *account;
+};
+
+struct _GossipChatClass {
+ GObjectClass parent;
+
+ /* VTable */
+ const gchar * (*get_name) (GossipChat *chat);
+ gchar * (*get_tooltip) (GossipChat *chat);
+ GdkPixbuf * (*get_status_pixbuf)(GossipChat *chat);
+ GossipContact * (*get_contact) (GossipChat *chat);
+ GtkWidget * (*get_widget) (GossipChat *chat);
+
+ gboolean (*get_show_contacts)(GossipChat *chat);
+ void (*set_show_contacts)(GossipChat *chat,
+ gboolean show);
+
+ gboolean (*is_group_chat) (GossipChat *chat);
+ void (*save_geometry) (GossipChat *chat,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+ void (*load_geometry) (GossipChat *chat,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h);
+};
+
+GType gossip_chat_get_type (void);
+
+GossipChatView * gossip_chat_get_view (GossipChat *chat);
+GossipChatWindow *gossip_chat_get_window (GossipChat *chat);
+void gossip_chat_set_window (GossipChat *chat,
+ GossipChatWindow *window);
+void gossip_chat_present (GossipChat *chat);
+void gossip_chat_clear (GossipChat *chat);
+void gossip_chat_scroll_down (GossipChat *chat);
+void gossip_chat_cut (GossipChat *chat);
+void gossip_chat_copy (GossipChat *chat);
+void gossip_chat_paste (GossipChat *chat);
+const gchar * gossip_chat_get_name (GossipChat *chat);
+gchar * gossip_chat_get_tooltip (GossipChat *chat);
+GdkPixbuf * gossip_chat_get_status_pixbuf (GossipChat *chat);
+GossipContact * gossip_chat_get_contact (GossipChat *chat);
+GossipContact * gossip_chat_get_own_contact (GossipChat *chat);
+GtkWidget * gossip_chat_get_widget (GossipChat *chat);
+gboolean gossip_chat_get_show_contacts (GossipChat *chat);
+void gossip_chat_set_show_contacts (GossipChat *chat,
+ gboolean show);
+
+gboolean gossip_chat_is_group_chat (GossipChat *chat);
+gboolean gossip_chat_is_connected (GossipChat *chat);
+
+void gossip_chat_save_geometry (GossipChat *chat,
+ gint x,
+ gint y,
+ gint w,
+ gint h);
+void gossip_chat_load_geometry (GossipChat *chat,
+ gint *x,
+ gint *y,
+ gint *w,
+ gint *h);
+void gossip_chat_set_tp_chat (GossipChat *chat,
+ EmpathyTpChat *tp_chat);
+
+/* For spell checker dialog to correct the misspelled word. */
+gboolean gossip_chat_get_is_command (const gchar *str);
+void gossip_chat_correct_word (GossipChat *chat,
+ GtkTextIter start,
+ GtkTextIter end,
+ const gchar *new_word);
+gboolean gossip_chat_should_play_sound (GossipChat *chat);
+gboolean gossip_chat_should_highlight_nick (GossipMessage *message);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CHAT_H__ */
diff --git a/libempathy-gtk/gossip-contact-groups.c b/libempathy-gtk/gossip-contact-groups.c
new file mode 100644
index 000000000..8a6afda11
--- /dev/null
+++ b/libempathy-gtk/gossip-contact-groups.c
@@ -0,0 +1,286 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-utils.h>
+
+#include "gossip-contact-groups.h"
+
+#define DEBUG_DOMAIN "ContactGroups"
+
+#define CONTACT_GROUPS_XML_FILENAME "contact-groups.xml"
+#define CONTACT_GROUPS_DTD_FILENAME "gossip-contact-groups.dtd"
+
+typedef struct {
+ gchar *name;
+ gboolean expanded;
+} ContactGroup;
+
+static void contact_groups_file_parse (const gchar *filename);
+static gboolean contact_groups_file_save (void);
+static ContactGroup *contact_group_new (const gchar *name,
+ gboolean expanded);
+static void contact_group_free (ContactGroup *group);
+
+static GList *groups = NULL;
+
+void
+gossip_contact_groups_get_all (void)
+{
+ gchar *dir;
+ gchar *file_with_path;
+
+ /* If already set up clean up first */
+ if (groups) {
+ g_list_foreach (groups, (GFunc)contact_group_free, NULL);
+ g_list_free (groups);
+ groups = NULL;
+ }
+
+ dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
+ file_with_path = g_build_filename (dir, CONTACT_GROUPS_XML_FILENAME, NULL);
+ g_free (dir);
+
+ if (g_file_test (file_with_path, G_FILE_TEST_EXISTS)) {
+ contact_groups_file_parse (file_with_path);
+ }
+
+ g_free (file_with_path);
+}
+
+static void
+contact_groups_file_parse (const gchar *filename)
+{
+ xmlParserCtxtPtr ctxt;
+ xmlDocPtr doc;
+ xmlNodePtr contacts;
+ xmlNodePtr account;
+ xmlNodePtr node;
+
+ gossip_debug (DEBUG_DOMAIN, "Attempting to parse file:'%s'...", filename);
+
+ ctxt = xmlNewParserCtxt ();
+
+ /* Parse and validate the file. */
+ doc = xmlCtxtReadFile (ctxt, filename, NULL, 0);
+ if (!doc) {
+ g_warning ("Failed to parse file:'%s'", filename);
+ xmlFreeParserCtxt (ctxt);
+ return;
+ }
+
+ if (!gossip_xml_validate (doc, CONTACT_GROUPS_DTD_FILENAME)) {
+ g_warning ("Failed to validate file:'%s'", filename);
+ xmlFreeDoc(doc);
+ xmlFreeParserCtxt (ctxt);
+ return;
+ }
+
+ /* The root node, contacts. */
+ contacts = xmlDocGetRootElement (doc);
+
+ account = NULL;
+ node = contacts->children;
+ while (node) {
+ if (strcmp ((gchar *) node->name, "account") == 0) {
+ account = node;
+ break;
+ }
+ node = node->next;
+ }
+
+ node = NULL;
+ if (account) {
+ node = account->children;
+ }
+
+ while (node) {
+ if (strcmp ((gchar *) node->name, "group") == 0) {
+ gchar *name;
+ gchar *expanded_str;
+ gboolean expanded;
+ ContactGroup *contact_group;
+
+ name = (gchar *) xmlGetProp (node, "name");
+ expanded_str = (gchar *) xmlGetProp (node, "expanded");
+
+ if (expanded_str && strcmp (expanded_str, "yes") == 0) {
+ expanded = TRUE;
+ } else {
+ expanded = FALSE;
+ }
+
+ contact_group = contact_group_new (name, expanded);
+ groups = g_list_append (groups, contact_group);
+
+ xmlFree (name);
+ xmlFree (expanded_str);
+ }
+
+ node = node->next;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Parsed %d contact groups", g_list_length (groups));
+
+ xmlFreeDoc(doc);
+ xmlFreeParserCtxt (ctxt);
+}
+
+static ContactGroup *
+contact_group_new (const gchar *name,
+ gboolean expanded)
+{
+ ContactGroup *group;
+
+ group = g_new0 (ContactGroup, 1);
+
+ group->name = g_strdup (name);
+ group->expanded = expanded;
+
+ return group;
+}
+
+static void
+contact_group_free (ContactGroup *group)
+{
+ g_return_if_fail (group != NULL);
+
+ g_free (group->name);
+
+ g_free (group);
+}
+
+static gboolean
+contact_groups_file_save (void)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root;
+ xmlNodePtr node;
+ GList *l;
+ gchar *dir;
+ gchar *file;
+
+ dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
+ g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
+ file = g_build_filename (dir, CONTACT_GROUPS_XML_FILENAME, NULL);
+ g_free (dir);
+
+ doc = xmlNewDoc ("1.0");
+ root = xmlNewNode (NULL, "contacts");
+ xmlDocSetRootElement (doc, root);
+
+ node = xmlNewChild (root, NULL, "account", NULL);
+ xmlNewProp (node, "name", "Default");
+
+ for (l = groups; l; l = l->next) {
+ ContactGroup *cg;
+ xmlNodePtr subnode;
+
+ cg = l->data;
+
+ subnode = xmlNewChild (node, NULL, "group", NULL);
+ xmlNewProp (subnode, "expanded", cg->expanded ? "yes" : "no");
+ xmlNewProp (subnode, "name", cg->name);
+ }
+
+ /* Make sure the XML is indented properly */
+ xmlIndentTreeOutput = 1;
+
+ gossip_debug (DEBUG_DOMAIN, "Saving file:'%s'", file);
+ xmlSaveFormatFileEnc (file, doc, "utf-8", 1);
+ xmlFreeDoc (doc);
+
+ xmlCleanupParser ();
+ xmlMemoryDump ();
+
+ g_free (file);
+
+ return TRUE;
+}
+
+gboolean
+gossip_contact_group_get_expanded (const gchar *group)
+{
+ GList *l;
+ gboolean default_val = TRUE;
+
+ g_return_val_if_fail (group != NULL, default_val);
+
+ for (l = groups; l; l = l->next) {
+ ContactGroup *cg = l->data;
+
+ if (!cg || !cg->name) {
+ continue;
+ }
+
+ if (strcmp (cg->name, group) == 0) {
+ return cg->expanded;
+ }
+ }
+
+ return default_val;
+}
+
+void
+gossip_contact_group_set_expanded (const gchar *group,
+ gboolean expanded)
+{
+ GList *l;
+ ContactGroup *cg;
+ gboolean changed = FALSE;
+
+ g_return_if_fail (group != NULL);
+
+ for (l = groups; l; l = l->next) {
+ ContactGroup *cg = l->data;
+
+ if (!cg || !cg->name) {
+ continue;
+ }
+
+ if (strcmp (cg->name, group) == 0) {
+ cg->expanded = expanded;
+ changed = TRUE;
+ break;
+ }
+ }
+
+ /* if here... we don't have a ContactGroup for the group. */
+ if (!changed) {
+ cg = contact_group_new (group, expanded);
+ groups = g_list_append (groups, cg);
+ }
+
+ contact_groups_file_save ();
+}
diff --git a/libempathy-gtk/gossip-contact-groups.dtd b/libempathy-gtk/gossip-contact-groups.dtd
new file mode 100644
index 000000000..689220f0e
--- /dev/null
+++ b/libempathy-gtk/gossip-contact-groups.dtd
@@ -0,0 +1,17 @@
+<!--
+ DTD for Gossips contact groups.
+ by Martyn Russell <mr@gnome.org>
+-->
+
+<!-- Root element. -->
+<!ELEMENT contacts (account)>
+
+<!ELEMENT account (group)+>
+<!ATTLIST account
+ name CDATA #REQUIRED>
+
+<!-- Groups in the roster. -->
+<!ELEMENT group EMPTY>
+<!ATTLIST group
+ name CDATA #REQUIRED
+ expanded CDATA #REQUIRED>
diff --git a/libempathy-gtk/gossip-contact-groups.h b/libempathy-gtk/gossip-contact-groups.h
new file mode 100644
index 000000000..88bbdc0cb
--- /dev/null
+++ b/libempathy-gtk/gossip-contact-groups.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_CONTACT_GROUPS_H__
+#define __GOSSIP_CONTACT_GROUPS_H__
+
+G_BEGIN_DECLS
+
+#include <glib.h>
+
+void gossip_contact_groups_get_all (void);
+
+gboolean gossip_contact_group_get_expanded (const gchar *group);
+void gossip_contact_group_set_expanded (const gchar *group,
+ gboolean expanded);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CONTACT_GROUPS_H__ */
diff --git a/libempathy-gtk/gossip-contact-list.c b/libempathy-gtk/gossip-contact-list.c
new file mode 100644
index 000000000..6e1573d8f
--- /dev/null
+++ b/libempathy-gtk/gossip-contact-list.c
@@ -0,0 +1,2419 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+#include <libempathy/empathy-contact-manager.h>
+#include <libempathy/gossip-debug.h>
+#include <libempathy/empathy-session.h>
+
+#include "gossip-contact-list.h"
+#include "gossip-contact-groups.h"
+#include "gossip-cell-renderer-expander.h"
+#include "gossip-cell-renderer-text.h"
+#include "gossip-stock.h"
+#include "gossip-ui-utils.h"
+//#include "gossip-chat-invite.h"
+//#include "gossip-contact-info-dialog.h"
+//#include "gossip-edit-contact-dialog.h"
+//#include "gossip-ft-window.h"
+//#include "gossip-log-window.h"
+
+#define DEBUG_DOMAIN "ContactListUI"
+
+/* Flashing delay for icons (milliseconds). */
+#define FLASH_TIMEOUT 500
+
+/* Active users are those which have recently changed state
+ * (e.g. online, offline or from normal to a busy state).
+ */
+
+/* Time user is shown as active */
+#define ACTIVE_USER_SHOW_TIME 7000
+
+/* Time after connecting which we wait before active users are enabled */
+#define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5000
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST, GossipContactListPriv))
+
+struct _GossipContactListPriv {
+ EmpathyContactManager *manager;
+
+ GHashTable *groups;
+
+ GtkUIManager *ui;
+ GtkTreeRowReference *drag_row;
+
+ gboolean show_offline;
+ gboolean show_avatars;
+ gboolean is_compact;
+ gboolean show_active;
+};
+
+typedef struct {
+ const gchar *name;
+ gboolean found;
+ GtkTreeIter iter;
+} FindGroup;
+
+typedef struct {
+ GossipContact *contact;
+ gboolean found;
+ GList *iters;
+} FindContact;
+
+typedef struct {
+ GossipContactList *list;
+ GtkTreePath *path;
+ guint timeout_id;
+} DragMotionData;
+
+typedef struct {
+ GossipContactList *list;
+ GossipContact *contact;
+ gboolean remove;
+} ShowActiveData;
+
+static void gossip_contact_list_class_init (GossipContactListClass *klass);
+static void gossip_contact_list_init (GossipContactList *list);
+static void contact_list_finalize (GObject *object);
+static void contact_list_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void contact_list_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void contact_list_contact_update (GossipContactList *list,
+ GossipContact *contact);
+static void contact_list_contact_added_cb (EmpathyContactManager *manager,
+ GossipContact *contact,
+ GossipContactList *list);
+static void contact_list_contact_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipContactList *list);
+static void contact_list_contact_groups_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipContactList *list);
+static void contact_list_contact_removed_cb (EmpathyContactManager *manager,
+ GossipContact *contact,
+ GossipContactList *list);
+static void contact_list_contact_set_active (GossipContactList *list,
+ GossipContact *contact,
+ gboolean active,
+ gboolean set_changed);
+static ShowActiveData *
+ contact_list_contact_active_new (GossipContactList *list,
+ GossipContact *contact,
+ gboolean remove);
+static void contact_list_contact_active_free (ShowActiveData *data);
+static gboolean contact_list_contact_active_cb (ShowActiveData *data);
+static gchar * contact_list_get_parent_group (GtkTreeModel *model,
+ GtkTreePath *path,
+ gboolean *path_is_group);
+static void contact_list_get_group (GossipContactList *list,
+ const gchar *name,
+ GtkTreeIter *iter_to_set,
+ gboolean *created);
+static gboolean contact_list_get_group_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ FindGroup *fg);
+static void contact_list_add_contact (GossipContactList *list,
+ GossipContact *contact);
+static void contact_list_remove_contact (GossipContactList *list,
+ GossipContact *contact);
+static void contact_list_create_model (GossipContactList *list);
+static gboolean contact_list_search_equal_func (GtkTreeModel *model,
+ gint column,
+ const gchar *key,
+ GtkTreeIter *iter,
+ gpointer search_data);
+static void contact_list_setup_view (GossipContactList *list);
+static void contact_list_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ gpointer user_data);
+static gboolean contact_list_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time,
+ gpointer data);
+static gboolean contact_list_drag_motion_cb (DragMotionData *data);
+static void contact_list_drag_begin (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data);
+static void contact_list_drag_data_get (GtkWidget *widget,
+ GdkDragContext *contact,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ gpointer user_data);
+static void contact_list_drag_end (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data);
+static void contact_list_cell_set_background (GossipContactList *list,
+ GtkCellRenderer *cell,
+ gboolean is_group,
+ gboolean is_active);
+static void contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list);
+static void contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list);
+static void contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list);
+static void contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list);
+static GtkWidget *contact_list_get_contact_menu (GossipContactList *list,
+ gboolean can_send_file,
+ gboolean can_show_log);
+static gboolean contact_list_button_press_event_cb (GossipContactList *list,
+ GdkEventButton *event,
+ gpointer user_data);
+static void contact_list_row_activated_cb (GossipContactList *list,
+ GtkTreePath *path,
+ GtkTreeViewColumn *col,
+ gpointer user_data);
+static void contact_list_row_expand_or_collapse_cb (GossipContactList *list,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gpointer user_data);
+static gint contact_list_sort_func (GtkTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b,
+ gpointer user_data);
+static GList * contact_list_find_contact (GossipContactList *list,
+ GossipContact *contact);
+static gboolean contact_list_find_contact_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ FindContact *fc);
+static void contact_list_action_cb (GtkAction *action,
+ GossipContactList *list);
+static gboolean contact_list_update_list_mode_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GossipContactList *list);
+enum {
+ CONTACT_CHAT,
+ CONTACT_INFORMATION,
+ CONTACT_EDIT,
+ CONTACT_REMOVE,
+ CONTACT_INVITE,
+ CONTACT_SEND_FILE,
+ CONTACT_LOG,
+ GROUP_RENAME,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+enum {
+ COL_PIXBUF_STATUS,
+ COL_PIXBUF_AVATAR,
+ COL_PIXBUF_AVATAR_VISIBLE,
+ COL_NAME,
+ COL_STATUS,
+ COL_STATUS_VISIBLE,
+ COL_CONTACT,
+ COL_IS_GROUP,
+ COL_IS_ACTIVE,
+ COL_IS_ONLINE,
+ COL_COUNT
+};
+
+enum {
+ PROP_0,
+ PROP_SHOW_OFFLINE,
+ PROP_SHOW_AVATARS,
+ PROP_IS_COMPACT,
+};
+
+static const GtkActionEntry entries[] = {
+ { "ContactMenu", NULL,
+ N_("_Contact"), NULL, NULL,
+ NULL
+ },
+ { "GroupMenu", NULL,
+ N_("_Group"),NULL, NULL,
+ NULL
+ },
+ { "Chat", GOSSIP_STOCK_MESSAGE,
+ N_("_Chat"), NULL, N_("Chat with contact"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "Information", GOSSIP_STOCK_CONTACT_INFORMATION,
+ N_("Infor_mation"), "<control>I", N_("View contact information"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "Rename", NULL,
+ N_("Re_name"), NULL, N_("Rename"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "Edit", GTK_STOCK_EDIT,
+ N_("_Edit"), NULL, N_("Edit the groups and name for this contact"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "Remove", GTK_STOCK_REMOVE,
+ N_("_Remove"), NULL, N_("Remove contact"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "Invite", GOSSIP_STOCK_GROUP_MESSAGE,
+ N_("_Invite to Chat Room"), NULL, N_("Invite to a currently open chat room"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "SendFile", NULL,
+ N_("_Send File..."), NULL, N_("Send a file"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+ { "Log", GTK_STOCK_JUSTIFY_LEFT,
+ N_("_View Previous Conversations"), NULL, N_("View previous conversations with this contact"),
+ G_CALLBACK (contact_list_action_cb)
+ },
+};
+
+static guint n_entries = G_N_ELEMENTS (entries);
+
+static const gchar *ui_info =
+ "<ui>"
+ " <popup name='Contact'>"
+ " <menuitem action='Chat'/>"
+ " <menuitem action='Log'/>"
+ " <menuitem action='SendFile'/>"
+ " <separator/>"
+ " <menuitem action='Invite'/>"
+ " <separator/>"
+ " <menuitem action='Edit'/>"
+ " <menuitem action='Remove'/>"
+ " <separator/>"
+ " <menuitem action='Information'/>"
+ " </popup>"
+ " <popup name='Group'>"
+ " <menuitem action='Rename'/>"
+ " </popup>"
+ "</ui>";
+
+enum DndDragType {
+ DND_DRAG_TYPE_CONTACT_ID,
+ DND_DRAG_TYPE_URL,
+ DND_DRAG_TYPE_STRING,
+};
+
+static const GtkTargetEntry drag_types_dest[] = {
+ { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
+ { "text/uri-list", 0, DND_DRAG_TYPE_URL },
+ { "text/plain", 0, DND_DRAG_TYPE_STRING },
+ { "STRING", 0, DND_DRAG_TYPE_STRING },
+};
+
+static const GtkTargetEntry drag_types_source[] = {
+ { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
+};
+
+static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
+static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
+
+G_DEFINE_TYPE (GossipContactList, gossip_contact_list, GTK_TYPE_TREE_VIEW);
+
+static void
+gossip_contact_list_class_init (GossipContactListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = contact_list_finalize;
+ object_class->get_property = contact_list_get_property;
+ object_class->set_property = contact_list_set_property;
+
+ signals[CONTACT_CHAT] =
+ g_signal_new ("contact-chat",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[CONTACT_INFORMATION] =
+ g_signal_new ("contact-information",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[CONTACT_EDIT] =
+ g_signal_new ("contact-edit",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[CONTACT_REMOVE] =
+ g_signal_new ("contact-remove",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[CONTACT_INVITE] =
+ g_signal_new ("contact-invite",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[CONTACT_SEND_FILE] =
+ g_signal_new ("contact-send-file",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[CONTACT_LOG] =
+ g_signal_new ("contact-log",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1, GOSSIP_TYPE_CONTACT);
+ signals[GROUP_RENAME] =
+ g_signal_new ("group-rename",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE,
+ 1, G_TYPE_STRING);
+
+
+ g_object_class_install_property (object_class,
+ PROP_SHOW_OFFLINE,
+ g_param_spec_boolean ("show-offline",
+ "Show Offline",
+ "Whether contact list should display "
+ "offline contacts",
+ FALSE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_SHOW_AVATARS,
+ g_param_spec_boolean ("show-avatars",
+ "Show Avatars",
+ "Whether contact list should display "
+ "avatars for contacts",
+ TRUE,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_IS_COMPACT,
+ g_param_spec_boolean ("is-compact",
+ "Is Compact",
+ "Whether the contact list is in compact mode or not",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (GossipContactListPriv));
+}
+
+static void
+gossip_contact_list_init (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+ GtkActionGroup *action_group;
+ GList *contacts, *l;
+ GError *error = NULL;
+
+ priv = GET_PRIV (list);
+
+ priv->manager = empathy_session_get_contact_manager ();
+ g_object_ref (priv->manager);
+ priv->is_compact = FALSE;
+ priv->show_active = TRUE;
+ priv->show_avatars = TRUE;
+
+ contact_list_create_model (list);
+ contact_list_setup_view (list);
+ empathy_contact_manager_setup (priv->manager);
+
+ /* Get saved group states. */
+ gossip_contact_groups_get_all ();
+
+ /* Set up UI Manager */
+ priv->ui = gtk_ui_manager_new ();
+
+ action_group = gtk_action_group_new ("Actions");
+ gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
+ gtk_action_group_add_actions (action_group, entries, n_entries, list);
+ gtk_ui_manager_insert_action_group (priv->ui, action_group, 0);
+
+ if (!gtk_ui_manager_add_ui_from_string (priv->ui, ui_info, -1, &error)) {
+ g_warning ("Could not build contact menus from string:'%s'", error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (action_group);
+
+ /* Signal connection. */
+ g_signal_connect (priv->manager,
+ "contact-added",
+ G_CALLBACK (contact_list_contact_added_cb),
+ list);
+ g_signal_connect (priv->manager,
+ "contact-removed",
+ G_CALLBACK (contact_list_contact_removed_cb),
+ list);
+
+ /* Connect to tree view signals rather than override. */
+ g_signal_connect (list,
+ "button-press-event",
+ G_CALLBACK (contact_list_button_press_event_cb),
+ NULL);
+ g_signal_connect (list,
+ "row-activated",
+ G_CALLBACK (contact_list_row_activated_cb),
+ NULL);
+ g_signal_connect (list,
+ "row-expanded",
+ G_CALLBACK (contact_list_row_expand_or_collapse_cb),
+ GINT_TO_POINTER (TRUE));
+ g_signal_connect (list,
+ "row-collapsed",
+ G_CALLBACK (contact_list_row_expand_or_collapse_cb),
+ GINT_TO_POINTER (FALSE));
+
+ /* Add contacts already created */
+ contacts = empathy_contact_manager_get_contacts (priv->manager);
+ for (l = contacts; l; l = l->next) {
+ GossipContact *contact;
+
+ contact = l->data;
+
+ contact_list_contact_added_cb (priv->manager, contact, list);
+
+ g_object_unref (contact);
+ }
+}
+
+static void
+contact_list_finalize (GObject *object)
+{
+ GossipContactListPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ /* FIXME: disconnect all signals on the manager and contacts */
+
+ g_object_unref (priv->manager);
+ g_object_unref (priv->ui);
+
+ G_OBJECT_CLASS (gossip_contact_list_parent_class)->finalize (object);
+}
+
+static void
+contact_list_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GossipContactListPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_SHOW_OFFLINE:
+ g_value_set_boolean (value, priv->show_offline);
+ break;
+ case PROP_SHOW_AVATARS:
+ g_value_set_boolean (value, priv->show_avatars);
+ break;
+ case PROP_IS_COMPACT:
+ g_value_set_boolean (value, priv->is_compact);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+contact_list_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GossipContactListPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ switch (param_id) {
+ case PROP_SHOW_OFFLINE:
+ gossip_contact_list_set_show_offline (GOSSIP_CONTACT_LIST (object),
+ g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_AVATARS:
+ gossip_contact_list_set_show_avatars (GOSSIP_CONTACT_LIST (object),
+ g_value_get_boolean (value));
+ break;
+ case PROP_IS_COMPACT:
+ gossip_contact_list_set_is_compact (GOSSIP_CONTACT_LIST (object),
+ g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ };
+}
+
+static void
+contact_list_contact_update (GossipContactList *list,
+ GossipContact *contact)
+{
+ GossipContactListPriv *priv;
+ ShowActiveData *data;
+ GtkTreeModel *model;
+ GList *iters, *l;
+ gboolean in_list;
+ gboolean should_be_in_list;
+ gboolean was_online = TRUE;
+ gboolean now_online = FALSE;
+ gboolean set_model = FALSE;
+ gboolean do_remove = FALSE;
+ gboolean do_set_active = FALSE;
+ gboolean do_set_refresh = FALSE;
+ GdkPixbuf *pixbuf_presence;
+ GdkPixbuf *pixbuf_avatar;
+
+ priv = GET_PRIV (list);
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ iters = contact_list_find_contact (list, contact);
+ if (!iters) {
+ in_list = FALSE;
+ } else {
+ in_list = TRUE;
+ }
+
+ /* Get online state now. */
+ now_online = gossip_contact_is_online (contact);
+
+ if (priv->show_offline || now_online) {
+ should_be_in_list = TRUE;
+ } else {
+ should_be_in_list = FALSE;
+ }
+
+ if (!in_list && !should_be_in_list) {
+ /* Nothing to do. */
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' in list:NO, should be:NO",
+ gossip_contact_get_name (contact));
+
+ g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
+ g_list_free (iters);
+ return;
+ }
+ else if (in_list && !should_be_in_list) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' in list:YES, should be:NO",
+ gossip_contact_get_name (contact));
+
+ if (priv->show_active) {
+ do_remove = TRUE;
+ do_set_active = TRUE;
+ do_set_refresh = TRUE;
+
+ set_model = TRUE;
+ gossip_debug (DEBUG_DOMAIN, "Remove item (after timeout)");
+ } else {
+ gossip_debug (DEBUG_DOMAIN, "Remove item (now)!");
+ contact_list_remove_contact (list, contact);
+ }
+ }
+ else if (!in_list && should_be_in_list) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' in list:NO, should be:YES",
+ gossip_contact_get_name (contact));
+
+ contact_list_add_contact (list, contact);
+
+ if (priv->show_active) {
+ do_set_active = TRUE;
+
+ gossip_debug (DEBUG_DOMAIN, "Set active (contact added)");
+ }
+ } else {
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' in list:YES, should be:YES",
+ gossip_contact_get_name (contact));
+
+ /* Get online state before. */
+ if (iters && g_list_length (iters) > 0) {
+ GtkTreeIter *iter;
+
+ iter = g_list_nth_data (iters, 0);
+ gtk_tree_model_get (model, iter, COL_IS_ONLINE, &was_online, -1);
+ }
+
+ /* Is this really an update or an online/offline. */
+ if (priv->show_active) {
+ if (was_online != now_online) {
+ gchar *str;
+
+ do_set_active = TRUE;
+ do_set_refresh = TRUE;
+
+ if (was_online) {
+ str = "online -> offline";
+ } else {
+ str = "offline -> online";
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Set active (contact updated %s)", str);
+ } else {
+ /* Was TRUE for presence updates. */
+ /* do_set_active = FALSE; */
+ do_set_refresh = TRUE;
+
+ gossip_debug (DEBUG_DOMAIN, "Set active (contact updated)");
+ }
+ }
+
+ set_model = TRUE;
+ }
+
+ pixbuf_presence = gossip_pixbuf_for_contact (contact);
+ pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
+ for (l = iters; l && set_model; l = l->next) {
+ gtk_tree_store_set (GTK_TREE_STORE (model), l->data,
+ COL_PIXBUF_STATUS, pixbuf_presence,
+ COL_STATUS, gossip_contact_get_status (contact),
+ COL_IS_ONLINE, now_online,
+ COL_NAME, gossip_contact_get_name (contact),
+ COL_PIXBUF_AVATAR, pixbuf_avatar,
+ -1);
+ }
+
+ if (pixbuf_presence) {
+ g_object_unref (pixbuf_presence);
+ }
+ if (pixbuf_avatar) {
+ g_object_unref (pixbuf_avatar);
+ }
+
+ if (priv->show_active && do_set_active) {
+ contact_list_contact_set_active (list, contact, do_set_active, do_set_refresh);
+
+ if (do_set_active) {
+ data = contact_list_contact_active_new (list, contact, do_remove);
+ g_timeout_add (ACTIVE_USER_SHOW_TIME,
+ (GSourceFunc) contact_list_contact_active_cb,
+ data);
+ }
+ }
+
+ /* FIXME: when someone goes online then offline quickly, the
+ * first timeout sets the user to be inactive and the second
+ * timeout removes the user from the contact list, really we
+ * should remove the first timeout.
+ */
+ g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
+ g_list_free (iters);
+}
+
+static void
+contact_list_contact_added_cb (EmpathyContactManager *manager,
+ GossipContact *contact,
+ GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+
+ priv = GET_PRIV (list);
+
+ gossip_debug (DEBUG_DOMAIN, "Contact:'%s' added",
+ gossip_contact_get_name (contact));
+
+ /* Connect notifications for contact updates */
+ g_signal_connect (contact, "notify::groups",
+ G_CALLBACK (contact_list_contact_groups_updated_cb),
+ list);
+ g_signal_connect (contact, "notify::presences",
+ G_CALLBACK (contact_list_contact_updated_cb),
+ list);
+ g_signal_connect (contact, "notify::name",
+ G_CALLBACK (contact_list_contact_updated_cb),
+ list);
+ g_signal_connect (contact, "notify::avatar",
+ G_CALLBACK (contact_list_contact_updated_cb),
+ list);
+ g_signal_connect (contact, "notify::type",
+ G_CALLBACK (contact_list_contact_updated_cb),
+ list);
+
+ contact_list_add_contact (list, contact);
+}
+
+static void
+contact_list_contact_groups_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+
+ priv = GET_PRIV (list);
+
+ if (priv->show_offline || gossip_contact_is_online (contact)) {
+ }
+
+
+ gossip_debug (DEBUG_DOMAIN, "Contact:'%s' groups updated",
+ gossip_contact_get_name (contact));
+
+ /* We do this to make sure the groups are correct, if not, we
+ * would have to check the groups already set up for each
+ * contact and then see what has been updated.
+ */
+ contact_list_remove_contact (list, contact);
+ contact_list_add_contact (list, contact);
+}
+
+static void
+contact_list_contact_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipContactList *list)
+{
+ contact_list_contact_update (list, contact);
+}
+
+static void
+contact_list_contact_removed_cb (EmpathyContactManager *manager,
+ GossipContact *contact,
+ GossipContactList *list)
+{
+ gossip_debug (DEBUG_DOMAIN, "Contact:'%s' removed",
+ gossip_contact_get_name (contact));
+
+ /* Disconnect signals */
+ g_signal_handlers_disconnect_by_func (contact,
+ G_CALLBACK (contact_list_contact_groups_updated_cb),
+ list);
+ g_signal_handlers_disconnect_by_func (contact,
+ G_CALLBACK (contact_list_contact_updated_cb),
+ list);
+
+ contact_list_remove_contact (list, contact);
+}
+
+static void
+contact_list_contact_set_active (GossipContactList *list,
+ GossipContact *contact,
+ gboolean active,
+ gboolean set_changed)
+{
+ GtkTreeModel *model;
+ GList *iters, *l;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ iters = contact_list_find_contact (list, contact);
+ for (l = iters; l; l = l->next) {
+ GtkTreePath *path;
+ GtkTreeIter *iter;
+
+ iter = l->data;
+
+ gtk_tree_store_set (GTK_TREE_STORE (model), iter,
+ COL_IS_ACTIVE, active,
+ -1);
+ gossip_debug (DEBUG_DOMAIN, "Set item %s", active ? "active" : "inactive");
+
+ if (set_changed) {
+ path = gtk_tree_model_get_path (model, iter);
+ gtk_tree_model_row_changed (model, path, iter);
+ gtk_tree_path_free (path);
+ }
+ }
+
+ g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
+ g_list_free (iters);
+}
+
+static ShowActiveData *
+contact_list_contact_active_new (GossipContactList *list,
+ GossipContact *contact,
+ gboolean remove)
+{
+ ShowActiveData *data;
+
+ g_return_val_if_fail (list != NULL, NULL);
+ g_return_val_if_fail (contact != NULL, NULL);
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' now active, and %s be removed",
+ gossip_contact_get_name (contact),
+ remove ? "WILL" : "WILL NOT");
+
+ data = g_slice_new0 (ShowActiveData);
+
+ data->list = g_object_ref (list);
+ data->contact = g_object_ref (contact);
+
+ data->remove = remove;
+
+ return data;
+}
+
+static void
+contact_list_contact_active_free (ShowActiveData *data)
+{
+ g_return_if_fail (data != NULL);
+
+ g_object_unref (data->contact);
+ g_object_unref (data->list);
+
+ g_slice_free (ShowActiveData, data);
+}
+
+static gboolean
+contact_list_contact_active_cb (ShowActiveData *data)
+{
+ GossipContactListPriv *priv;
+
+ g_return_val_if_fail (data != NULL, FALSE);
+
+ priv = GET_PRIV (data->list);
+
+ if (data->remove &&
+ !priv->show_offline &&
+ !gossip_contact_is_online (data->contact)) {
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' active timeout, removing item",
+ gossip_contact_get_name (data->contact));
+ contact_list_remove_contact (data->list,
+ data->contact);
+ }
+
+ gossip_debug (DEBUG_DOMAIN,
+ "Contact:'%s' no longer active",
+ gossip_contact_get_name (data->contact));
+ contact_list_contact_set_active (data->list,
+ data->contact,
+ FALSE,
+ TRUE);
+
+ contact_list_contact_active_free (data);
+
+ return FALSE;
+}
+
+static gchar *
+contact_list_get_parent_group (GtkTreeModel *model,
+ GtkTreePath *path,
+ gboolean *path_is_group)
+{
+ GtkTreeIter parent_iter, iter;
+ gchar *name;
+ gboolean is_group;
+
+ g_return_val_if_fail (model != NULL, NULL);
+ g_return_val_if_fail (path != NULL, NULL);
+ g_return_val_if_fail (path_is_group != NULL, NULL);
+
+ if (!gtk_tree_model_get_iter (model, &iter, path)) {
+ return NULL;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ COL_IS_GROUP, &is_group,
+ -1);
+
+ if (!is_group) {
+ if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
+ return NULL;
+ }
+
+ iter = parent_iter;
+
+ gtk_tree_model_get (model, &iter,
+ COL_IS_GROUP, &is_group,
+ -1);
+
+ if (!is_group) {
+ return NULL;
+ }
+
+ *path_is_group = TRUE;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ COL_NAME, &name,
+ -1);
+
+ return name;
+}
+
+static void
+contact_list_get_group (GossipContactList *list,
+ const gchar *name,
+ GtkTreeIter *iter_to_set,
+ gboolean *created)
+{
+ GtkTreeModel *model;
+ FindGroup fg;
+
+ memset (&fg, 0, sizeof (fg));
+
+ fg.name = name;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc) contact_list_get_group_foreach,
+ &fg);
+
+ if (!fg.found) {
+ if (created) {
+ *created = TRUE;
+ }
+
+ gtk_tree_store_append (GTK_TREE_STORE (model), iter_to_set, NULL);
+ gtk_tree_store_set (GTK_TREE_STORE (model), iter_to_set,
+ COL_PIXBUF_STATUS, NULL,
+ COL_NAME, name,
+ COL_IS_GROUP, TRUE,
+ COL_IS_ACTIVE, FALSE,
+ -1);
+ } else {
+ if (created) {
+ *created = FALSE;
+ }
+
+ *iter_to_set = fg.iter;
+ }
+}
+
+static gboolean
+contact_list_get_group_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ FindGroup *fg)
+{
+ gchar *str;
+ gboolean is_group;
+
+ /* Groups are only at the top level. */
+ if (gtk_tree_path_get_depth (path) != 1) {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (model, iter,
+ COL_NAME, &str,
+ COL_IS_GROUP, &is_group,
+ -1);
+ if (is_group && strcmp (str, fg->name) == 0) {
+ fg->found = TRUE;
+ fg->iter = *iter;
+ }
+
+ g_free (str);
+
+ return fg->found;
+}
+
+static void
+contact_list_add_contact (GossipContactList *list,
+ GossipContact *contact)
+{
+ GossipContactListPriv *priv;
+ GtkTreeIter iter, iter_group;
+ GtkTreeModel *model;
+ GList *l, *groups;
+
+ priv = GET_PRIV (list);
+
+ if (!priv->show_offline && !gossip_contact_is_online (contact)) {
+ return;
+ }
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ /* If no groups just add it at the top level. */
+ groups = gossip_contact_get_groups (contact);
+ if (!groups) {
+ GdkPixbuf *pixbuf_status;
+ GdkPixbuf *pixbuf_avatar;
+ gboolean show_avatar = FALSE;
+
+ pixbuf_status = gossip_pixbuf_for_contact (contact);
+ pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (
+ contact, 32, 32);
+
+ if (priv->show_avatars && !priv->is_compact) {
+ show_avatar = TRUE;
+ }
+
+ gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+ COL_PIXBUF_STATUS, pixbuf_status,
+ COL_PIXBUF_AVATAR, pixbuf_avatar,
+ COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
+ COL_NAME, gossip_contact_get_name (contact),
+ COL_STATUS, gossip_contact_get_status (contact),
+ COL_STATUS_VISIBLE, !priv->is_compact,
+ COL_CONTACT, contact,
+ COL_IS_GROUP, FALSE,
+ COL_IS_ACTIVE, FALSE,
+ COL_IS_ONLINE, gossip_contact_is_online (contact),
+ -1);
+
+ if (pixbuf_avatar) {
+ g_object_unref (pixbuf_avatar);
+ }
+ if (pixbuf_status) {
+ g_object_unref (pixbuf_status);
+ }
+ }
+
+ /* Else add to each group. */
+ for (l = groups; l; l = l->next) {
+ GtkTreePath *path;
+ GdkPixbuf *pixbuf_status;
+ GdkPixbuf *pixbuf_avatar;
+ const gchar *name;
+ gboolean created;
+ gboolean show_avatar = FALSE;
+
+ name = l->data;
+ if (!name) {
+ continue;
+ }
+
+ pixbuf_status = gossip_pixbuf_for_contact (contact);
+ pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (
+ contact, 32, 32);
+
+ contact_list_get_group (list, name, &iter_group, &created);
+
+ if (priv->show_avatars && !priv->is_compact) {
+ show_avatar = TRUE;
+ }
+
+ gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &iter_group);
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+ COL_PIXBUF_STATUS, pixbuf_status,
+ COL_PIXBUF_AVATAR, pixbuf_avatar,
+ COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
+ COL_NAME, gossip_contact_get_name (contact),
+ COL_STATUS, gossip_contact_get_status (contact),
+ COL_STATUS_VISIBLE, !priv->is_compact,
+ COL_CONTACT, contact,
+ COL_IS_GROUP, FALSE,
+ COL_IS_ACTIVE, FALSE,
+ COL_IS_ONLINE, gossip_contact_is_online (contact),
+ -1);
+
+ if (pixbuf_avatar) {
+ g_object_unref (pixbuf_avatar);
+ }
+ if (pixbuf_status) {
+ g_object_unref (pixbuf_status);
+ }
+
+ if (!created) {
+ continue;
+ }
+
+ path = gtk_tree_model_get_path (model, &iter_group);
+ if (!path) {
+ continue;
+ }
+
+ if (gossip_contact_group_get_expanded (name)) {
+ g_signal_handlers_block_by_func (list,
+ contact_list_row_expand_or_collapse_cb,
+ GINT_TO_POINTER (TRUE));
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (list), path, TRUE);
+ g_signal_handlers_unblock_by_func (list,
+ contact_list_row_expand_or_collapse_cb,
+ GINT_TO_POINTER (TRUE));
+ } else {
+ g_signal_handlers_block_by_func (list,
+ contact_list_row_expand_or_collapse_cb,
+ GINT_TO_POINTER (FALSE));
+ gtk_tree_view_collapse_row (GTK_TREE_VIEW (list), path);
+ g_signal_handlers_unblock_by_func (list,
+ contact_list_row_expand_or_collapse_cb,
+ GINT_TO_POINTER (FALSE));
+ }
+
+ gtk_tree_path_free (path);
+ }
+}
+
+static void
+contact_list_remove_contact (GossipContactList *list,
+ GossipContact *contact)
+{
+ GossipContactListPriv *priv;
+ GtkTreeModel *model;
+ GList *iters, *l;
+
+ priv = GET_PRIV (list);
+
+ iters = contact_list_find_contact (list, contact);
+ if (!iters) {
+ return;
+ }
+
+ /* Clean up model */
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ for (l = iters; l; l = l->next) {
+ GtkTreeIter parent;
+
+ if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
+ gtk_tree_model_iter_n_children (model, &parent) <= 1) {
+ gtk_tree_store_remove (GTK_TREE_STORE (model), &parent);
+ } else {
+ gtk_tree_store_remove (GTK_TREE_STORE (model), l->data);
+ }
+ }
+
+ g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
+ g_list_free (iters);
+}
+
+static void
+contact_list_create_model (GossipContactList *list)
+{
+ GtkTreeModel *model;
+
+ model = GTK_TREE_MODEL (
+ gtk_tree_store_new (COL_COUNT,
+ GDK_TYPE_PIXBUF, /* Status pixbuf */
+ GDK_TYPE_PIXBUF, /* Avatar pixbuf */
+ G_TYPE_BOOLEAN, /* Avatar pixbuf visible */
+ G_TYPE_STRING, /* Name */
+ G_TYPE_STRING, /* Status string */
+ G_TYPE_BOOLEAN, /* Show status */
+ GOSSIP_TYPE_CONTACT, /* Contact type */
+ G_TYPE_BOOLEAN, /* Is group */
+ G_TYPE_BOOLEAN, /* Is active */
+ G_TYPE_BOOLEAN)); /* Is online */
+
+ gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
+ COL_NAME,
+ contact_list_sort_func,
+ list, NULL);
+
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
+ COL_NAME,
+ GTK_SORT_ASCENDING);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (list), model);
+
+ g_object_unref (model);
+}
+
+static gboolean
+contact_list_search_equal_func (GtkTreeModel *model,
+ gint column,
+ const gchar *key,
+ GtkTreeIter *iter,
+ gpointer search_data)
+{
+ gchar *name, *name_folded;
+ gchar *key_folded;
+ gboolean ret;
+
+ gtk_tree_model_get (model, iter,
+ COL_NAME, &name,
+ -1);
+
+ name_folded = g_utf8_casefold (name, -1);
+ key_folded = g_utf8_casefold (key, -1);
+
+ if (strstr (name_folded, key_folded)) {
+ ret = FALSE;
+ } else {
+ ret = TRUE;
+ }
+
+ g_free (name);
+ g_free (name_folded);
+ g_free (key_folded);
+
+ return ret;
+}
+
+static void
+contact_list_setup_view (GossipContactList *list)
+{
+ GtkCellRenderer *cell;
+ GtkTreeViewColumn *col;
+ gint i;
+
+ gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (list),
+ contact_list_search_equal_func,
+ list,
+ NULL);
+
+ g_object_set (list,
+ "headers-visible", FALSE,
+ "reorderable", TRUE,
+ "show-expanders", FALSE,
+ NULL);
+
+ col = gtk_tree_view_column_new ();
+
+ /* State */
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (col, cell, FALSE);
+ gtk_tree_view_column_set_cell_data_func (
+ col, cell,
+ (GtkTreeCellDataFunc) contact_list_pixbuf_cell_data_func,
+ list, NULL);
+
+ g_object_set (cell,
+ "xpad", 5,
+ "ypad", 1,
+ "visible", FALSE,
+ NULL);
+
+ /* Name */
+ cell = gossip_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (col, cell, TRUE);
+ gtk_tree_view_column_set_cell_data_func (
+ col, cell,
+ (GtkTreeCellDataFunc) contact_list_text_cell_data_func,
+ list, NULL);
+
+ gtk_tree_view_column_add_attribute (col, cell,
+ "name", COL_NAME);
+ gtk_tree_view_column_add_attribute (col, cell,
+ "status", COL_STATUS);
+ gtk_tree_view_column_add_attribute (col, cell,
+ "is_group", COL_IS_GROUP);
+
+ /* Avatar */
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (col, cell, FALSE);
+ gtk_tree_view_column_set_cell_data_func (
+ col, cell,
+ (GtkTreeCellDataFunc) contact_list_avatar_cell_data_func,
+ list, NULL);
+
+ g_object_set (cell,
+ "xpad", 0,
+ "ypad", 0,
+ "visible", FALSE,
+ "width", 32,
+ "height", 32,
+ NULL);
+
+ /* Expander */
+ cell = gossip_cell_renderer_expander_new ();
+ gtk_tree_view_column_pack_end (col, cell, FALSE);
+ gtk_tree_view_column_set_cell_data_func (
+ col, cell,
+ (GtkTreeCellDataFunc) contact_list_expander_cell_data_func,
+ list, NULL);
+
+ /* Actually add the column now we have added all cell renderers */
+ gtk_tree_view_append_column (GTK_TREE_VIEW (list), col);
+
+ /* Drag & Drop. */
+ for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
+ drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
+ FALSE);
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
+ drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
+ FALSE);
+ }
+
+ /* Note: We support the COPY action too, but need to make the
+ * MOVE action the default.
+ */
+ gtk_drag_source_set (GTK_WIDGET (list),
+ GDK_BUTTON1_MASK,
+ drag_types_source,
+ G_N_ELEMENTS (drag_types_source),
+ GDK_ACTION_MOVE);
+
+ gtk_drag_dest_set (GTK_WIDGET (list),
+ GTK_DEST_DEFAULT_ALL,
+ drag_types_dest,
+ G_N_ELEMENTS (drag_types_dest),
+ GDK_ACTION_MOVE | GDK_ACTION_LINK);
+
+ g_signal_connect (GTK_WIDGET (list),
+ "drag-data-received",
+ G_CALLBACK (contact_list_drag_data_received),
+ NULL);
+
+ /* FIXME: noticed but when you drag the row over the treeview
+ * fast, it seems to stop redrawing itself, if we don't
+ * connect this signal, all is fine.
+ */
+ g_signal_connect (GTK_WIDGET (list),
+ "drag-motion",
+ G_CALLBACK (contact_list_drag_motion),
+ NULL);
+
+ g_signal_connect (GTK_WIDGET (list),
+ "drag-begin",
+ G_CALLBACK (contact_list_drag_begin),
+ NULL);
+ g_signal_connect (GTK_WIDGET (list),
+ "drag-data-get",
+ G_CALLBACK (contact_list_drag_data_get),
+ NULL);
+ g_signal_connect (GTK_WIDGET (list),
+ "drag-end",
+ G_CALLBACK (contact_list_drag_end),
+ NULL);
+}
+
+static void
+contact_list_drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ gpointer user_data)
+{
+ GossipContactListPriv *priv;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeViewDropPosition position;
+ GossipContact *contact;
+ GList *groups;
+ const gchar *id;
+ gchar *old_group;
+ gboolean is_row;
+ gboolean drag_success = TRUE;
+ gboolean drag_del = FALSE;
+
+ priv = GET_PRIV (widget);
+
+ id = (const gchar*) selection->data;
+ gossip_debug (DEBUG_DOMAIN, "Received %s%s drag & drop contact from roster with id:'%s'",
+ context->action == GDK_ACTION_MOVE ? "move" : "",
+ context->action == GDK_ACTION_COPY ? "copy" : "",
+ id);
+
+ /* FIXME: This is ambigous, an id can come from multiple accounts */
+ contact = empathy_contact_manager_find (priv->manager, id);
+ if (!contact) {
+ gossip_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
+ return;
+ }
+
+ groups = gossip_contact_get_groups (contact);
+
+ is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
+ x,
+ y,
+ &path,
+ &position);
+
+ if (!is_row) {
+ if (g_list_length (groups) != 1) {
+ /* if they have dragged a contact out of a
+ * group then we would set the contact to have
+ * NO groups but only if they were ONE group
+ * to begin with - should we do this
+ * regardless to how many groups they are in
+ * already or not at all?
+ */
+ return;
+ }
+
+ gossip_contact_set_groups (contact, NULL);
+ } else {
+ GList *l, *new_groups;
+ gchar *name;
+ gboolean is_group;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+ name = contact_list_get_parent_group (model, path, &is_group);
+
+ if (groups && name &&
+ g_list_find_custom (groups, name, (GCompareFunc)strcmp)) {
+ g_free (name);
+ return;
+ }
+
+ /* Get source group information. */
+ priv = GET_PRIV (widget);
+ if (!priv->drag_row) {
+ g_free (name);
+ return;
+ }
+
+ path = gtk_tree_row_reference_get_path (priv->drag_row);
+ if (!path) {
+ g_free (name);
+ return;
+ }
+
+ old_group = contact_list_get_parent_group (model, path, &is_group);
+ gtk_tree_path_free (path);
+
+ if (!name && old_group && GDK_ACTION_MOVE) {
+ drag_success = FALSE;
+ }
+
+ if (context->action == GDK_ACTION_MOVE) {
+ drag_del = TRUE;
+ }
+
+ /* Create new groups GList. */
+ for (l = groups, new_groups = NULL; l && drag_success; l = l->next) {
+ gchar *str;
+
+ str = l->data;
+ if (context->action == GDK_ACTION_MOVE &&
+ old_group != NULL &&
+ strcmp (str, old_group) == 0) {
+ continue;
+ }
+
+ if (str == NULL) {
+ continue;
+ }
+
+ new_groups = g_list_append (new_groups, g_strdup (str));
+ }
+
+ if (drag_success) {
+ if (name) {
+ new_groups = g_list_append (new_groups, name);
+ }
+ gossip_contact_set_groups (contact, new_groups);
+ } else {
+ g_free (name);
+ }
+ }
+
+ gtk_drag_finish (context, drag_success, drag_del, GDK_CURRENT_TIME);
+}
+
+static gboolean
+contact_list_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time,
+ gpointer data)
+{
+ static DragMotionData *dm = NULL;
+ GtkTreePath *path;
+ gboolean is_row;
+ gboolean is_different = FALSE;
+ gboolean cleanup = TRUE;
+
+ is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+ x,
+ y,
+ &path,
+ NULL,
+ NULL,
+ NULL);
+
+ cleanup &= (!dm);
+
+ if (is_row) {
+ cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
+ is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
+ } else {
+ cleanup &= FALSE;
+ }
+
+ if (!is_different && !cleanup) {
+ return TRUE;
+ }
+
+ if (dm) {
+ gtk_tree_path_free (dm->path);
+ if (dm->timeout_id) {
+ g_source_remove (dm->timeout_id);
+ }
+
+ g_free (dm);
+
+ dm = NULL;
+ }
+
+ if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
+ dm = g_new0 (DragMotionData, 1);
+
+ dm->list = GOSSIP_CONTACT_LIST (widget);
+ dm->path = gtk_tree_path_copy (path);
+
+ dm->timeout_id = g_timeout_add (
+ 1500,
+ (GSourceFunc) contact_list_drag_motion_cb,
+ dm);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+contact_list_drag_motion_cb (DragMotionData *data)
+{
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (data->list),
+ data->path,
+ FALSE);
+
+ data->timeout_id = 0;
+
+ return FALSE;
+}
+
+static void
+contact_list_drag_begin (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data)
+{
+ GossipContactListPriv *priv;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ priv = GET_PRIV (widget);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ return;
+ }
+
+ path = gtk_tree_model_get_path (model, &iter);
+ priv->drag_row = gtk_tree_row_reference_new (model, path);
+ gtk_tree_path_free (path);
+}
+
+static void
+contact_list_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection,
+ guint info,
+ guint time,
+ gpointer user_data)
+{
+ GossipContactListPriv *priv;
+ GtkTreePath *src_path;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GossipContact *contact;
+ const gchar *id;
+
+ priv = GET_PRIV (widget);
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+ if (!priv->drag_row) {
+ return;
+ }
+
+ src_path = gtk_tree_row_reference_get_path (priv->drag_row);
+ if (!src_path) {
+ return;
+ }
+
+ if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
+ gtk_tree_path_free (src_path);
+ return;
+ }
+
+ gtk_tree_path_free (src_path);
+
+ contact = gossip_contact_list_get_selected (GOSSIP_CONTACT_LIST (widget));
+ if (!contact) {
+ return;
+ }
+
+ id = gossip_contact_get_id (contact);
+ g_object_unref (contact);
+
+ switch (info) {
+ case DND_DRAG_TYPE_CONTACT_ID:
+ gtk_selection_data_set (selection, drag_atoms_source[info], 8,
+ (guchar*)id, strlen (id) + 1);
+ break;
+
+ default:
+ return;
+ }
+}
+
+static void
+contact_list_drag_end (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer user_data)
+{
+ GossipContactListPriv *priv;
+
+ priv = GET_PRIV (widget);
+
+ if (priv->drag_row) {
+ gtk_tree_row_reference_free (priv->drag_row);
+ priv->drag_row = NULL;
+ }
+}
+
+static void
+contact_list_cell_set_background (GossipContactList *list,
+ GtkCellRenderer *cell,
+ gboolean is_group,
+ gboolean is_active)
+{
+ GdkColor color;
+ GtkStyle *style;
+ gint color_sum_normal, color_sum_selected;
+
+ g_return_if_fail (list != NULL);
+ g_return_if_fail (cell != NULL);
+
+ style = gtk_widget_get_style (GTK_WIDGET (list));
+
+ if (!is_group) {
+ if (is_active) {
+ color = style->bg[GTK_STATE_SELECTED];
+
+ /* Here we take the current theme colour and add it to
+ * the colour for white and average the two. This
+ * gives a colour which is inline with the theme but
+ * slightly whiter.
+ */
+ color.red = (color.red + (style->white).red) / 2;
+ color.green = (color.green + (style->white).green) / 2;
+ color.blue = (color.blue + (style->white).blue) / 2;
+
+ g_object_set (cell,
+ "cell-background-gdk", &color,
+ NULL);
+ } else {
+ g_object_set (cell,
+ "cell-background-gdk", NULL,
+ NULL);
+ }
+ } else {
+ color = style->base[GTK_STATE_SELECTED];
+ color_sum_normal = color.red+color.green+color.blue;
+ color = style->base[GTK_STATE_NORMAL];
+ color_sum_selected = color.red+color.green+color.blue;
+ color = style->text_aa[GTK_STATE_INSENSITIVE];
+
+ if(color_sum_normal < color_sum_selected) {
+ /* found a light theme */
+ color.red = (color.red + (style->white).red) / 2;
+ color.green = (color.green + (style->white).green) / 2;
+ color.blue = (color.blue + (style->white).blue) / 2;
+ } else {
+ /* found a dark theme */
+ color.red = (color.red + (style->black).red) / 2;
+ color.green = (color.green + (style->black).green) / 2;
+ color.blue = (color.blue + (style->black).blue) / 2;
+ }
+
+ g_object_set (cell,
+ "cell-background-gdk", &color,
+ NULL);
+ }
+}
+
+static void
+contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list)
+{
+ GdkPixbuf *pixbuf;
+ gboolean is_group;
+ gboolean is_active;
+
+ gtk_tree_model_get (model, iter,
+ COL_IS_GROUP, &is_group,
+ COL_IS_ACTIVE, &is_active,
+ COL_PIXBUF_STATUS, &pixbuf,
+ -1);
+
+ g_object_set (cell,
+ "visible", !is_group,
+ "pixbuf", pixbuf,
+ NULL);
+
+ if (pixbuf) {
+ g_object_unref (pixbuf);
+ }
+
+ contact_list_cell_set_background (list, cell, is_group, is_active);
+}
+
+static void
+contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list)
+{
+ GdkPixbuf *pixbuf;
+ gboolean show_avatar;
+ gboolean is_group;
+ gboolean is_active;
+
+ gtk_tree_model_get (model, iter,
+ COL_PIXBUF_AVATAR, &pixbuf,
+ COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
+ COL_IS_GROUP, &is_group,
+ COL_IS_ACTIVE, &is_active,
+ -1);
+
+ g_object_set (cell,
+ "visible", !is_group && show_avatar,
+ "pixbuf", pixbuf,
+ NULL);
+
+ if (pixbuf) {
+ g_object_unref (pixbuf);
+ }
+
+ contact_list_cell_set_background (list, cell, is_group, is_active);
+}
+
+static void
+contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list)
+{
+ gboolean is_group;
+ gboolean is_active;
+ gboolean show_status;
+
+ gtk_tree_model_get (model, iter,
+ COL_IS_GROUP, &is_group,
+ COL_IS_ACTIVE, &is_active,
+ COL_STATUS_VISIBLE, &show_status,
+ -1);
+
+ g_object_set (cell,
+ "show-status", show_status,
+ NULL);
+
+ contact_list_cell_set_background (list, cell, is_group, is_active);
+}
+
+static void
+contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContactList *list)
+{
+ gboolean is_group;
+ gboolean is_active;
+
+ gtk_tree_model_get (model, iter,
+ COL_IS_GROUP, &is_group,
+ COL_IS_ACTIVE, &is_active,
+ -1);
+
+ if (gtk_tree_model_iter_has_child (model, iter)) {
+ GtkTreePath *path;
+ gboolean row_expanded;
+
+ path = gtk_tree_model_get_path (model, iter);
+ row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
+ gtk_tree_path_free (path);
+
+ g_object_set (cell,
+ "visible", TRUE,
+ "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
+ NULL);
+ } else {
+ g_object_set (cell, "visible", FALSE, NULL);
+ }
+
+ contact_list_cell_set_background (list, cell, is_group, is_active);
+}
+
+static GtkWidget *
+contact_list_get_contact_menu (GossipContactList *list,
+ gboolean can_send_file,
+ gboolean can_show_log)
+{
+ GossipContactListPriv *priv;
+ GtkAction *action;
+ GtkWidget *widget;
+
+ priv = GET_PRIV (list);
+
+ /* Sort out sensitive items */
+ action = gtk_ui_manager_get_action (priv->ui, "/Contact/Log");
+ gtk_action_set_sensitive (action, can_show_log);
+
+ action = gtk_ui_manager_get_action (priv->ui, "/Contact/SendFile");
+ gtk_action_set_visible (action, can_send_file);
+
+ widget = gtk_ui_manager_get_widget (priv->ui, "/Contact");
+
+ return widget;
+}
+
+GtkWidget *
+gossip_contact_list_get_group_menu (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+ GtkWidget *widget;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
+
+ priv = GET_PRIV (list);
+
+ widget = gtk_ui_manager_get_widget (priv->ui, "/Group");
+
+ return widget;
+}
+
+GtkWidget *
+gossip_contact_list_get_contact_menu (GossipContactList *list,
+ GossipContact *contact)
+{
+ GtkWidget *menu;
+ gboolean can_show_log;
+ gboolean can_send_file;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ can_show_log = FALSE; /* FIXME: gossip_log_exists_for_contact (contact); */
+ can_send_file = FALSE;
+
+ menu = contact_list_get_contact_menu (list,
+ can_send_file,
+ can_show_log);
+ return menu;
+}
+
+static gboolean
+contact_list_button_press_event_cb (GossipContactList *list,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ GossipContactListPriv *priv;
+ GossipContact *contact;
+ GtkTreePath *path;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean row_exists;
+ GtkWidget *menu;
+
+ if (event->button != 3) {
+ return FALSE;
+ }
+
+ priv = GET_PRIV (list);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ gtk_widget_grab_focus (GTK_WIDGET (list));
+
+ row_exists = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (list),
+ event->x, event->y,
+ &path,
+ NULL, NULL, NULL);
+ if (!row_exists) {
+ return FALSE;
+ }
+
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, path);
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_path_free (path);
+
+ gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
+
+ if (contact) {
+ menu = gossip_contact_list_get_contact_menu (list, contact);
+ g_object_unref (contact);
+ } else {
+ menu = gossip_contact_list_get_group_menu (list);
+ }
+
+ if (!menu) {
+ return FALSE;
+ }
+
+ gtk_widget_show (menu);
+
+ gtk_menu_popup (GTK_MENU (menu),
+ NULL, NULL, NULL, NULL,
+ event->button, event->time);
+
+ return TRUE;
+}
+
+static void
+contact_list_row_activated_cb (GossipContactList *list,
+ GtkTreePath *path,
+ GtkTreeViewColumn *col,
+ gpointer user_data)
+{
+ GossipContact *contact;
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ view = GTK_TREE_VIEW (list);
+ model = gtk_tree_view_get_model (view);
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
+
+ if (contact) {
+ g_signal_emit (list, signals[CONTACT_CHAT], 0, contact);
+ g_object_unref (contact);
+ }
+}
+
+static void
+contact_list_row_expand_or_collapse_cb (GossipContactList *list,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ GtkTreeModel *model;
+ gchar *name;
+ gboolean expanded;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ gtk_tree_model_get (model, iter,
+ COL_NAME, &name,
+ -1);
+
+ expanded = GPOINTER_TO_INT (user_data);
+ gossip_contact_group_set_expanded (name, expanded);
+
+ g_free (name);
+}
+
+static gint
+contact_list_sort_func (GtkTreeModel *model,
+ GtkTreeIter *iter_a,
+ GtkTreeIter *iter_b,
+ gpointer user_data)
+{
+ gchar *name_a, *name_b;
+ GossipContact *contact_a, *contact_b;
+ gint ret_val;
+
+ gtk_tree_model_get (model, iter_a,
+ COL_NAME, &name_a,
+ COL_CONTACT, &contact_a,
+ -1);
+ gtk_tree_model_get (model, iter_b,
+ COL_NAME, &name_b,
+ COL_CONTACT, &contact_b,
+ -1);
+
+ /* If contact is NULL it means it's a group. */
+
+ if (!contact_a && contact_b) {
+ ret_val = 1;
+ } else if (contact_a && !contact_b) {
+ ret_val = -1;
+ } else {
+ ret_val = g_utf8_collate (name_a, name_b);
+ }
+
+ g_free (name_a);
+ g_free (name_b);
+
+ if (contact_a) {
+ g_object_unref (contact_a);
+ }
+
+ if (contact_b) {
+ g_object_unref (contact_b);
+ }
+
+ return ret_val;
+}
+
+static gboolean
+contact_list_iter_equal_contact (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GossipContact *contact)
+{
+ GossipContact *c;
+ gboolean equal;
+
+ gtk_tree_model_get (model, iter,
+ COL_CONTACT, &c,
+ -1);
+
+ if (!c) {
+ return FALSE;
+ }
+
+ equal = (c == contact);
+ g_object_unref (c);
+
+ return equal;
+}
+
+static GList *
+contact_list_find_contact (GossipContactList *list,
+ GossipContact *contact)
+{
+ GtkTreeModel *model;
+ FindContact fc;
+ GList *l = NULL;
+
+ memset (&fc, 0, sizeof (fc));
+
+ fc.contact = contact;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc) contact_list_find_contact_foreach,
+ &fc);
+
+ if (fc.found) {
+ l = fc.iters;
+ }
+
+ return l;
+}
+
+static gboolean
+contact_list_find_contact_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ FindContact *fc)
+{
+ if (contact_list_iter_equal_contact (model, iter, fc->contact)) {
+ fc->found = TRUE;
+ fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
+ }
+
+ /* We want to find ALL contacts that match, this means if we
+ * have the same contact in 3 groups, all iters should be
+ * returned.
+ */
+ return FALSE;
+}
+
+static void
+contact_list_action_cb (GtkAction *action,
+ GossipContactList *list)
+{
+ GossipContact *contact;
+ const gchar *name;
+ gchar *group;
+
+ name = gtk_action_get_name (action);
+ if (!name) {
+ return;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Action:'%s' activated", name);
+
+ contact = gossip_contact_list_get_selected (list);
+ group = gossip_contact_list_get_selected_group (list);
+
+ if (contact && strcmp (name, "Chat") == 0) {
+ g_signal_emit (list, signals[CONTACT_CHAT], 0, contact);
+ }
+ else if (contact && strcmp (name, "Information") == 0) {
+ g_signal_emit (list, signals[CONTACT_INFORMATION], 0, contact);
+ }
+ else if (contact && strcmp (name, "Edit") == 0) {
+ g_signal_emit (list, signals[CONTACT_EDIT], 0, contact);
+ }
+ else if (contact && strcmp (name, "Remove") == 0) {
+ g_signal_emit (list, signals[CONTACT_REMOVE], 0, contact);
+ }
+ else if (contact && strcmp (name, "Invite") == 0) {
+ g_signal_emit (list, signals[CONTACT_INVITE], 0, contact);
+ }
+ else if (contact && strcmp (name, "SendFile") == 0) {
+ g_signal_emit (list, signals[CONTACT_SEND_FILE], 0, contact);
+ }
+ else if (contact && strcmp (name, "Log") == 0) {
+ g_signal_emit (list, signals[CONTACT_LOG], 0, contact);
+ }
+ else if (group && strcmp (name, "Rename") == 0) {
+ g_signal_emit (list, signals[GROUP_RENAME], 0, group);
+ }
+
+ g_free (group);
+ if (contact) {
+ g_object_unref (contact);
+ }
+}
+
+static gboolean
+contact_list_update_list_mode_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+ gboolean show_avatar = FALSE;
+
+ priv = GET_PRIV (list);
+
+ if (priv->show_avatars && !priv->is_compact) {
+ show_avatar = TRUE;
+ }
+
+ gtk_tree_store_set (GTK_TREE_STORE (model), iter,
+ COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
+ COL_STATUS_VISIBLE, !priv->is_compact,
+ -1);
+
+ return FALSE;
+}
+
+GossipContactList *
+gossip_contact_list_new (void)
+{
+ return g_object_new (GOSSIP_TYPE_CONTACT_LIST, NULL);
+}
+
+GossipContact *
+gossip_contact_list_get_selected (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GossipContact *contact;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
+
+ priv = GET_PRIV (list);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ return NULL;
+ }
+
+ gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
+
+ return contact;
+}
+
+gchar *
+gossip_contact_list_get_selected_group (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gboolean is_group;
+ gchar *name;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
+
+ priv = GET_PRIV (list);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ return NULL;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ COL_IS_GROUP, &is_group,
+ COL_NAME, &name,
+ -1);
+
+ if (!is_group) {
+ g_free (name);
+ return NULL;
+ }
+
+ return name;
+}
+
+gboolean
+gossip_contact_list_get_show_offline (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), FALSE);
+
+ priv = GET_PRIV (list);
+
+ return priv->show_offline;
+}
+
+void
+gossip_contact_list_set_show_offline (GossipContactList *list,
+ gboolean show_offline)
+{
+ GossipContactListPriv *priv;
+ GList *contacts, *l;
+ gboolean show_active;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
+
+ priv = GET_PRIV (list);
+
+ priv->show_offline = show_offline;
+ show_active = priv->show_active;
+
+ /* Disable temporarily. */
+ priv->show_active = FALSE;
+
+ contacts = empathy_contact_manager_get_contacts (priv->manager);
+ for (l = contacts; l; l = l->next) {
+ GossipContact *contact;
+
+ contact = GOSSIP_CONTACT (l->data);
+
+ contact_list_contact_update (list, contact);
+
+ g_object_unref (contact);
+ }
+ g_list_free (contacts);
+
+ /* Restore to original setting. */
+ priv->show_active = show_active;
+}
+
+gboolean
+gossip_contact_list_get_show_avatars (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
+
+ priv = GET_PRIV (list);
+
+ return priv->show_avatars;
+}
+
+void
+gossip_contact_list_set_show_avatars (GossipContactList *list,
+ gboolean show_avatars)
+{
+ GossipContactListPriv *priv;
+ GtkTreeModel *model;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
+
+ priv = GET_PRIV (list);
+
+ priv->show_avatars = show_avatars;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc)
+ contact_list_update_list_mode_foreach,
+ list);
+}
+
+gboolean
+gossip_contact_list_get_is_compact (GossipContactList *list)
+{
+ GossipContactListPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
+
+ priv = GET_PRIV (list);
+
+ return priv->is_compact;
+}
+
+void
+gossip_contact_list_set_is_compact (GossipContactList *list,
+ gboolean is_compact)
+{
+ GossipContactListPriv *priv;
+ GtkTreeModel *model;
+
+ g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
+
+ priv = GET_PRIV (list);
+
+ priv->is_compact = is_compact;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc)
+ contact_list_update_list_mode_foreach,
+ list);
+}
+
diff --git a/libempathy-gtk/gossip-contact-list.h b/libempathy-gtk/gossip-contact-list.h
new file mode 100644
index 000000000..b5947b947
--- /dev/null
+++ b/libempathy-gtk/gossip-contact-list.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_CONTACT_LIST_H__
+#define __GOSSIP_CONTACT_LIST_H__
+
+#include <gtk/gtktreeview.h>
+
+#include <libempathy/gossip-contact.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_CONTACT_LIST (gossip_contact_list_get_type ())
+#define GOSSIP_CONTACT_LIST(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_CONTACT_LIST, GossipContactList))
+#define GOSSIP_CONTACT_LIST_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GOSSIP_TYPE_CONTACT_LIST, GossipContactListClass))
+#define GOSSIP_IS_CONTACT_LIST(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_CONTACT_LIST))
+#define GOSSIP_IS_CONTACT_LIST_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_CONTACT_LIST))
+#define GOSSIP_CONTACT_LIST_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_CONTACT_LIST, GossipContactListClass))
+
+typedef struct _GossipContactList GossipContactList;
+typedef struct _GossipContactListClass GossipContactListClass;
+typedef struct _GossipContactListPriv GossipContactListPriv;
+
+struct _GossipContactList {
+ GtkTreeView parent;
+};
+
+struct _GossipContactListClass {
+ GtkTreeViewClass parent_class;
+};
+
+GType gossip_contact_list_get_type (void) G_GNUC_CONST;
+GossipContactList *gossip_contact_list_new (void);
+GossipContact * gossip_contact_list_get_selected (GossipContactList *list);
+gchar * gossip_contact_list_get_selected_group (GossipContactList *list);
+gboolean gossip_contact_list_get_show_offline (GossipContactList *list);
+void gossip_contact_list_set_show_offline (GossipContactList *list,
+ gboolean show_offline);
+gboolean gossip_contact_list_get_show_avatars (GossipContactList *list);
+void gossip_contact_list_set_show_avatars (GossipContactList *list,
+ gboolean show_avatars);
+gboolean gossip_contact_list_get_is_compact (GossipContactList *list);
+void gossip_contact_list_set_is_compact (GossipContactList *list,
+ gboolean is_compact);
+GtkWidget * gossip_contact_list_get_contact_menu (GossipContactList *list,
+ GossipContact *contact);
+GtkWidget * gossip_contact_list_get_group_menu (GossipContactList *list);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_CONTACT_LIST_H__ */
+
diff --git a/libempathy-gtk/gossip-preferences.c b/libempathy-gtk/gossip-preferences.c
new file mode 100644
index 000000000..2f127a515
--- /dev/null
+++ b/libempathy-gtk/gossip-preferences.c
@@ -0,0 +1,885 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+
+#include <libempathy/gossip-conf.h>
+
+#include "gossip-preferences.h"
+#include "gossip-stock.h"
+#include "gossip-ui-utils.h"
+#include "gossip-theme-manager.h"
+#include "gossip-spell.h"
+
+typedef struct {
+ GtkWidget *dialog;
+
+ GtkWidget *notebook;
+
+ GtkWidget *checkbutton_show_avatars;
+ GtkWidget *checkbutton_compact_contact_list;
+ GtkWidget *checkbutton_show_smileys;
+ GtkWidget *combobox_chat_theme;
+ GtkWidget *checkbutton_theme_chat_room;
+ GtkWidget *checkbutton_separate_chat_windows;
+
+ GtkWidget *checkbutton_sounds_for_messages;
+ GtkWidget *checkbutton_sounds_when_busy;
+ GtkWidget *checkbutton_sounds_when_away;
+ GtkWidget *checkbutton_popups_when_available;
+
+ GtkWidget *treeview_spell_checker;
+ GtkWidget *checkbutton_spell_checker;
+
+ GList *notify_ids;
+} GossipPreferences;
+
+static void preferences_setup_widgets (GossipPreferences *preferences);
+static void preferences_languages_setup (GossipPreferences *preferences);
+static void preferences_languages_add (GossipPreferences *preferences);
+static void preferences_languages_save (GossipPreferences *preferences);
+static gboolean preferences_languages_save_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gchar **languages);
+static void preferences_languages_load (GossipPreferences *preferences);
+static gboolean preferences_languages_load_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gchar **languages);
+static void preferences_languages_cell_toggled_cb (GtkCellRendererToggle *cell,
+ gchar *path_string,
+ GossipPreferences *preferences);
+static void preferences_themes_setup (GossipPreferences *preferences);
+static void preferences_widget_sync_bool (const gchar *key,
+ GtkWidget *widget);
+static void preferences_widget_sync_int (const gchar *key,
+ GtkWidget *widget);
+static void preferences_widget_sync_string (const gchar *key,
+ GtkWidget *widget);
+static void preferences_widget_sync_string_combo (const gchar *key,
+ GtkWidget *widget);
+static void preferences_notify_int_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void preferences_notify_string_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void preferences_notify_string_combo_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void preferences_notify_bool_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void preferences_notify_sensitivity_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void preferences_hookup_spin_button (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget);
+static void preferences_hookup_entry (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget);
+static void preferences_hookup_toggle_button (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget);
+static void preferences_hookup_string_combo (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget);
+static void preferences_hookup_sensitivity (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget);
+static void preferences_spin_button_value_changed_cb (GtkWidget *button,
+ gpointer user_data);
+static void preferences_entry_value_changed_cb (GtkWidget *entry,
+ gpointer user_data);
+static void preferences_toggle_button_toggled_cb (GtkWidget *button,
+ gpointer user_data);
+static void preferences_string_combo_changed_cb (GtkWidget *button,
+ gpointer user_data);
+static void preferences_destroy_cb (GtkWidget *widget,
+ GossipPreferences *preferences);
+static void preferences_response_cb (GtkWidget *widget,
+ gint response,
+ GossipPreferences *preferences);
+
+enum {
+ COL_LANG_ENABLED,
+ COL_LANG_CODE,
+ COL_LANG_NAME,
+ COL_LANG_COUNT
+};
+
+enum {
+ COL_COMBO_VISIBLE_NAME,
+ COL_COMBO_NAME,
+ COL_COMBO_COUNT
+};
+
+static void
+preferences_setup_widgets (GossipPreferences *preferences)
+{
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_SOUNDS_FOR_MESSAGES,
+ preferences->checkbutton_sounds_for_messages);
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_SOUNDS_WHEN_AWAY,
+ preferences->checkbutton_sounds_when_away);
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_SOUNDS_WHEN_BUSY,
+ preferences->checkbutton_sounds_when_busy);
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_POPUPS_WHEN_AVAILABLE,
+ preferences->checkbutton_popups_when_available);
+
+ preferences_hookup_sensitivity (preferences,
+ GOSSIP_PREFS_SOUNDS_FOR_MESSAGES,
+ preferences->checkbutton_sounds_when_away);
+ preferences_hookup_sensitivity (preferences,
+ GOSSIP_PREFS_SOUNDS_FOR_MESSAGES,
+ preferences->checkbutton_sounds_when_busy);
+
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_UI_SEPARATE_CHAT_WINDOWS,
+ preferences->checkbutton_separate_chat_windows);
+
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ preferences->checkbutton_show_avatars);
+
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_UI_COMPACT_CONTACT_LIST,
+ preferences->checkbutton_compact_contact_list);
+
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_CHAT_SHOW_SMILEYS,
+ preferences->checkbutton_show_smileys);
+
+ preferences_hookup_string_combo (preferences,
+ GOSSIP_PREFS_CHAT_THEME,
+ preferences->combobox_chat_theme);
+
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
+ preferences->checkbutton_theme_chat_room);
+
+ preferences_hookup_toggle_button (preferences,
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_ENABLED,
+ preferences->checkbutton_spell_checker);
+ preferences_hookup_sensitivity (preferences,
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_ENABLED,
+ preferences->treeview_spell_checker);
+}
+
+static void
+preferences_languages_setup (GossipPreferences *preferences)
+{
+ GtkTreeView *view;
+ GtkListStore *store;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ guint col_offset;
+
+ view = GTK_TREE_VIEW (preferences->treeview_spell_checker);
+
+ store = gtk_list_store_new (COL_LANG_COUNT,
+ G_TYPE_BOOLEAN, /* enabled */
+ G_TYPE_STRING, /* code */
+ G_TYPE_STRING); /* name */
+
+ gtk_tree_view_set_model (view, GTK_TREE_MODEL (store));
+
+ selection = gtk_tree_view_get_selection (view);
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+ model = GTK_TREE_MODEL (store);
+
+ renderer = gtk_cell_renderer_toggle_new ();
+ g_signal_connect (renderer, "toggled",
+ G_CALLBACK (preferences_languages_cell_toggled_cb),
+ preferences);
+
+ column = gtk_tree_view_column_new_with_attributes (NULL, renderer,
+ "active", COL_LANG_ENABLED,
+ NULL);
+
+ gtk_tree_view_append_column (view, column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ col_offset = gtk_tree_view_insert_column_with_attributes (view,
+ -1, _("Language"),
+ renderer,
+ "text", COL_LANG_NAME,
+ NULL);
+
+ g_object_set_data (G_OBJECT (renderer),
+ "column", GINT_TO_POINTER (COL_LANG_NAME));
+
+ column = gtk_tree_view_get_column (view, col_offset - 1);
+ gtk_tree_view_column_set_sort_column_id (column, COL_LANG_NAME);
+ gtk_tree_view_column_set_resizable (column, FALSE);
+ gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
+
+ g_object_unref (store);
+}
+
+static void
+preferences_languages_add (GossipPreferences *preferences)
+{
+ GtkTreeView *view;
+ GtkListStore *store;
+ GList *codes, *l;
+
+ view = GTK_TREE_VIEW (preferences->treeview_spell_checker);
+ store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
+
+ codes = gossip_spell_get_language_codes ();
+ for (l = codes; l; l = l->next) {
+ GtkTreeIter iter;
+ const gchar *code;
+ const gchar *name;
+
+ code = l->data;
+ name = gossip_spell_get_language_name (code);
+ if (!name) {
+ continue;
+ }
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COL_LANG_CODE, code,
+ COL_LANG_NAME, name,
+ -1);
+ }
+
+ gossip_spell_free_language_codes (codes);
+}
+
+static void
+preferences_languages_save (GossipPreferences *preferences)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+
+ gchar *languages = NULL;
+
+ view = GTK_TREE_VIEW (preferences->treeview_spell_checker);
+ model = gtk_tree_view_get_model (view);
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc) preferences_languages_save_foreach,
+ &languages);
+
+ if (!languages) {
+ /* Default to english */
+ languages = g_strdup ("en");
+ }
+
+ gossip_conf_set_string (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_LANGUAGES,
+ languages);
+
+ g_free (languages);
+}
+
+static gboolean
+preferences_languages_save_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gchar **languages)
+{
+ gboolean enabled;
+ gchar *code;
+
+ if (!languages) {
+ return TRUE;
+ }
+
+ gtk_tree_model_get (model, iter, COL_LANG_ENABLED, &enabled, -1);
+ if (!enabled) {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (model, iter, COL_LANG_CODE, &code, -1);
+ if (!code) {
+ return FALSE;
+ }
+
+ if (!(*languages)) {
+ *languages = g_strdup (code);
+ } else {
+ gchar *str = *languages;
+ *languages = g_strdup_printf ("%s,%s", str, code);
+ g_free (str);
+ }
+
+ g_free (code);
+
+ return FALSE;
+}
+
+static void
+preferences_languages_load (GossipPreferences *preferences)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ gchar *value;
+ gchar **vlanguages;
+
+ if (!gossip_conf_get_string (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_LANGUAGES,
+ &value) || !value) {
+ return;
+ }
+
+ vlanguages = g_strsplit (value, ",", -1);
+ g_free (value);
+
+ view = GTK_TREE_VIEW (preferences->treeview_spell_checker);
+ model = gtk_tree_view_get_model (view);
+
+ gtk_tree_model_foreach (model,
+ (GtkTreeModelForeachFunc) preferences_languages_load_foreach,
+ vlanguages);
+
+ g_strfreev (vlanguages);
+}
+
+static gboolean
+preferences_languages_load_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gchar **languages)
+{
+ gchar *code;
+ gchar *lang;
+ gint i;
+ gboolean found = FALSE;
+
+ if (!languages) {
+ return TRUE;
+ }
+
+ gtk_tree_model_get (model, iter, COL_LANG_CODE, &code, -1);
+ if (!code) {
+ return FALSE;
+ }
+
+ for (i = 0, lang = languages[i]; lang; lang = languages[++i]) {
+ if (strcmp (lang, code) == 0) {
+ found = TRUE;
+ }
+ }
+
+ gtk_list_store_set (GTK_LIST_STORE (model), iter, COL_LANG_ENABLED, found, -1);
+ return FALSE;
+}
+
+static void
+preferences_languages_cell_toggled_cb (GtkCellRendererToggle *cell,
+ gchar *path_string,
+ GossipPreferences *preferences)
+{
+ GtkTreeView *view;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gboolean enabled;
+
+ view = GTK_TREE_VIEW (preferences->treeview_spell_checker);
+ model = gtk_tree_view_get_model (view);
+ store = GTK_LIST_STORE (model);
+
+ path = gtk_tree_path_new_from_string (path_string);
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter, COL_LANG_ENABLED, &enabled, -1);
+
+ enabled ^= 1;
+
+ gtk_list_store_set (store, &iter, COL_LANG_ENABLED, enabled, -1);
+ gtk_tree_path_free (path);
+
+ preferences_languages_save (preferences);
+}
+
+static void
+preferences_themes_setup (GossipPreferences *preferences)
+{
+ GtkComboBox *combo;
+ GtkListStore *model;
+ GtkTreeIter iter;
+ const gchar **themes;
+ gint i;
+
+ combo = GTK_COMBO_BOX (preferences->combobox_chat_theme);
+
+ model = gtk_list_store_new (COL_COMBO_COUNT,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ themes = gossip_theme_manager_get_themes ();
+ for (i = 0; themes[i]; i += 2) {
+ gtk_list_store_append (model, &iter);
+ gtk_list_store_set (model, &iter,
+ COL_COMBO_VISIBLE_NAME, _(themes[i + 1]),
+ COL_COMBO_NAME, themes[i],
+ -1);
+ }
+
+ gtk_combo_box_set_model (combo, GTK_TREE_MODEL (model));
+ g_object_unref (model);
+}
+
+static void
+preferences_widget_sync_bool (const gchar *key, GtkWidget *widget)
+{
+ gboolean value;
+
+ if (gossip_conf_get_bool (gossip_conf_get (), key, &value)) {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), value);
+ }
+}
+
+static void
+preferences_widget_sync_int (const gchar *key, GtkWidget *widget)
+{
+ gint value;
+
+ if (gossip_conf_get_int (gossip_conf_get (), key, &value)) {
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), value);
+ }
+}
+
+static void
+preferences_widget_sync_string (const gchar *key, GtkWidget *widget)
+{
+ gchar *value;
+
+ if (gossip_conf_get_string (gossip_conf_get (), key, &value) && value) {
+ gtk_entry_set_text (GTK_ENTRY (widget), value);
+ g_free (value);
+ }
+}
+
+static void
+preferences_widget_sync_string_combo (const gchar *key, GtkWidget *widget)
+{
+ gchar *value;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean found;
+
+ if (!gossip_conf_get_string (gossip_conf_get (), key, &value)) {
+ return;
+ }
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (widget));
+
+ found = FALSE;
+ if (value && gtk_tree_model_get_iter_first (model, &iter)) {
+ gchar *name;
+
+ do {
+ gtk_tree_model_get (model, &iter,
+ COL_COMBO_NAME, &name,
+ -1);
+
+ if (strcmp (name, value) == 0) {
+ found = TRUE;
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (widget), &iter);
+ break;
+ } else {
+ found = FALSE;
+ }
+
+ g_free (name);
+ } while (gtk_tree_model_iter_next (model, &iter));
+ }
+
+ /* Fallback to the first one. */
+ if (!found) {
+ if (gtk_tree_model_get_iter_first (model, &iter)) {
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (widget), &iter);
+ }
+ }
+
+ g_free (value);
+}
+
+static void
+preferences_notify_int_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ gint value;
+
+ if (gossip_conf_get_int (conf, key, &value)) {
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (user_data), value);
+ }
+}
+
+static void
+preferences_notify_string_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ gchar *value;
+
+ if (gossip_conf_get_string (conf, key, &value) && value) {
+ gtk_entry_set_text (GTK_ENTRY (user_data), value);
+ g_free (value);
+ }
+}
+
+static void
+preferences_notify_string_combo_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ preferences_widget_sync_string_combo (key, user_data);
+}
+
+static void
+preferences_notify_bool_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ preferences_widget_sync_bool (key, user_data);
+/*
+ if (gossip_conf_get_bool (conf, key, &value)) {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (user_data),
+ gconf_value_get_bool (value));
+ }
+*/
+}
+
+static void
+preferences_notify_sensitivity_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ gboolean value;
+
+ if (gossip_conf_get_bool (conf, key, &value)) {
+ gtk_widget_set_sensitive (GTK_WIDGET (user_data), value);
+ }
+}
+
+static void
+preferences_add_id (GossipPreferences *preferences, guint id)
+{
+ preferences->notify_ids = g_list_prepend (preferences->notify_ids,
+ GUINT_TO_POINTER (id));
+}
+
+static void
+preferences_hookup_spin_button (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget)
+{
+ guint id;
+
+ /* Silence warning. */
+ if (0) {
+ preferences_hookup_spin_button (preferences, key, widget);
+ }
+
+ preferences_widget_sync_int (key, widget);
+
+ g_object_set_data_full (G_OBJECT (widget), "key",
+ g_strdup (key), g_free);
+
+ g_signal_connect (widget,
+ "value_changed",
+ G_CALLBACK (preferences_spin_button_value_changed_cb),
+ NULL);
+
+ id = gossip_conf_notify_add (gossip_conf_get (),
+ key,
+ preferences_notify_int_cb,
+ widget);
+ if (id) {
+ preferences_add_id (preferences, id);
+ }
+}
+
+static void
+preferences_hookup_entry (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget)
+{
+ guint id;
+
+ if (0) { /* Silent warning before we use this function. */
+ preferences_hookup_entry (preferences, key, widget);
+ }
+
+ preferences_widget_sync_string (key, widget);
+
+ g_object_set_data_full (G_OBJECT (widget), "key",
+ g_strdup (key), g_free);
+
+ g_signal_connect (widget,
+ "changed",
+ G_CALLBACK (preferences_entry_value_changed_cb),
+ NULL);
+
+ id = gossip_conf_notify_add (gossip_conf_get (),
+ key,
+ preferences_notify_string_cb,
+ widget);
+ if (id) {
+ preferences_add_id (preferences, id);
+ }
+}
+
+static void
+preferences_hookup_toggle_button (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget)
+{
+ guint id;
+
+ preferences_widget_sync_bool (key, widget);
+
+ g_object_set_data_full (G_OBJECT (widget), "key",
+ g_strdup (key), g_free);
+
+ g_signal_connect (widget,
+ "toggled",
+ G_CALLBACK (preferences_toggle_button_toggled_cb),
+ NULL);
+
+ id = gossip_conf_notify_add (gossip_conf_get (),
+ key,
+ preferences_notify_bool_cb,
+ widget);
+ if (id) {
+ preferences_add_id (preferences, id);
+ }
+}
+
+static void
+preferences_hookup_string_combo (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget)
+{
+ guint id;
+
+ preferences_widget_sync_string_combo (key, widget);
+
+ g_object_set_data_full (G_OBJECT (widget), "key",
+ g_strdup (key), g_free);
+
+ g_signal_connect (widget,
+ "changed",
+ G_CALLBACK (preferences_string_combo_changed_cb),
+ NULL);
+
+ id = gossip_conf_notify_add (gossip_conf_get (),
+ key,
+ preferences_notify_string_combo_cb,
+ widget);
+ if (id) {
+ preferences_add_id (preferences, id);
+ }
+}
+
+static void
+preferences_hookup_sensitivity (GossipPreferences *preferences,
+ const gchar *key,
+ GtkWidget *widget)
+{
+ gboolean value;
+ guint id;
+
+ if (gossip_conf_get_bool (gossip_conf_get (), key, &value)) {
+ gtk_widget_set_sensitive (widget, value);
+ }
+
+ id = gossip_conf_notify_add (gossip_conf_get (),
+ key,
+ preferences_notify_sensitivity_cb,
+ widget);
+ if (id) {
+ preferences_add_id (preferences, id);
+ }
+}
+
+static void
+preferences_spin_button_value_changed_cb (GtkWidget *button,
+ gpointer user_data)
+{
+ const gchar *key;
+
+ key = g_object_get_data (G_OBJECT (button), "key");
+
+ gossip_conf_set_int (gossip_conf_get (),
+ key,
+ gtk_spin_button_get_value (GTK_SPIN_BUTTON (button)));
+}
+
+static void
+preferences_entry_value_changed_cb (GtkWidget *entry,
+ gpointer user_data)
+{
+ const gchar *key;
+
+ key = g_object_get_data (G_OBJECT (entry), "key");
+
+ gossip_conf_set_string (gossip_conf_get (),
+ key,
+ gtk_entry_get_text (GTK_ENTRY (entry)));
+}
+
+static void
+preferences_toggle_button_toggled_cb (GtkWidget *button,
+ gpointer user_data)
+{
+ const gchar *key;
+
+ key = g_object_get_data (G_OBJECT (button), "key");
+
+ gossip_conf_set_bool (gossip_conf_get (),
+ key,
+ gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)));
+}
+
+static void
+preferences_string_combo_changed_cb (GtkWidget *combo,
+ gpointer user_data)
+{
+ const gchar *key;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *name;
+
+ key = g_object_get_data (G_OBJECT (combo), "key");
+
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) {
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo));
+
+ gtk_tree_model_get (model, &iter,
+ COL_COMBO_NAME, &name,
+ -1);
+ gossip_conf_set_string (gossip_conf_get (), key, name);
+ g_free (name);
+ }
+}
+
+static void
+preferences_response_cb (GtkWidget *widget,
+ gint response,
+ GossipPreferences *preferences)
+{
+ gtk_widget_destroy (widget);
+}
+
+static void
+preferences_destroy_cb (GtkWidget *widget,
+ GossipPreferences *preferences)
+{
+ GList *l;
+
+ for (l = preferences->notify_ids; l; l = l->next) {
+ guint id;
+
+ id = GPOINTER_TO_UINT (l->data);
+ gossip_conf_notify_remove (gossip_conf_get (), id);
+ }
+
+ g_list_free (preferences->notify_ids);
+ g_free (preferences);
+}
+
+void
+gossip_preferences_show (void)
+{
+ static GossipPreferences *preferences;
+ GladeXML *glade;
+
+ if (preferences) {
+ gtk_window_present (GTK_WINDOW (preferences->dialog));
+ return;
+ }
+
+ preferences = g_new0 (GossipPreferences, 1);
+
+ glade = gossip_glade_get_file (
+ "main.glade",
+ "preferences_dialog",
+ NULL,
+ "preferences_dialog", &preferences->dialog,
+ "notebook", &preferences->notebook,
+ "checkbutton_show_avatars", &preferences->checkbutton_show_avatars,
+ "checkbutton_compact_contact_list", &preferences->checkbutton_compact_contact_list,
+ "checkbutton_show_smileys", &preferences->checkbutton_show_smileys,
+ "combobox_chat_theme", &preferences->combobox_chat_theme,
+ "checkbutton_theme_chat_room", &preferences->checkbutton_theme_chat_room,
+ "checkbutton_separate_chat_windows", &preferences->checkbutton_separate_chat_windows,
+ "checkbutton_sounds_for_messages", &preferences->checkbutton_sounds_for_messages,
+ "checkbutton_sounds_when_busy", &preferences->checkbutton_sounds_when_busy,
+ "checkbutton_sounds_when_away", &preferences->checkbutton_sounds_when_away,
+ "checkbutton_popups_when_available", &preferences->checkbutton_popups_when_available,
+ "treeview_spell_checker", &preferences->treeview_spell_checker,
+ "checkbutton_spell_checker", &preferences->checkbutton_spell_checker,
+ NULL);
+
+ gossip_glade_connect (glade,
+ preferences,
+ "preferences_dialog", "destroy", preferences_destroy_cb,
+ "preferences_dialog", "response", preferences_response_cb,
+ NULL);
+
+ g_object_unref (glade);
+
+ g_object_add_weak_pointer (G_OBJECT (preferences->dialog), (gpointer) &preferences);
+
+ preferences_themes_setup (preferences);
+
+ preferences_setup_widgets (preferences);
+
+ preferences_languages_setup (preferences);
+ preferences_languages_add (preferences);
+ preferences_languages_load (preferences);
+
+ if (gossip_spell_supported ()) {
+ GtkWidget *page;
+
+ page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (preferences->notebook), 2);
+ gtk_widget_show (page);
+ }
+
+ gtk_widget_show (preferences->dialog);
+}
diff --git a/libempathy-gtk/gossip-preferences.h b/libempathy-gtk/gossip-preferences.h
new file mode 100644
index 000000000..428d0fc72
--- /dev/null
+++ b/libempathy-gtk/gossip-preferences.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_PREFERENCES_H__
+#define __GOSSIP_PREFERENCES_H__
+
+G_BEGIN_DECLS
+
+#define GOSSIP_PREFS_PATH "/apps/empathy"
+
+#define GOSSIP_PREFS_SOUNDS_FOR_MESSAGES GOSSIP_PREFS_PATH "/notifications/sounds_for_messages"
+#define GOSSIP_PREFS_SOUNDS_WHEN_AWAY GOSSIP_PREFS_PATH "/notifications/sounds_when_away"
+#define GOSSIP_PREFS_SOUNDS_WHEN_BUSY GOSSIP_PREFS_PATH "/notifications/sounds_when_busy"
+#define GOSSIP_PREFS_POPUPS_WHEN_AVAILABLE GOSSIP_PREFS_PATH "/notifications/popups_when_available"
+#define GOSSIP_PREFS_CHAT_SHOW_SMILEYS GOSSIP_PREFS_PATH "/conversation/graphical_smileys"
+#define GOSSIP_PREFS_CHAT_THEME GOSSIP_PREFS_PATH "/conversation/theme"
+#define GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM GOSSIP_PREFS_PATH "/conversation/theme_chat_room"
+#define GOSSIP_PREFS_CHAT_SPELL_CHECKER_LANGUAGES GOSSIP_PREFS_PATH "/conversation/spell_checker_languages"
+#define GOSSIP_PREFS_CHAT_SPELL_CHECKER_ENABLED GOSSIP_PREFS_PATH "/conversation/spell_checker_enabled"
+#define GOSSIP_PREFS_UI_SEPARATE_CHAT_WINDOWS GOSSIP_PREFS_PATH "/ui/separate_chat_windows"
+#define GOSSIP_PREFS_UI_MAIN_WINDOW_HIDDEN GOSSIP_PREFS_PATH "/ui/main_window_hidden"
+#define GOSSIP_PREFS_UI_AVATAR_DIRECTORY GOSSIP_PREFS_PATH "/ui/avatar_directory"
+#define GOSSIP_PREFS_UI_SHOW_AVATARS GOSSIP_PREFS_PATH "/ui/show_avatars"
+#define GOSSIP_PREFS_UI_COMPACT_CONTACT_LIST GOSSIP_PREFS_PATH "/ui/compact_contact_list"
+#define GOSSIP_PREFS_CONTACTS_SHOW_OFFLINE GOSSIP_PREFS_PATH "/contacts/show_offline"
+#define GOSSIP_PREFS_HINTS_CLOSE_MAIN_WINDOW GOSSIP_PREFS_PATH "/hints/close_main_window"
+
+void gossip_preferences_show (void);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_PREFERENCES_H__ */
+
+
diff --git a/libempathy-gtk/gossip-private-chat.c b/libempathy-gtk/gossip-private-chat.c
new file mode 100644
index 000000000..41a7f5946
--- /dev/null
+++ b/libempathy-gtk/gossip-private-chat.c
@@ -0,0 +1,495 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <glib/gi18n.h>
+
+#include <libmissioncontrol/mc-account.h>
+
+#include <libempathy/gossip-debug.h>
+#include <libempathy/empathy-tp-chat.h>
+//#include <libgossip/gossip-log.h>
+
+#include "gossip-private-chat.h"
+#include "gossip-chat-view.h"
+#include "gossip-chat.h"
+#include "gossip-preferences.h"
+//#include "gossip-sound.h"
+#include "gossip-stock.h"
+#include "gossip-ui-utils.h"
+
+#define DEBUG_DOMAIN "PrivateChat"
+
+#define COMPOSING_STOP_TIMEOUT 5
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_PRIVATE_CHAT, GossipPrivateChatPriv))
+
+struct _GossipPrivateChatPriv {
+ GossipContact *contact;
+ gchar *name;
+
+ guint scroll_idle_id;
+ gboolean is_online;
+
+ GtkWidget *widget;
+ GtkWidget *text_view_sw;
+};
+
+static void gossip_private_chat_class_init (GossipPrivateChatClass *klass);
+static void gossip_private_chat_init (GossipPrivateChat *chat);
+static void private_chat_finalize (GObject *object);
+static void private_chat_create_ui (GossipPrivateChat *chat);
+static void private_chat_contact_presence_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipPrivateChat *chat);
+static void private_chat_contact_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipPrivateChat *chat);
+static void private_chat_widget_destroy_cb (GtkWidget *widget,
+ GossipPrivateChat *chat);
+static const gchar * private_chat_get_name (GossipChat *chat);
+static gchar * private_chat_get_tooltip (GossipChat *chat);
+static GdkPixbuf * private_chat_get_status_pixbuf (GossipChat *chat);
+static GossipContact *private_chat_get_contact (GossipChat *chat);
+static GtkWidget * private_chat_get_widget (GossipChat *chat);
+/*static GdkPixbuf * private_chat_pad_to_size (GdkPixbuf *pixbuf,
+ gint width,
+ gint height,
+ gint extra_padding_right);*/
+
+G_DEFINE_TYPE (GossipPrivateChat, gossip_private_chat, GOSSIP_TYPE_CHAT);
+
+static void
+gossip_private_chat_class_init (GossipPrivateChatClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GossipChatClass *chat_class = GOSSIP_CHAT_CLASS (klass);
+
+ object_class->finalize = private_chat_finalize;
+
+ chat_class->get_name = private_chat_get_name;
+ chat_class->get_tooltip = private_chat_get_tooltip;
+ chat_class->get_status_pixbuf = private_chat_get_status_pixbuf;
+ chat_class->get_contact = private_chat_get_contact;
+ chat_class->get_widget = private_chat_get_widget;
+ chat_class->get_show_contacts = NULL;
+ chat_class->set_show_contacts = NULL;
+ chat_class->is_group_chat = NULL;
+
+ g_type_class_add_private (object_class, sizeof (GossipPrivateChatPriv));
+}
+
+static void
+gossip_private_chat_init (GossipPrivateChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ priv->is_online = FALSE;
+
+ private_chat_create_ui (chat);
+
+}
+
+static void
+private_chat_finalize (GObject *object)
+{
+ GossipPrivateChat *chat;
+ GossipPrivateChatPriv *priv;
+
+ chat = GOSSIP_PRIVATE_CHAT (object);
+ priv = GET_PRIV (chat);
+
+ g_signal_handlers_disconnect_by_func (priv->contact,
+ private_chat_contact_updated_cb,
+ chat);
+ g_signal_handlers_disconnect_by_func (priv->contact,
+ private_chat_contact_presence_updated_cb,
+ chat);
+
+ if (priv->contact) {
+ g_object_unref (priv->contact);
+ }
+
+ if (priv->scroll_idle_id) {
+ g_source_remove (priv->scroll_idle_id);
+ }
+
+ g_free (priv->name);
+
+ G_OBJECT_CLASS (gossip_private_chat_parent_class)->finalize (object);
+}
+
+static void
+private_chat_create_ui (GossipPrivateChat *chat)
+{
+ GladeXML *glade;
+ GossipPrivateChatPriv *priv;
+ GtkWidget *input_text_view_sw;
+
+ priv = GET_PRIV (chat);
+
+ glade = gossip_glade_get_file ("empathy-chat.glade",
+ "chat_widget",
+ NULL,
+ "chat_widget", &priv->widget,
+ "chat_view_sw", &priv->text_view_sw,
+ "input_text_view_sw", &input_text_view_sw,
+ NULL);
+
+ gossip_glade_connect (glade,
+ chat,
+ "chat_widget", "destroy", private_chat_widget_destroy_cb,
+ NULL);
+
+ g_object_unref (glade);
+
+ g_object_set_data (G_OBJECT (priv->widget), "chat", g_object_ref (chat));
+
+ gtk_container_add (GTK_CONTAINER (priv->text_view_sw),
+ GTK_WIDGET (GOSSIP_CHAT (chat)->view));
+ gtk_widget_show (GTK_WIDGET (GOSSIP_CHAT (chat)->view));
+
+ gtk_container_add (GTK_CONTAINER (input_text_view_sw),
+ GOSSIP_CHAT (chat)->input_text_view);
+ gtk_widget_show (GOSSIP_CHAT (chat)->input_text_view);
+}
+
+static void
+private_chat_contact_presence_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipPrivateChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ gossip_debug (DEBUG_DOMAIN, "Presence update for contact: %s",
+ gossip_contact_get_id (contact));
+
+ if (!gossip_contact_is_online (contact)) {
+ if (priv->is_online) {
+ gchar *msg;
+
+ msg = g_strdup_printf (_("%s went offline"),
+ gossip_contact_get_name (priv->contact));
+ gossip_chat_view_append_event (GOSSIP_CHAT (chat)->view, msg);
+ g_free (msg);
+ }
+
+ priv->is_online = FALSE;
+
+ g_signal_emit_by_name (chat, "composing", FALSE);
+
+ } else {
+ if (!priv->is_online) {
+ gchar *msg;
+
+ msg = g_strdup_printf (_("%s has come online"),
+ gossip_contact_get_name (priv->contact));
+ gossip_chat_view_append_event (GOSSIP_CHAT (chat)->view, msg);
+ g_free (msg);
+ }
+
+ priv->is_online = TRUE;
+ }
+
+ g_signal_emit_by_name (chat, "status-changed");
+}
+
+static void
+private_chat_contact_updated_cb (GossipContact *contact,
+ GParamSpec *param,
+ GossipPrivateChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ if (strcmp (priv->name, gossip_contact_get_name (contact)) != 0) {
+ g_free (priv->name);
+ priv->name = g_strdup (gossip_contact_get_name (contact));
+ g_signal_emit_by_name (chat, "name-changed", priv->name);
+ }
+}
+
+static void
+private_chat_widget_destroy_cb (GtkWidget *widget,
+ GossipPrivateChat *chat)
+{
+ gossip_debug (DEBUG_DOMAIN, "Destroyed");
+
+ g_object_unref (chat);
+}
+
+static const gchar *
+private_chat_get_name (GossipChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
+
+ priv = GET_PRIV (chat);
+
+ return priv->name;
+}
+
+static gchar *
+private_chat_get_tooltip (GossipChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+ GossipContact *contact;
+ const gchar *status;
+
+ g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
+
+ priv = GET_PRIV (chat);
+
+ contact = gossip_chat_get_contact (chat);
+ status = gossip_contact_get_status (contact);
+
+ return g_strdup_printf ("%s\n%s",
+ gossip_contact_get_id (contact),
+ status);
+}
+
+static GdkPixbuf *
+private_chat_get_status_pixbuf (GossipChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+ GossipContact *contact;
+
+ g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
+
+ priv = GET_PRIV (chat);
+
+ contact = gossip_chat_get_contact (chat);
+
+ return gossip_pixbuf_for_contact (contact);
+}
+
+static GossipContact *
+private_chat_get_contact (GossipChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ g_return_val_if_fail (GOSSIP_IS_PRIVATE_CHAT (chat), NULL);
+
+ priv = GET_PRIV (chat);
+
+ return priv->contact;
+}
+
+static GtkWidget *
+private_chat_get_widget (GossipChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ return priv->widget;
+}
+
+/* Scroll down after the back-log has been received. */
+static gboolean
+private_chat_scroll_down_idle_func (GossipChat *chat)
+{
+ GossipPrivateChatPriv *priv;
+
+ priv = GET_PRIV (chat);
+
+ gossip_chat_scroll_down (chat);
+ g_object_unref (chat);
+
+ priv->scroll_idle_id = 0;
+
+ return FALSE;
+}
+
+static void
+private_chat_setup (GossipPrivateChat *chat,
+ GossipContact *contact,
+ EmpathyTpChat *tp_chat)
+{
+ GossipPrivateChatPriv *priv;
+ //GossipLogManager *log_manager;
+ GossipChatView *view;
+/* GossipContact *sender;
+ GossipMessage *message;
+ GList *messages, *l;
+ gint num_messages, i;*/
+
+ priv = GET_PRIV (chat);
+
+ gossip_chat_set_tp_chat (GOSSIP_CHAT (chat), tp_chat);
+
+ priv->contact = g_object_ref (contact);
+ GOSSIP_CHAT (chat)->account = g_object_ref (gossip_contact_get_account (contact));
+
+ priv->name = g_strdup (gossip_contact_get_name (contact));
+
+ g_signal_connect (priv->contact,
+ "notify::name",
+ G_CALLBACK (private_chat_contact_updated_cb),
+ chat);
+ g_signal_connect (priv->contact,
+ "notify::presences",
+ G_CALLBACK (private_chat_contact_presence_updated_cb),
+ chat);
+
+ view = GOSSIP_CHAT (chat)->view;
+
+ /* Turn off scrolling temporarily */
+ gossip_chat_view_scroll (view, FALSE);
+#if 0
+FIXME:
+ /* Add messages from last conversation */
+ log_manager = gossip_session_get_log_manager (gossip_app_get_session ());
+ messages = gossip_log_get_last_for_contact (log_manager, priv->contact);
+ num_messages = g_list_length (messages);
+
+ for (l = messages, i = 0; l; l = l->next, i++) {
+ message = l->data;
+
+ if (num_messages - i > 10) {
+ continue;
+ }
+
+ sender = gossip_message_get_sender (message);
+ if (gossip_contact_equal (priv->own_contact, sender)) {
+ gossip_chat_view_append_message_from_self (view,
+ message,
+ priv->own_contact,
+ priv->own_avatar);
+ } else {
+ gossip_chat_view_append_message_from_other (view,
+ message,
+ sender,
+ priv->other_avatar);
+ }
+ }
+
+ g_list_foreach (messages, (GFunc) g_object_unref, NULL);
+ g_list_free (messages);
+#endif
+ /* Turn back on scrolling */
+ gossip_chat_view_scroll (view, TRUE);
+
+ /* Scroll to the most recent messages, we reference the chat
+ * for the duration of the scroll func.
+ */
+ priv->scroll_idle_id = g_idle_add ((GSourceFunc)
+ private_chat_scroll_down_idle_func,
+ g_object_ref (chat));
+
+ priv->is_online = gossip_contact_is_online (priv->contact);
+}
+
+GossipPrivateChat *
+gossip_private_chat_new (GossipContact *contact)
+{
+ GossipPrivateChat *chat;
+ EmpathyTpChat *tp_chat;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ chat = g_object_new (GOSSIP_TYPE_PRIVATE_CHAT, NULL);
+ tp_chat = empathy_tp_chat_new_with_contact (contact);
+
+ private_chat_setup (chat, contact, tp_chat);
+ g_object_unref (tp_chat);
+
+ return chat;
+}
+
+GossipPrivateChat *
+gossip_private_chat_new_with_channel (GossipContact *contact,
+ TpChan *tp_chan)
+{
+ GossipPrivateChat *chat;
+ EmpathyTpChat *tp_chat;
+ McAccount *account;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+ g_return_val_if_fail (TELEPATHY_IS_CHAN (tp_chan), NULL);
+
+ account = gossip_contact_get_account (contact);
+ chat = g_object_new (GOSSIP_TYPE_PRIVATE_CHAT, NULL);
+ tp_chat = empathy_tp_chat_new (account, tp_chan);
+
+ private_chat_setup (chat, contact, tp_chat);
+ g_object_unref (tp_chat);
+
+ return chat;
+}
+
+/* Pads a pixbuf to the specified size, by centering it in a larger transparent
+ * pixbuf. Returns a new ref.
+ */
+#if 0
+FIXME:
+static GdkPixbuf *
+private_chat_pad_to_size (GdkPixbuf *pixbuf,
+ gint width,
+ gint height,
+ gint extra_padding_right)
+{
+ gint src_width, src_height;
+ GdkPixbuf *padded;
+ gint x_offset, y_offset;
+
+ src_width = gdk_pixbuf_get_width (pixbuf);
+ src_height = gdk_pixbuf_get_height (pixbuf);
+
+ x_offset = (width - src_width) / 2;
+ y_offset = (height - src_height) / 2;
+
+ padded = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (pixbuf),
+ TRUE, /* alpha */
+ gdk_pixbuf_get_bits_per_sample (pixbuf),
+ width + extra_padding_right,
+ height);
+
+ gdk_pixbuf_fill (padded, 0);
+
+ gdk_pixbuf_copy_area (pixbuf,
+ 0, /* source coords */
+ 0,
+ src_width,
+ src_height,
+ padded,
+ x_offset, /* dest coords */
+ y_offset);
+
+ return padded;
+}
+#endif
+
diff --git a/libempathy-gtk/gossip-private-chat.h b/libempathy-gtk/gossip-private-chat.h
new file mode 100644
index 000000000..48741696d
--- /dev/null
+++ b/libempathy-gtk/gossip-private-chat.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ * Geert-Jan Van den Bogaerde <geertjan@gnome.org>
+ */
+
+#ifndef __GOSSIP_PRIVATE_CHAT_H__
+#define __GOSSIP_PRIVATE_CHAT_H__
+
+#include <libempathy/gossip-contact.h>
+#include <libempathy/gossip-message.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_PRIVATE_CHAT (gossip_private_chat_get_type ())
+#define GOSSIP_PRIVATE_CHAT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_PRIVATE_CHAT, GossipPrivateChat))
+#define GOSSIP_PRIVATE_CHAT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GOSSIP_TYPE_PRIVATE_CHAT, GossipPrivateChatClass))
+#define GOSSIP_IS_PRIVATE_CHAT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_PRIVATE_CHAT))
+#define GOSSIP_IS_PRIVATE_CHAT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_PRIVATE_CHAT))
+#define GOSSIP_PRIVATE_CHAT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_PRIVATE_CHAT, GossipPrivateChatClass))
+
+typedef struct _GossipPrivateChat GossipPrivateChat;
+typedef struct _GossipPrivateChatClass GossipPrivateChatClass;
+typedef struct _GossipPrivateChatPriv GossipPrivateChatPriv;
+
+#include "gossip-chat.h"
+
+struct _GossipPrivateChat {
+ GossipChat parent;
+};
+
+struct _GossipPrivateChatClass {
+ GossipChatClass parent;
+};
+
+GType gossip_private_chat_get_type (void);
+GossipPrivateChat * gossip_private_chat_new (GossipContact *contact);
+GossipPrivateChat * gossip_private_chat_new_with_channel (GossipContact *contact,
+ TpChan *tp_chan);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_PRIVATE_CHAT_H__ */
diff --git a/libempathy-gtk/gossip-profile-chooser.c b/libempathy-gtk/gossip-profile-chooser.c
new file mode 100644
index 000000000..15f2fbb34
--- /dev/null
+++ b/libempathy-gtk/gossip-profile-chooser.c
@@ -0,0 +1,102 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Xavier Claessens <xclaesse@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+
+#include <gtk/gtk.h>
+#include <libmissioncontrol/mc-profile.h>
+
+#include <libempathy-gtk/gossip-ui-utils.h>
+
+#include "gossip-profile-chooser.h"
+
+enum {
+ COL_ICON,
+ COL_LABEL,
+ COL_PROFILE,
+ COL_COUNT
+};
+
+McProfile*
+gossip_profile_chooser_get_selected (GtkWidget *widget)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ McProfile *profile = NULL;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (widget));
+ if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (widget), &iter)) {
+ gtk_tree_model_get (model, &iter,
+ COL_PROFILE, &profile,
+ -1);
+ }
+
+ return profile;
+}
+
+GtkWidget *
+gossip_profile_chooser_new (void)
+{
+ GList *profiles, *l;
+ GtkListStore *store;
+ GtkCellRenderer *renderer;
+ GtkWidget *combo_box;
+ GtkTreeIter iter;
+
+ /* set up combo box with new store */
+ store = gtk_list_store_new (COL_COUNT,
+ GDK_TYPE_PIXBUF, /* Icon */
+ G_TYPE_STRING, /* Label */
+ MC_TYPE_PROFILE); /* Profile */
+ combo_box = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
+
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, FALSE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer,
+ "pixbuf", COL_ICON,
+ NULL);
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer,
+ "text", COL_LABEL,
+ NULL);
+
+ profiles = mc_profiles_list ();
+ for (l = profiles; l; l = l->next) {
+ McProfile *profile;
+
+ profile = l->data;
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COL_ICON, gossip_pixbuf_from_profile (profile, GTK_ICON_SIZE_BUTTON),
+ COL_LABEL, mc_profile_get_display_name (profile),
+ COL_PROFILE, profile,
+ -1);
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter);
+ }
+
+ mc_profiles_free_list (profiles);
+ g_object_unref (store);
+
+ return combo_box;
+}
+
diff --git a/libempathy-gtk/gossip-profile-chooser.h b/libempathy-gtk/gossip-profile-chooser.h
new file mode 100644
index 000000000..e608db7e1
--- /dev/null
+++ b/libempathy-gtk/gossip-profile-chooser.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#ifndef __GOSSIP_PROTOCOL_CHOOSER_H__
+#define __GOSSIP_PROTOCOL_CHOOSER_H__
+
+#include <libmissioncontrol/mc-profile.h>
+
+G_BEGIN_DECLS
+
+GtkWidget * gossip_profile_chooser_new (void);
+McProfile * gossip_profile_chooser_get_selected (GtkWidget *widget);
+
+G_END_DECLS
+#endif /* __GOSSIP_PROTOCOL_CHOOSER_H__ */
diff --git a/libempathy-gtk/gossip-spell.c b/libempathy-gtk/gossip-spell.c
new file mode 100644
index 000000000..db06e9f1d
--- /dev/null
+++ b/libempathy-gtk/gossip-spell.c
@@ -0,0 +1,457 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#ifdef HAVE_ASPELL
+#include <aspell.h>
+#endif
+
+#include <libempathy/gossip-debug.h>
+#include <libempathy/gossip-conf.h>
+
+#include "gossip-spell.h"
+#include "gossip-preferences.h"
+
+#define DEBUG_DOMAIN "Spell"
+
+#ifdef HAVE_ASPELL
+
+/* Note: We could use aspell_reset_cache (NULL); periodically if we wanted
+ * to...
+ */
+
+typedef struct {
+ AspellConfig *spell_config;
+ AspellCanHaveError *spell_possible_err;
+ AspellSpeller *spell_checker;
+} SpellLanguage;
+
+#define ISO_CODES_DATADIR ISO_CODES_PREFIX "/share/xml/iso-codes"
+#define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX "/share/locale"
+
+static GHashTable *iso_code_names = NULL;
+static GList *languages = NULL;
+static gboolean gossip_conf_notify_inited = FALSE;
+
+static void
+spell_iso_codes_parse_start_tag (GMarkupParseContext *ctx,
+ const gchar *element_name,
+ const gchar **attr_names,
+ const gchar **attr_values,
+ gpointer data,
+ GError **error)
+{
+ const gchar *ccode_longB, *ccode_longT, *ccode;
+ const gchar *lang_name;
+
+ if (!g_str_equal (element_name, "iso_639_entry") ||
+ attr_names == NULL || attr_values == NULL) {
+ return;
+ }
+
+ ccode = NULL;
+ ccode_longB = NULL;
+ ccode_longT = NULL;
+ lang_name = NULL;
+
+ while (*attr_names && *attr_values) {
+ if (g_str_equal (*attr_names, "iso_639_1_code")) {
+ if (**attr_values) {
+ ccode = *attr_values;
+ }
+ }
+ else if (g_str_equal (*attr_names, "iso_639_2B_code")) {
+ if (**attr_values) {
+ ccode_longB = *attr_values;
+ }
+ }
+ else if (g_str_equal (*attr_names, "iso_639_2T_code")) {
+ if (**attr_values) {
+ ccode_longT = *attr_values;
+ }
+ }
+ else if (g_str_equal (*attr_names, "name")) {
+ lang_name = *attr_values;
+ }
+
+ attr_names++;
+ attr_values++;
+ }
+
+ if (!lang_name) {
+ return;
+ }
+
+ if (ccode) {
+ g_hash_table_insert (iso_code_names,
+ g_strdup (ccode),
+ g_strdup (lang_name));
+ }
+
+ if (ccode_longB) {
+ g_hash_table_insert (iso_code_names,
+ g_strdup (ccode_longB),
+ g_strdup (lang_name));
+ }
+
+ if (ccode_longT) {
+ g_hash_table_insert (iso_code_names,
+ g_strdup (ccode_longT),
+ g_strdup (lang_name));
+ }
+}
+
+static void
+spell_iso_code_names_init (void)
+{
+ GError *err = NULL;
+ gchar *buf;
+ gsize buf_len;
+
+ iso_code_names = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_free);
+
+ bindtextdomain ("iso_639", ISO_CODES_LOCALESDIR);
+ bind_textdomain_codeset ("iso_639", "UTF-8");
+
+ /* FIXME: We should read this in chunks and pass to the parser. */
+ if (g_file_get_contents (ISO_CODES_DATADIR "/iso_639.xml", &buf, &buf_len, &err)) {
+ GMarkupParseContext *ctx;
+ GMarkupParser parser = {
+ spell_iso_codes_parse_start_tag,
+ NULL, NULL, NULL, NULL
+ };
+
+ ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
+ if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err)) {
+ g_warning ("Failed to parse '%s': %s",
+ ISO_CODES_DATADIR"/iso_639.xml",
+ err->message);
+ g_error_free (err);
+ }
+
+ g_markup_parse_context_free (ctx);
+ g_free (buf);
+ } else {
+ g_warning ("Failed to load '%s': %s",
+ ISO_CODES_DATADIR"/iso_639.xml", err->message);
+ g_error_free (err);
+ }
+}
+
+static void
+spell_notify_languages_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ GList *l;
+
+ gossip_debug (DEBUG_DOMAIN, "Resetting languages due to config change");
+
+ /* We just reset the languages list. */
+ for (l = languages; l; l = l->next) {
+ SpellLanguage *lang;
+
+ lang = l->data;
+
+ delete_aspell_config (lang->spell_config);
+ delete_aspell_speller (lang->spell_checker);
+
+ g_slice_free (SpellLanguage, lang);
+ }
+
+ g_list_free (languages);
+ languages = NULL;
+}
+
+static void
+spell_setup_languages (void)
+{
+ gchar *str;
+
+ if (!gossip_conf_notify_inited) {
+ gossip_conf_notify_add (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_LANGUAGES,
+ spell_notify_languages_cb, NULL);
+
+ gossip_conf_notify_inited = TRUE;
+ }
+
+ if (languages) {
+ gossip_debug (DEBUG_DOMAIN, "No languages to setup");
+ return;
+ }
+
+ if (gossip_conf_get_string (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_SPELL_CHECKER_LANGUAGES,
+ &str) && str) {
+ gchar **strv;
+ gint i;
+
+ strv = g_strsplit (str, ",", -1);
+
+ i = 0;
+ while (strv && strv[i]) {
+ SpellLanguage *lang;
+
+ gossip_debug (DEBUG_DOMAIN, "Setting up language:'%s'", strv[i]);
+
+ lang = g_slice_new0 (SpellLanguage);
+
+ lang->spell_config = new_aspell_config();
+
+ aspell_config_replace (lang->spell_config, "encoding", "utf-8");
+ aspell_config_replace (lang->spell_config, "lang", strv[i++]);
+
+ lang->spell_possible_err = new_aspell_speller (lang->spell_config);
+
+ if (aspell_error_number (lang->spell_possible_err) == 0) {
+ lang->spell_checker = to_aspell_speller (lang->spell_possible_err);
+ languages = g_list_append (languages, lang);
+ } else {
+ delete_aspell_config (lang->spell_config);
+ g_slice_free (SpellLanguage, lang);
+ }
+ }
+
+ if (strv) {
+ g_strfreev (strv);
+ }
+
+ g_free (str);
+ }
+}
+
+const char *
+gossip_spell_get_language_name (const char *code)
+{
+ const gchar *name;
+
+ g_return_val_if_fail (code != NULL, NULL);
+
+ if (!iso_code_names) {
+ spell_iso_code_names_init ();
+ }
+
+ name = g_hash_table_lookup (iso_code_names, code);
+ if (!name) {
+ return NULL;
+ }
+
+ return dgettext ("iso_639", name);
+}
+
+GList *
+gossip_spell_get_language_codes (void)
+{
+ AspellConfig *config;
+ AspellDictInfoList *dlist;
+ AspellDictInfoEnumeration *dels;
+ const AspellDictInfo *entry;
+ GList *codes = NULL;
+
+ config = new_aspell_config ();
+ dlist = get_aspell_dict_info_list (config);
+ dels = aspell_dict_info_list_elements (dlist);
+
+ while ((entry = aspell_dict_info_enumeration_next (dels)) != 0) {
+ if (g_list_find_custom (codes, entry->code, (GCompareFunc) strcmp)) {
+ continue;
+ }
+
+ codes = g_list_append (codes, g_strdup (entry->code));
+ }
+
+ delete_aspell_dict_info_enumeration (dels);
+ delete_aspell_config (config);
+
+ return codes;
+}
+
+void
+gossip_spell_free_language_codes (GList *codes)
+{
+ g_list_foreach (codes, (GFunc) g_free, NULL);
+ g_list_free (codes);
+}
+
+gboolean
+gossip_spell_check (const gchar *word)
+{
+ GList *l;
+ gint n_langs;
+ gboolean correct = FALSE;
+ gint len;
+ const gchar *p;
+ gunichar c;
+ gboolean digit;
+
+ g_return_val_if_fail (word != NULL, FALSE);
+
+ spell_setup_languages ();
+
+ if (!languages) {
+ gossip_debug (DEBUG_DOMAIN, "No languages to check against");
+ return TRUE;
+ }
+
+ /* Ignore certain cases like numbers, etc. */
+ for (p = word, digit = TRUE; *p && digit; p = g_utf8_next_char (p)) {
+ c = g_utf8_get_char (p);
+ digit = g_unichar_isdigit (c);
+ }
+
+ if (digit) {
+ /* We don't spell check digits. */
+ gossip_debug (DEBUG_DOMAIN, "Not spell checking word:'%s', it is all digits", word);
+ return TRUE;
+ }
+
+ len = strlen (word);
+ n_langs = g_list_length (languages);
+ for (l = languages; l; l = l->next) {
+ SpellLanguage *lang;
+
+ lang = l->data;
+
+ correct = aspell_speller_check (lang->spell_checker, word, len);
+ if (n_langs > 1 && correct) {
+ break;
+ }
+ }
+
+ return correct;
+}
+
+GList *
+gossip_spell_get_suggestions (const gchar *word)
+{
+ GList *l1;
+ GList *l2 = NULL;
+ const AspellWordList *suggestions;
+ AspellStringEnumeration *elements;
+ const char *next;
+ gint len;
+
+ g_return_val_if_fail (word != NULL, NULL);
+
+ spell_setup_languages ();
+
+ len = strlen (word);
+
+ for (l1 = languages; l1; l1 = l1->next) {
+ SpellLanguage *lang;
+
+ lang = l1->data;
+
+ suggestions = aspell_speller_suggest (lang->spell_checker,
+ word, len);
+
+ elements = aspell_word_list_elements (suggestions);
+
+ while ((next = aspell_string_enumeration_next (elements))) {
+ l2 = g_list_append (l2, g_strdup (next));
+ }
+
+ delete_aspell_string_enumeration (elements);
+ }
+
+ return l2;
+}
+
+gboolean
+gossip_spell_supported (void)
+{
+ if (g_getenv ("GOSSIP_SPELL_DISABLED")) {
+ gossip_debug (DEBUG_DOMAIN, "GOSSIP_SPELL_DISABLE env variable defined");
+ return FALSE;
+ }
+
+ gossip_debug (DEBUG_DOMAIN, "Support enabled");
+
+ return TRUE;
+}
+
+#else /* not HAVE_ASPELL */
+
+gboolean
+gossip_spell_supported (void)
+{
+ gossip_debug (DEBUG_DOMAIN, "Support disabled");
+
+ return FALSE;
+}
+
+GList *
+gossip_spell_get_suggestions (const gchar *word)
+{
+ gossip_debug (DEBUG_DOMAIN, "Support disabled, could not get suggestions");
+
+ return NULL;
+}
+
+gboolean
+gossip_spell_check (const gchar *word)
+{
+ gossip_debug (DEBUG_DOMAIN, "Support disabled, could not check spelling");
+
+ return TRUE;
+}
+
+const char *
+gossip_spell_get_language_name (const char *lang)
+{
+ gossip_debug (DEBUG_DOMAIN, "Support disabled, could not get language name");
+
+ return NULL;
+}
+
+GList *
+gossip_spell_get_language_codes (void)
+{
+ gossip_debug (DEBUG_DOMAIN, "Support disabled, could not get language codes");
+
+ return NULL;
+}
+
+void
+gossip_spell_free_language_codes (GList *codes)
+{
+}
+
+#endif /* HAVE_ASPELL */
+
+
+void
+gossip_spell_free_suggestions (GList *suggestions)
+{
+ g_list_foreach (suggestions, (GFunc) g_free, NULL);
+ g_list_free (suggestions);
+}
+
diff --git a/libempathy-gtk/gossip-spell.h b/libempathy-gtk/gossip-spell.h
new file mode 100644
index 000000000..f2d841b3a
--- /dev/null
+++ b/libempathy-gtk/gossip-spell.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2004-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Martyn Russell <martyn@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ */
+
+#ifndef __GOSSIP_SPELL_H__
+#define __GOSSIP_SPELL_H__
+
+G_BEGIN_DECLS
+
+gboolean gossip_spell_supported (void);
+const gchar *gossip_spell_get_language_name (const gchar *code);
+GList *gossip_spell_get_language_codes (void);
+void gossip_spell_free_language_codes (GList *codes);
+gboolean gossip_spell_check (const gchar *word);
+GList * gossip_spell_get_suggestions (const gchar *word);
+void gossip_spell_free_suggestions (GList *suggestions);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_SPELL_H__ */
diff --git a/libempathy-gtk/gossip-stock.c b/libempathy-gtk/gossip-stock.c
new file mode 100644
index 000000000..f43949ee4
--- /dev/null
+++ b/libempathy-gtk/gossip-stock.c
@@ -0,0 +1,105 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#include <libempathy/gossip-paths.h>
+
+#include "gossip-stock.h"
+
+static GtkIconFactory *icon_factory = NULL;
+static GtkWidget *main_widget = NULL;
+
+static GtkStockItem stock_items[] = {
+ { GOSSIP_STOCK_OFFLINE, NULL },
+ { GOSSIP_STOCK_AVAILABLE, NULL },
+ { GOSSIP_STOCK_BUSY, NULL },
+ { GOSSIP_STOCK_AWAY, NULL },
+ { GOSSIP_STOCK_EXT_AWAY, NULL },
+ { GOSSIP_STOCK_PENDING, NULL },
+ { GOSSIP_STOCK_MESSAGE, NULL },
+ { GOSSIP_STOCK_TYPING, NULL },
+ { GOSSIP_STOCK_CONTACT_INFORMATION, NULL },
+ { GOSSIP_STOCK_GROUP_MESSAGE, NULL }
+};
+
+void
+gossip_stock_init (GtkWidget *widget)
+{
+ GtkIconSet *icon_set;
+ gint i;
+
+ g_assert (icon_factory == NULL);
+
+ main_widget = g_object_ref (widget);
+
+ gtk_stock_add (stock_items, G_N_ELEMENTS (stock_items));
+
+ icon_factory = gtk_icon_factory_new ();
+ gtk_icon_factory_add_default (icon_factory);
+ g_object_unref (icon_factory);
+
+ for (i = 0; i < G_N_ELEMENTS (stock_items); i++) {
+ gchar *path, *filename;
+ GdkPixbuf *pixbuf;
+
+ filename = g_strdup_printf ("%s.png", stock_items[i].stock_id);
+ path = gossip_paths_get_image_path (filename);
+ pixbuf = gdk_pixbuf_new_from_file (path, NULL);
+ g_free (path);
+ g_free (filename);
+
+ icon_set = gtk_icon_set_new_from_pixbuf (pixbuf);
+
+ gtk_icon_factory_add (icon_factory,
+ stock_items[i].stock_id,
+ icon_set);
+
+ gtk_icon_set_unref (icon_set);
+
+ g_object_unref (pixbuf);
+ }
+}
+
+void
+gossip_stock_finalize (void)
+{
+ g_assert (icon_factory != NULL);
+
+ gtk_icon_factory_remove_default (icon_factory);
+ g_object_unref (main_widget);
+
+ main_widget = NULL;
+ icon_factory = NULL;
+}
+
+GdkPixbuf *
+gossip_stock_render (const gchar *stock,
+ GtkIconSize size)
+{
+ return gtk_widget_render_icon (main_widget, stock, size, NULL);
+}
+
diff --git a/libempathy-gtk/gossip-stock.h b/libempathy-gtk/gossip-stock.h
new file mode 100644
index 000000000..412aceef1
--- /dev/null
+++ b/libempathy-gtk/gossip-stock.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2003-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ */
+
+#ifndef __GOSSIP_STOCK_H__
+#define __GOSSIP_STOCK_H__
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GOSSIP_STOCK_OFFLINE "gossip-offline"
+#define GOSSIP_STOCK_AVAILABLE "gossip-available"
+#define GOSSIP_STOCK_BUSY "gossip-busy"
+#define GOSSIP_STOCK_AWAY "gossip-away"
+#define GOSSIP_STOCK_EXT_AWAY "gossip-extended-away"
+#define GOSSIP_STOCK_PENDING "gossip-pending"
+
+#define GOSSIP_STOCK_MESSAGE "gossip-message"
+#define GOSSIP_STOCK_TYPING "gossip-typing"
+
+
+#define GOSSIP_STOCK_CONTACT_INFORMATION "vcard_16"
+
+#define GOSSIP_STOCK_AIM "gossip-aim"
+#define GOSSIP_STOCK_ICQ "gossip-icq"
+#define GOSSIP_STOCK_MSN "gossip-msn"
+#define GOSSIP_STOCK_YAHOO "gossip-yahoo"
+
+#define GOSSIP_STOCK_GROUP_MESSAGE "gossip-group-message"
+
+void gossip_stock_init (GtkWidget *widget);
+void gossip_stock_finalize (void);
+GdkPixbuf * gossip_stock_render (const gchar *stock,
+ GtkIconSize size);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_STOCK_ICONS_H__ */
diff --git a/libempathy-gtk/gossip-theme-manager.c b/libempathy-gtk/gossip-theme-manager.c
new file mode 100644
index 000000000..6d5905e5a
--- /dev/null
+++ b/libempathy-gtk/gossip-theme-manager.c
@@ -0,0 +1,1045 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Richard Hult <richard@imendio.com>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include <libempathy/gossip-conf.h>
+#include <libempathy/gossip-utils.h>
+
+#include "gossip-chat-view.h"
+#include "gossip-preferences.h"
+#include "gossip-theme-manager.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_THEME_MANAGER, GossipThemeManagerPriv))
+
+typedef struct {
+ gchar *name;
+ guint name_notify_id;
+ guint room_notify_id;
+
+ gboolean show_avatars;
+ guint show_avatars_notify_id;
+
+ gboolean irc_style;
+} GossipThemeManagerPriv;
+
+static void theme_manager_finalize (GObject *object);
+static void theme_manager_notify_name_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void theme_manager_notify_room_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void theme_manager_notify_show_avatars_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data);
+static void theme_manager_ensure_tag_by_name (GtkTextBuffer *buffer,
+ const gchar *name);
+static gboolean theme_manager_ensure_theme_exists (const gchar *name);
+static GtkTextTag *theme_manager_init_tag_by_name (GtkTextTagTable *table,
+ const gchar *name);
+static void theme_manager_add_tag (GtkTextTagTable *table,
+ GtkTextTag *tag);
+static void theme_manager_fixup_tag_table (GossipThemeManager *theme_manager,
+ GossipChatView *view);
+static void theme_manager_apply_theme_classic (GossipThemeManager *manager,
+ GossipChatView *view);
+static void theme_manager_apply_theme_clean (GossipThemeManager *manager,
+ GossipChatView *view);
+static void theme_manager_apply_theme_blue (GossipThemeManager *manager,
+ GossipChatView *view);
+static void theme_manager_apply_theme (GossipThemeManager *manager,
+ GossipChatView *view,
+ const gchar *name);
+
+enum {
+ THEME_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static const gchar *themes[] = {
+ "classic", N_("Classic"),
+ "simple", N_("Simple"),
+ "clean", N_("Clean"),
+ "blue", N_("Blue"),
+ NULL
+};
+
+G_DEFINE_TYPE (GossipThemeManager, gossip_theme_manager, G_TYPE_OBJECT);
+
+static void
+gossip_theme_manager_class_init (GossipThemeManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ signals[THEME_CHANGED] =
+ g_signal_new ("theme-changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_type_class_add_private (object_class, sizeof (GossipThemeManagerPriv));
+
+ object_class->finalize = theme_manager_finalize;
+}
+
+static void
+gossip_theme_manager_init (GossipThemeManager *manager)
+{
+ GossipThemeManagerPriv *priv;
+
+ priv = GET_PRIV (manager);
+
+ priv->name_notify_id =
+ gossip_conf_notify_add (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_THEME,
+ theme_manager_notify_name_cb,
+ manager);
+
+ priv->room_notify_id =
+ gossip_conf_notify_add (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_THEME_CHAT_ROOM,
+ theme_manager_notify_room_cb,
+ manager);
+
+ gossip_conf_get_string (gossip_conf_get (),
+ GOSSIP_PREFS_CHAT_THEME,
+ &priv->name);
+
+ /* Unused right now, but will be used soon. */
+ priv->show_avatars_notify_id =
+ gossip_conf_notify_add (gossip_conf_get (),
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ theme_manager_notify_show_avatars_cb,
+ manager);
+
+ gossip_conf_get_bool (gossip_conf_get (),
+ GOSSIP_PREFS_UI_SHOW_AVATARS,
+ &priv->show_avatars);
+}
+
+static void
+theme_manager_finalize (GObject *object)
+{
+ GossipThemeManagerPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ gossip_conf_notify_remove (gossip_conf_get (), priv->name_notify_id);
+ gossip_conf_notify_remove (gossip_conf_get (), priv->room_notify_id);
+ gossip_conf_notify_remove (gossip_conf_get (), priv->show_avatars_notify_id);
+
+ g_free (priv->name);
+
+ G_OBJECT_CLASS (gossip_theme_manager_parent_class)->finalize (object);
+}
+
+static void
+theme_manager_notify_name_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ GossipThemeManager *manager;
+ GossipThemeManagerPriv *priv;
+ gchar *name;
+
+ manager = user_data;
+ priv = GET_PRIV (manager);
+
+ g_free (priv->name);
+
+ name = NULL;
+ if (!gossip_conf_get_string (conf, key, &name) ||
+ name == NULL || name[0] == 0) {
+ priv->name = g_strdup ("classic");
+ g_free (name);
+ } else {
+ priv->name = name;
+ }
+
+ g_signal_emit (manager, signals[THEME_CHANGED], 0, NULL);
+}
+
+static void
+theme_manager_notify_room_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ g_signal_emit (user_data, signals[THEME_CHANGED], 0, NULL);
+}
+
+static void
+theme_manager_notify_show_avatars_cb (GossipConf *conf,
+ const gchar *key,
+ gpointer user_data)
+{
+ GossipThemeManager *manager;
+ GossipThemeManagerPriv *priv;
+ gboolean value;
+
+ manager = user_data;
+ priv = GET_PRIV (manager);
+
+ if (!gossip_conf_get_bool (conf, key, &value)) {
+ priv->show_avatars = FALSE;
+ } else {
+ priv->show_avatars = value;
+ }
+}
+
+static void
+theme_manager_ensure_tag_by_name (GtkTextBuffer *buffer,
+ const gchar *name)
+{
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+
+ table = gtk_text_buffer_get_tag_table (buffer);
+ tag = gtk_text_tag_table_lookup (table, name);
+
+ if (!tag) {
+ gtk_text_buffer_create_tag (buffer,
+ name,
+ NULL);
+ }
+}
+
+static gboolean
+theme_manager_ensure_theme_exists (const gchar *name)
+{
+ gint i;
+
+ if (G_STR_EMPTY (name)) {
+ return FALSE;
+ }
+
+ for (i = 0; themes[i]; i += 2) {
+ if (strcmp (themes[i], name) == 0) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static GtkTextTag *
+theme_manager_init_tag_by_name (GtkTextTagTable *table,
+ const gchar *name)
+{
+ GtkTextTag *tag;
+
+ tag = gtk_text_tag_table_lookup (table, name);
+
+ if (!tag) {
+ return gtk_text_tag_new (name);
+ }
+
+ /* Clear the old values so that we don't affect the new theme. */
+ g_object_set (tag,
+ "background-set", FALSE,
+ "foreground-set", FALSE,
+ "invisible-set", FALSE,
+ "justification-set", FALSE,
+ "paragraph-background-set", FALSE,
+ "pixels-above-lines-set", FALSE,
+ "pixels-below-lines-set", FALSE,
+ "rise-set", FALSE,
+ "scale-set", FALSE,
+ "size-set", FALSE,
+ "style-set", FALSE,
+ "weight-set", FALSE,
+ NULL);
+
+ return tag;
+}
+
+static void
+theme_manager_add_tag (GtkTextTagTable *table,
+ GtkTextTag *tag)
+{
+ gchar *name;
+ GtkTextTag *check_tag;
+
+ g_object_get (tag, "name", &name, NULL);
+ check_tag = gtk_text_tag_table_lookup (table, name);
+ g_free (name);
+ if (check_tag) {
+ return;
+ }
+
+ gtk_text_tag_table_add (table, tag);
+
+ g_object_unref (tag);
+}
+
+static void
+theme_manager_fixup_tag_table (GossipThemeManager *theme_manager,
+ GossipChatView *view)
+{
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ /* "Fancy" style tags. */
+ theme_manager_ensure_tag_by_name (buffer, "fancy-header-self");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-header-self-avatar");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-avatar-self");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-line-top-self");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-line-bottom-self");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-body-self");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-action-self");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-highlight-self");
+
+ theme_manager_ensure_tag_by_name (buffer, "fancy-header-other");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-header-other-avatar");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-avatar-other");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-line-top-other");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-line-bottom-other");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-body-other");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-action-other");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-highlight-other");
+
+ theme_manager_ensure_tag_by_name (buffer, "fancy-spacing");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-time");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-event");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-invite");
+ theme_manager_ensure_tag_by_name (buffer, "fancy-link");
+
+ /* IRC style tags. */
+ theme_manager_ensure_tag_by_name (buffer, "irc-nick-self");
+ theme_manager_ensure_tag_by_name (buffer, "irc-body-self");
+ theme_manager_ensure_tag_by_name (buffer, "irc-action-self");
+
+ theme_manager_ensure_tag_by_name (buffer, "irc-nick-other");
+ theme_manager_ensure_tag_by_name (buffer, "irc-body-other");
+ theme_manager_ensure_tag_by_name (buffer, "irc-action-other");
+
+ theme_manager_ensure_tag_by_name (buffer, "irc-nick-highlight");
+ theme_manager_ensure_tag_by_name (buffer, "irc-spacing");
+ theme_manager_ensure_tag_by_name (buffer, "irc-time");
+ theme_manager_ensure_tag_by_name (buffer, "irc-event");
+ theme_manager_ensure_tag_by_name (buffer, "irc-invite");
+ theme_manager_ensure_tag_by_name (buffer, "irc-link");
+}
+
+static void
+theme_manager_apply_theme_classic (GossipThemeManager *manager,
+ GossipChatView *view)
+{
+ GossipThemeManagerPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+
+ priv = GET_PRIV (manager);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ priv->irc_style = TRUE;
+
+ tag = theme_manager_init_tag_by_name (table, "irc-spacing");
+ g_object_set (tag,
+ "size", 2000,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-nick-self");
+ g_object_set (tag,
+ "foreground", "sea green",
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-body-self");
+ g_object_set (tag,
+ /* To get the default theme color: */
+ "foreground-set", FALSE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-action-self");
+ g_object_set (tag,
+ "foreground", "brown4",
+ "style", PANGO_STYLE_ITALIC,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-nick-highlight");
+ g_object_set (tag,
+ "foreground", "indian red",
+ "weight", PANGO_WEIGHT_BOLD,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-nick-other");
+ g_object_set (tag,
+ "foreground", "skyblue4",
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-body-other");
+ g_object_set (tag,
+ /* To get the default theme color: */
+ "foreground-set", FALSE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-action-other");
+ g_object_set (tag,
+ "foreground", "brown4",
+ "style", PANGO_STYLE_ITALIC,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-time");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ "justification", GTK_JUSTIFY_CENTER,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-event");
+ g_object_set (tag,
+ "foreground", "PeachPuff4",
+ "justification", GTK_JUSTIFY_LEFT,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-invite");
+ g_object_set (tag,
+ "foreground", "sienna",
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "irc-link");
+ g_object_set (tag,
+ "foreground", "steelblue",
+ "underline", PANGO_UNDERLINE_SINGLE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+}
+
+static void
+theme_manager_apply_theme_simple (GossipThemeManager *manager,
+ GossipChatView *view)
+{
+ GossipThemeManagerPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+ GtkWidget *widget;
+ GtkStyle *style;
+
+ priv = GET_PRIV (manager);
+
+ widget = gtk_entry_new ();
+ style = gtk_widget_get_style (widget);
+ gtk_widget_destroy (widget);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ priv->irc_style = FALSE;
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-spacing");
+ g_object_set (tag,
+ "size", 3000,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-self");
+ g_object_set (tag,
+ "weight", PANGO_WEIGHT_BOLD,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-self-avatar");
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-avatar-self");
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-top-self");
+ g_object_set (tag,
+ "size", 6 * PANGO_SCALE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-bottom-self");
+ g_object_set (tag,
+ "size", 1,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-body-self");
+ g_object_set (tag,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-action-self");
+ g_object_set (tag,
+ "foreground-gdk", &style->base[GTK_STATE_SELECTED],
+ "style", PANGO_STYLE_ITALIC,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-highlight-self");
+ g_object_set (tag,
+ "weight", PANGO_WEIGHT_BOLD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-other");
+ g_object_set (tag,
+ "weight", PANGO_WEIGHT_BOLD,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-other-avatar");
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-avatar-other");
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-top-other");
+ g_object_set (tag,
+ "size", 6 * PANGO_SCALE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-bottom-other");
+ g_object_set (tag,
+ "size", 1,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-body-other");
+ g_object_set (tag,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-action-other");
+ g_object_set (tag,
+ "foreground-gdk", &style->base[GTK_STATE_SELECTED],
+ "style", PANGO_STYLE_ITALIC,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-highlight-other");
+ g_object_set (tag,
+ "weight", PANGO_WEIGHT_BOLD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-time");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ "justification", GTK_JUSTIFY_CENTER,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-event");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ "justification", GTK_JUSTIFY_LEFT,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-invite");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-link");
+ g_object_set (tag,
+ "foreground-gdk", &style->base[GTK_STATE_SELECTED],
+ "underline", PANGO_UNDERLINE_SINGLE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+}
+
+static void
+theme_manager_apply_theme_clean (GossipThemeManager *manager,
+ GossipChatView *view)
+{
+ GossipThemeManagerPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+
+ priv = GET_PRIV (manager);
+
+ /* Inherit the simple theme. */
+ theme_manager_apply_theme_simple (manager, view);
+
+#define ELEGANT_HEAD "#efefdf"
+#define ELEGANT_LINE "#e3e3d3"
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-spacing");
+ g_object_set (tag,
+ "size", PANGO_SCALE * 10,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-self");
+ g_object_set (tag,
+ "foreground", "black",
+ "weight", PANGO_WEIGHT_BOLD,
+ "paragraph-background", ELEGANT_HEAD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-avatar-self");
+ g_object_set (tag,
+ "paragraph-background", ELEGANT_HEAD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-top-self");
+ g_object_set (tag,
+ "size", 1 * PANGO_SCALE,
+ "paragraph-background", ELEGANT_LINE,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-bottom-self");
+ g_object_set (tag,
+ "size", 1 * PANGO_SCALE,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-action-self");
+ g_object_set (tag,
+ "foreground", "brown4",
+ "style", PANGO_STYLE_ITALIC,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-highlight-self");
+ g_object_set (tag,
+ "foreground", "black",
+ "weight", PANGO_WEIGHT_BOLD,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-other");
+ g_object_set (tag,
+ "foreground", "black",
+ "weight", PANGO_WEIGHT_BOLD,
+ "paragraph-background", ELEGANT_HEAD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-avatar-other");
+ g_object_set (tag,
+ "paragraph-background", ELEGANT_HEAD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-top-other");
+ g_object_set (tag,
+ "size", 1 * PANGO_SCALE,
+ "paragraph-background", ELEGANT_LINE,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-bottom-other");
+ g_object_set (tag,
+ "size", 1 * PANGO_SCALE,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-action-other");
+ g_object_set (tag,
+ "foreground", "brown4",
+ "style", PANGO_STYLE_ITALIC,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-time");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ "justification", GTK_JUSTIFY_CENTER,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-event");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ "justification", GTK_JUSTIFY_LEFT,
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-invite");
+ g_object_set (tag,
+ "foreground", "sienna",
+ NULL);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-link");
+ g_object_set (tag,
+ "foreground", "#49789e",
+ "underline", PANGO_UNDERLINE_SINGLE,
+ NULL);
+}
+
+static void
+theme_manager_apply_theme_blue (GossipThemeManager *manager,
+ GossipChatView *view)
+{
+ GossipThemeManagerPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextTag *tag;
+
+ priv = GET_PRIV (manager);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ priv->irc_style = FALSE;
+
+#define BLUE_BODY_SELF "#dcdcdc"
+#define BLUE_HEAD_SELF "#b9b9b9"
+#define BLUE_LINE_SELF "#aeaeae"
+
+#define BLUE_BODY_OTHER "#adbdc8"
+#define BLUE_HEAD_OTHER "#88a2b4"
+#define BLUE_LINE_OTHER "#7f96a4"
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-spacing");
+ g_object_set (tag,
+ "size", 3000,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-self");
+ g_object_set (tag,
+ "foreground", "black",
+ "paragraph-background", BLUE_HEAD_SELF,
+ "weight", PANGO_WEIGHT_BOLD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-self-avatar");
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-avatar-self");
+ g_object_set (tag,
+ "paragraph-background", BLUE_HEAD_SELF,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-top-self");
+ g_object_set (tag,
+ "size", 1,
+ "paragraph-background", BLUE_LINE_SELF,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-bottom-self");
+ g_object_set (tag,
+ "size", 1,
+ "paragraph-background", BLUE_LINE_SELF,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-body-self");
+ g_object_set (tag,
+ "foreground", "black",
+ "paragraph-background", BLUE_BODY_SELF,
+ "pixels-above-lines", 4,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-action-self");
+ g_object_set (tag,
+ "foreground", "brown4",
+ "style", PANGO_STYLE_ITALIC,
+ "paragraph-background", BLUE_BODY_SELF,
+ "pixels-above-lines", 4,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-highlight-self");
+ g_object_set (tag,
+ "foreground", "black",
+ "weight", PANGO_WEIGHT_BOLD,
+ "paragraph-background", BLUE_BODY_SELF,
+ "pixels-above-lines", 4,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-other");
+ g_object_set (tag,
+ "foreground", "black",
+ "paragraph-background", BLUE_HEAD_OTHER,
+ "weight", PANGO_WEIGHT_BOLD,
+ "pixels-above-lines", 2,
+ "pixels-below-lines", 2,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-header-other-avatar");
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-avatar-other");
+ g_object_set (tag,
+ "paragraph-background", BLUE_HEAD_OTHER,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-top-other");
+ g_object_set (tag,
+ "size", 1,
+ "paragraph-background", BLUE_LINE_OTHER,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-line-bottom-other");
+ g_object_set (tag,
+ "size", 1,
+ "paragraph-background", BLUE_LINE_OTHER,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-body-other");
+ g_object_set (tag,
+ "foreground", "black",
+ "paragraph-background", BLUE_BODY_OTHER,
+ "pixels-above-lines", 4,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-action-other");
+ g_object_set (tag,
+ "foreground", "brown4",
+ "style", PANGO_STYLE_ITALIC,
+ "paragraph-background", BLUE_BODY_OTHER,
+ "pixels-above-lines", 4,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-highlight-other");
+ g_object_set (tag,
+ "foreground", "black",
+ "weight", PANGO_WEIGHT_BOLD,
+ "paragraph-background", BLUE_BODY_OTHER,
+ "pixels-above-lines", 4,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-time");
+ g_object_set (tag,
+ "foreground", "darkgrey",
+ "justification", GTK_JUSTIFY_CENTER,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-event");
+ g_object_set (tag,
+ "foreground", BLUE_LINE_OTHER,
+ "justification", GTK_JUSTIFY_LEFT,
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-invite");
+ g_object_set (tag,
+ "foreground", "sienna",
+ NULL);
+ theme_manager_add_tag (table, tag);
+
+ tag = theme_manager_init_tag_by_name (table, "fancy-link");
+ g_object_set (tag,
+ "foreground", "#49789e",
+ "underline", PANGO_UNDERLINE_SINGLE,
+ NULL);
+ theme_manager_add_tag (table, tag);
+}
+
+static void
+theme_manager_apply_theme (GossipThemeManager *manager,
+ GossipChatView *view,
+ const gchar *name)
+{
+ GossipThemeManagerPriv *priv;
+ gint margin;
+
+ priv = GET_PRIV (manager);
+
+ /* Make sure all tags are present. Note: not useful now but when we have
+ * user defined theme it will be.
+ */
+ theme_manager_fixup_tag_table (manager, view);
+
+ if (theme_manager_ensure_theme_exists (name)) {
+ if (strcmp (name, "clean") == 0) {
+ theme_manager_apply_theme_clean (manager, view);
+ margin = 3;
+ }
+ else if (strcmp (name, "simple") == 0) {
+ theme_manager_apply_theme_simple (manager, view);
+ margin = 3;
+ }
+ else if (strcmp (name, "blue") == 0) {
+ theme_manager_apply_theme_blue (manager, view);
+ margin = 0;
+ } else {
+ theme_manager_apply_theme_classic (manager, view);
+ margin = 3;
+ }
+ } else {
+ theme_manager_apply_theme_classic (manager, view);
+ margin = 3;
+ }
+
+ gossip_chat_view_set_margin (view, margin);
+ gossip_chat_view_set_irc_style (view, priv->irc_style);
+}
+
+GossipThemeManager *
+gossip_theme_manager_get (void)
+{
+ static GossipThemeManager *manager = NULL;
+
+ if (!manager) {
+ manager = g_object_new (GOSSIP_TYPE_THEME_MANAGER, NULL);
+ }
+
+ return manager;
+}
+
+const gchar **
+gossip_theme_manager_get_themes (void)
+{
+ return themes;
+}
+
+void
+gossip_theme_manager_apply (GossipThemeManager *manager,
+ GossipChatView *view,
+ const gchar *name)
+{
+ GossipThemeManagerPriv *priv;
+
+ priv = GET_PRIV (manager);
+
+ theme_manager_apply_theme (manager, view, name);
+}
+
+void
+gossip_theme_manager_apply_saved (GossipThemeManager *manager,
+ GossipChatView *view)
+{
+ GossipThemeManagerPriv *priv;
+
+ priv = GET_PRIV (manager);
+
+ theme_manager_apply_theme (manager, view, priv->name);
+}
+
+/* FIXME: A bit ugly. We should probably change the scheme so that instead of
+ * the manager signalling, views are registered and applied to automatically.
+ */
+void
+gossip_theme_manager_update_show_avatars (GossipThemeManager *manager,
+ GossipChatView *view,
+ gboolean show)
+{
+ GossipThemeManagerPriv *priv;
+ GtkTextBuffer *buffer;
+ GtkTextTagTable *table;
+ GtkTextTag *tag_text_self, *tag_text_other;
+ GtkTextTag *tag_image_self, *tag_image_other;
+
+ priv = GET_PRIV (manager);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ table = gtk_text_buffer_get_tag_table (buffer);
+
+ tag_text_self = gtk_text_tag_table_lookup (table, "fancy-header-self-avatar");
+ tag_text_other = gtk_text_tag_table_lookup (table, "fancy-header-other-avatar");
+
+ tag_image_self = gtk_text_tag_table_lookup (table, "fancy-avatar-self");
+ tag_image_other = gtk_text_tag_table_lookup (table, "fancy-avatar-other");
+
+ if (!show) {
+ g_object_set (tag_text_self,
+ "rise", 0,
+ NULL);
+ g_object_set (tag_text_other,
+ "rise", 0,
+ NULL);
+ g_object_set (tag_image_self,
+ "invisible", TRUE,
+ NULL);
+ g_object_set (tag_image_other,
+ "invisible", TRUE,
+ NULL);
+ } else {
+ GtkTextAttributes *attrs;
+ gint size;
+ gint rise;
+
+ attrs = gtk_text_view_get_default_attributes (GTK_TEXT_VIEW (view));
+ size = pango_font_description_get_size (attrs->font);
+ rise = MAX (0, (32 * PANGO_SCALE - size) / 2.0);
+
+ g_object_set (tag_text_self,
+ "rise", rise,
+ NULL);
+ g_object_set (tag_text_other,
+ "rise", rise,
+ NULL);
+ g_object_set (tag_image_self,
+ "invisible", FALSE,
+ NULL);
+ g_object_set (tag_image_other,
+ "invisible", FALSE,
+ NULL);
+
+ gtk_text_attributes_unref (attrs);
+ }
+}
+
diff --git a/libempathy-gtk/gossip-theme-manager.h b/libempathy-gtk/gossip-theme-manager.h
new file mode 100644
index 000000000..bb7e3cf70
--- /dev/null
+++ b/libempathy-gtk/gossip-theme-manager.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Richard Hult <richard@imendio.com>
+ */
+
+#ifndef __GOSSIP_THEME_MANAGER_H__
+#define __GOSSIP_THEME_MANAGER_H__
+
+#include <glib-object.h>
+
+#include "gossip-chat-view.h"
+
+G_BEGIN_DECLS
+
+#define GOSSIP_TYPE_THEME_MANAGER (gossip_theme_manager_get_type ())
+#define GOSSIP_THEME_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GOSSIP_TYPE_THEME_MANAGER, GossipThemeManager))
+#define GOSSIP_THEME_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GOSSIP_TYPE_THEME_MANAGER, GossipThemeManagerClass))
+#define GOSSIP_IS_THEME_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GOSSIP_TYPE_THEME_MANAGER))
+#define GOSSIP_IS_THEME_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GOSSIP_TYPE_THEME_MANAGER))
+#define GOSSIP_THEME_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GOSSIP_TYPE_THEME_MANAGER, GossipThemeManagerClass))
+
+typedef struct _GossipThemeManager GossipThemeManager;
+typedef struct _GossipThemeManagerClass GossipThemeManagerClass;
+
+struct _GossipThemeManager {
+ GObject parent;
+};
+
+struct _GossipThemeManagerClass {
+ GObjectClass parent_class;
+};
+
+GType gossip_theme_manager_get_type (void) G_GNUC_CONST;
+GossipThemeManager *gossip_theme_manager_get (void);
+const gchar ** gossip_theme_manager_get_themes (void);
+void gossip_theme_manager_apply (GossipThemeManager *manager,
+ GossipChatView *view,
+ const gchar *theme);
+void gossip_theme_manager_apply_saved (GossipThemeManager *manager,
+ GossipChatView *view);
+void gossip_theme_manager_update_show_avatars (GossipThemeManager *manager,
+ GossipChatView *view,
+ gboolean show);
+
+G_END_DECLS
+
+#endif /* __GOSSIP_THEME_MANAGER_H__ */
diff --git a/libempathy-gtk/gossip-ui-utils.c b/libempathy-gtk/gossip-ui-utils.c
new file mode 100644
index 000000000..e902012f1
--- /dev/null
+++ b/libempathy-gtk/gossip-ui-utils.c
@@ -0,0 +1,1360 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ *
+ * Part of this file is copied from GtkSourceView (gtksourceiter.c):
+ * Paolo Maggi
+ * Jeroen Zwartepoorte
+ */
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <libgnome/libgnome.h>
+
+#include <libmissioncontrol/mc-profile.h>
+
+#include <libempathy/gossip-paths.h>
+#include <libempathy/gossip-debug.h>
+
+#include "gossip-ui-utils.h"
+#include "gossip-stock.h"
+
+struct SizeData {
+ gint width;
+ gint height;
+ gboolean preserve_aspect_ratio;
+};
+
+static GladeXML *
+get_glade_file (const gchar *filename,
+ const gchar *root,
+ const gchar *domain,
+ const gchar *first_required_widget,
+ va_list args)
+{
+ gchar *path;
+ GladeXML *gui;
+ const char *name;
+ GtkWidget **widget_ptr;
+
+ path = gossip_paths_get_glade_path (filename);
+ gui = glade_xml_new (path, root, domain);
+ g_free (path);
+
+ if (!gui) {
+ g_warning ("Couldn't find necessary glade file '%s'", filename);
+ return NULL;
+ }
+
+ for (name = first_required_widget; name; name = va_arg (args, char *)) {
+ widget_ptr = va_arg (args, void *);
+
+ *widget_ptr = glade_xml_get_widget (gui, name);
+
+ if (!*widget_ptr) {
+ g_warning ("Glade file '%s' is missing widget '%s'.",
+ filename, name);
+ continue;
+ }
+ }
+
+ return gui;
+}
+
+void
+gossip_glade_get_file_simple (const gchar *filename,
+ const gchar *root,
+ const gchar *domain,
+ const gchar *first_required_widget, ...)
+{
+ va_list args;
+ GladeXML *gui;
+
+ va_start (args, first_required_widget);
+
+ gui = get_glade_file (filename,
+ root,
+ domain,
+ first_required_widget,
+ args);
+
+ va_end (args);
+
+ if (!gui) {
+ return;
+ }
+
+ g_object_unref (gui);
+}
+
+GladeXML *
+gossip_glade_get_file (const gchar *filename,
+ const gchar *root,
+ const gchar *domain,
+ const gchar *first_required_widget, ...)
+{
+ va_list args;
+ GladeXML *gui;
+
+ va_start (args, first_required_widget);
+
+ gui = get_glade_file (filename,
+ root,
+ domain,
+ first_required_widget,
+ args);
+
+ va_end (args);
+
+ if (!gui) {
+ return NULL;
+ }
+
+ return gui;
+}
+
+void
+gossip_glade_connect (GladeXML *gui,
+ gpointer user_data,
+ gchar *first_widget, ...)
+{
+ va_list args;
+ const gchar *name;
+ const gchar *signal;
+ GtkWidget *widget;
+ gpointer *callback;
+
+ va_start (args, first_widget);
+
+ for (name = first_widget; name; name = va_arg (args, char *)) {
+ signal = va_arg (args, void *);
+ callback = va_arg (args, void *);
+
+ widget = glade_xml_get_widget (gui, name);
+ if (!widget) {
+ g_warning ("Glade file is missing widget '%s', aborting",
+ name);
+ continue;
+ }
+
+ g_signal_connect (widget,
+ signal,
+ G_CALLBACK (callback),
+ user_data);
+ }
+
+ va_end (args);
+}
+
+void
+gossip_glade_setup_size_group (GladeXML *gui,
+ GtkSizeGroupMode mode,
+ gchar *first_widget, ...)
+{
+ va_list args;
+ GtkWidget *widget;
+ GtkSizeGroup *size_group;
+ const gchar *name;
+
+ va_start (args, first_widget);
+
+ size_group = gtk_size_group_new (mode);
+
+ for (name = first_widget; name; name = va_arg (args, char *)) {
+ widget = glade_xml_get_widget (gui, name);
+ if (!widget) {
+ g_warning ("Glade file is missing widget '%s'", name);
+ continue;
+ }
+
+ gtk_size_group_add_widget (size_group, widget);
+ }
+
+ g_object_unref (size_group);
+
+ va_end (args);
+}
+
+GdkPixbuf *
+gossip_pixbuf_from_smiley (GossipSmiley type,
+ GtkIconSize icon_size)
+{
+ GtkIconTheme *theme;
+ GdkPixbuf *pixbuf = NULL;
+ GError *error = NULL;
+ gint w, h;
+ gint size;
+ const gchar *icon_id;
+
+ theme = gtk_icon_theme_get_default ();
+
+ if (!gtk_icon_size_lookup (icon_size, &w, &h)) {
+ size = 16;
+ } else {
+ size = (w + h) / 2;
+ }
+
+ switch (type) {
+ case GOSSIP_SMILEY_NORMAL: /* :) */
+ icon_id = "stock_smiley-1";
+ break;
+ case GOSSIP_SMILEY_WINK: /* ;) */
+ icon_id = "stock_smiley-3";
+ break;
+ case GOSSIP_SMILEY_BIGEYE: /* =) */
+ icon_id = "stock_smiley-2";
+ break;
+ case GOSSIP_SMILEY_NOSE: /* :-) */
+ icon_id = "stock_smiley-7";
+ break;
+ case GOSSIP_SMILEY_CRY: /* :'( */
+ icon_id = "stock_smiley-11";
+ break;
+ case GOSSIP_SMILEY_SAD: /* :( */
+ icon_id = "stock_smiley-4";
+ break;
+ case GOSSIP_SMILEY_SCEPTICAL: /* :/ */
+ icon_id = "stock_smiley-9";
+ break;
+ case GOSSIP_SMILEY_BIGSMILE: /* :D */
+ icon_id = "stock_smiley-6";
+ break;
+ case GOSSIP_SMILEY_INDIFFERENT: /* :| */
+ icon_id = "stock_smiley-8";
+ break;
+ case GOSSIP_SMILEY_TOUNGE: /* :p */
+ icon_id = "stock_smiley-10";
+ break;
+ case GOSSIP_SMILEY_SHOCKED: /* :o */
+ icon_id = "stock_smiley-5";
+ break;
+ case GOSSIP_SMILEY_COOL: /* 8) */
+ icon_id = "stock_smiley-15";
+ break;
+ case GOSSIP_SMILEY_SORRY: /* *| */
+ icon_id = "stock_smiley-12";
+ break;
+ case GOSSIP_SMILEY_KISS: /* :* */
+ icon_id = "stock_smiley-13";
+ break;
+ case GOSSIP_SMILEY_SHUTUP: /* :# */
+ icon_id = "stock_smiley-14";
+ break;
+ case GOSSIP_SMILEY_YAWN: /* |O */
+ icon_id = "";
+ break;
+ case GOSSIP_SMILEY_CONFUSED: /* :$ */
+ icon_id = "stock_smiley-17";
+ break;
+ case GOSSIP_SMILEY_ANGEL: /* O) */
+ icon_id = "stock_smiley-18";
+ break;
+ case GOSSIP_SMILEY_OOOH: /* :x */
+ icon_id = "stock_smiley-19";
+ break;
+ case GOSSIP_SMILEY_LOOKAWAY: /* *) */
+ icon_id = "stock_smiley-20";
+ break;
+ case GOSSIP_SMILEY_BLUSH: /* *S */
+ icon_id = "stock_smiley-23";
+ break;
+ case GOSSIP_SMILEY_COOLBIGSMILE: /* 8D */
+ icon_id = "stock_smiley-25";
+ break;
+ case GOSSIP_SMILEY_ANGRY: /* :@ */
+ icon_id = "stock_smiley-16";
+ break;
+ case GOSSIP_SMILEY_BOSS: /* @) */
+ icon_id = "stock_smiley-21";
+ break;
+ case GOSSIP_SMILEY_MONKEY: /* #) */
+ icon_id = "stock_smiley-22";
+ break;
+ case GOSSIP_SMILEY_SILLY: /* O) */
+ icon_id = "stock_smiley-24";
+ break;
+ case GOSSIP_SMILEY_SICK: /* +o( */
+ icon_id = "stock_smiley-26";
+ break;
+
+ default:
+ g_assert_not_reached ();
+ icon_id = NULL;
+ }
+
+ pixbuf = gtk_icon_theme_load_icon (theme,
+ icon_id, /* icon name */
+ size, /* size */
+ 0, /* flags */
+ &error);
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+gossip_pixbuf_from_profile (McProfile *profile,
+ GtkIconSize icon_size)
+{
+ GtkIconTheme *theme;
+ gint size = 48;
+ gint w, h;
+ const gchar *icon_id = NULL;
+ GError *error = NULL;
+
+ theme = gtk_icon_theme_get_default ();
+
+ if (gtk_icon_size_lookup (icon_size, &w, &h)) {
+ size = (w + h) / 2;
+ }
+
+ icon_id = mc_profile_get_icon_name (profile);
+
+ theme = gtk_icon_theme_get_default ();
+ return gtk_icon_theme_load_icon (theme,
+ icon_id, /* Icon name */
+ size, /* Size */
+ 0, /* Flags */
+ &error);
+}
+
+GdkPixbuf *
+gossip_pixbuf_from_account (McAccount *account,
+ GtkIconSize icon_size)
+{
+ McProfile *profile;
+
+ profile = mc_account_get_profile (account);
+
+ return gossip_pixbuf_from_profile (profile, icon_size);
+}
+
+GdkPixbuf *
+gossip_pixbuf_for_presence_state (GossipPresenceState state)
+{
+ const gchar *stock = NULL;
+
+ switch (state) {
+ case GOSSIP_PRESENCE_STATE_AVAILABLE:
+ stock = GOSSIP_STOCK_AVAILABLE;
+ break;
+ case GOSSIP_PRESENCE_STATE_BUSY:
+ stock = GOSSIP_STOCK_BUSY;
+ break;
+ case GOSSIP_PRESENCE_STATE_AWAY:
+ stock = GOSSIP_STOCK_AWAY;
+ break;
+ case GOSSIP_PRESENCE_STATE_EXT_AWAY:
+ stock = GOSSIP_STOCK_EXT_AWAY;
+ break;
+ case GOSSIP_PRESENCE_STATE_HIDDEN:
+ case GOSSIP_PRESENCE_STATE_UNAVAILABLE:
+ stock = GOSSIP_STOCK_OFFLINE;
+ break;
+ }
+
+ return gossip_stock_render (stock, GTK_ICON_SIZE_MENU);
+}
+
+GdkPixbuf *
+gossip_pixbuf_for_presence (GossipPresence *presence)
+{
+ GossipPresenceState state;
+
+ g_return_val_if_fail (GOSSIP_IS_PRESENCE (presence),
+ gossip_pixbuf_offline ());
+
+ state = gossip_presence_get_state (presence);
+
+ return gossip_pixbuf_for_presence_state (state);
+}
+
+GdkPixbuf *
+gossip_pixbuf_for_contact (GossipContact *contact)
+{
+ GossipPresence *presence;
+ GossipSubscription subscription;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact),
+ gossip_pixbuf_offline ());
+
+ presence = gossip_contact_get_active_presence (contact);
+
+ if (presence) {
+ return gossip_pixbuf_for_presence (presence);
+ }
+
+ subscription = gossip_contact_get_subscription (contact);
+
+ if (subscription != GOSSIP_SUBSCRIPTION_BOTH &&
+ subscription != GOSSIP_SUBSCRIPTION_TO) {
+ return gossip_stock_render (GOSSIP_STOCK_PENDING,
+ GTK_ICON_SIZE_MENU);
+ }
+
+ return gossip_pixbuf_offline ();
+}
+
+GdkPixbuf *
+gossip_pixbuf_offline (void)
+{
+ return gossip_stock_render (GOSSIP_STOCK_OFFLINE,
+ GTK_ICON_SIZE_MENU);
+}
+
+GdkPixbuf *
+gossip_pixbuf_avatar_from_contact (GossipContact *contact)
+{
+ GdkPixbuf *pixbuf;
+ GdkPixbufLoader *loader;
+ GossipAvatar *avatar;
+ GError *error = NULL;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ avatar = gossip_contact_get_avatar (contact);
+ if (!avatar) {
+ return NULL;
+ }
+
+ loader = gdk_pixbuf_loader_new ();
+
+ if (!gdk_pixbuf_loader_write (loader, avatar->data, avatar->len, &error)) {
+ g_warning ("Couldn't write avatar image:%p with "
+ "length:%" G_GSIZE_FORMAT " to pixbuf loader: %s",
+ avatar->data, avatar->len, error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ gdk_pixbuf_loader_close (loader, NULL);
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+
+ g_object_ref (pixbuf);
+ g_object_unref (loader);
+
+ return pixbuf;
+}
+
+static void
+pixbuf_from_avatar_size_prepared_cb (GdkPixbufLoader *loader,
+ int width,
+ int height,
+ struct SizeData *data)
+{
+ g_return_if_fail (width > 0 && height > 0);
+
+ if (data->preserve_aspect_ratio && (data->width > 0 || data->height > 0)) {
+ if (width < data->width && height < data->height) {
+ width = width;
+ height = height;
+ }
+
+ if (data->width < 0) {
+ width = width * (double) data->height / (gdouble) height;
+ height = data->height;
+ } else if (data->height < 0) {
+ height = height * (double) data->width / (double) width;
+ width = data->width;
+ } else if ((double) height * (double) data->width >
+ (double) width * (double) data->height) {
+ width = 0.5 + (double) width * (double) data->height / (double) height;
+ height = data->height;
+ } else {
+ height = 0.5 + (double) height * (double) data->width / (double) width;
+ width = data->width;
+ }
+ } else {
+ if (data->width > 0) {
+ width = data->width;
+ }
+
+ if (data->height > 0) {
+ height = data->height;
+ }
+ }
+
+ gdk_pixbuf_loader_set_size (loader, width, height);
+}
+
+GdkPixbuf *
+gossip_pixbuf_from_avatar_scaled (GossipAvatar *avatar,
+ gint width,
+ gint height)
+{
+ GdkPixbuf *pixbuf;
+ GdkPixbufLoader *loader;
+ struct SizeData data;
+ GError *error = NULL;
+
+ if (!avatar) {
+ return NULL;
+ }
+
+ data.width = width;
+ data.height = height;
+ data.preserve_aspect_ratio = TRUE;
+
+ loader = gdk_pixbuf_loader_new ();
+
+ g_signal_connect (loader, "size-prepared",
+ G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
+ &data);
+
+ if (!gdk_pixbuf_loader_write (loader, avatar->data, avatar->len, &error)) {
+ g_warning ("Couldn't write avatar image:%p with "
+ "length:%" G_GSIZE_FORMAT " to pixbuf loader: %s",
+ avatar->data, avatar->len, error->message);
+ g_error_free (error);
+ return NULL;
+ }
+
+ gdk_pixbuf_loader_close (loader, NULL);
+
+ pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+
+ g_object_ref (pixbuf);
+ g_object_unref (loader);
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+gossip_pixbuf_avatar_from_contact_scaled (GossipContact *contact,
+ gint width,
+ gint height)
+{
+ GossipAvatar *avatar;
+
+ g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
+
+ avatar = gossip_contact_get_avatar (contact);
+
+ return gossip_pixbuf_from_avatar_scaled (avatar, width, height);
+}
+/* Stolen from GtkSourceView, hence the weird intendation. Please keep it like
+ * that to make it easier to apply changes from the original code.
+ */
+#define GTK_TEXT_UNKNOWN_CHAR 0xFFFC
+
+/* this function acts like g_utf8_offset_to_pointer() except that if it finds a
+ * decomposable character it consumes the decomposition length from the given
+ * offset. So it's useful when the offset was calculated for the normalized
+ * version of str, but we need a pointer to str itself. */
+static const gchar *
+pointer_from_offset_skipping_decomp (const gchar *str, gint offset)
+{
+ gchar *casefold, *normal;
+ const gchar *p, *q;
+
+ p = str;
+ while (offset > 0)
+ {
+ q = g_utf8_next_char (p);
+ casefold = g_utf8_casefold (p, q - p);
+ normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ offset -= g_utf8_strlen (normal, -1);
+ g_free (casefold);
+ g_free (normal);
+ p = q;
+ }
+ return p;
+}
+
+static const gchar *
+g_utf8_strcasestr (const gchar *haystack, const gchar *needle)
+{
+ gsize needle_len;
+ gsize haystack_len;
+ const gchar *ret = NULL;
+ gchar *p;
+ gchar *casefold;
+ gchar *caseless_haystack;
+ gint i;
+
+ g_return_val_if_fail (haystack != NULL, NULL);
+ g_return_val_if_fail (needle != NULL, NULL);
+
+ casefold = g_utf8_casefold (haystack, -1);
+ caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ needle_len = g_utf8_strlen (needle, -1);
+ haystack_len = g_utf8_strlen (caseless_haystack, -1);
+
+ if (needle_len == 0)
+ {
+ ret = (gchar *)haystack;
+ goto finally_1;
+ }
+
+ if (haystack_len < needle_len)
+ {
+ ret = NULL;
+ goto finally_1;
+ }
+
+ p = (gchar*)caseless_haystack;
+ needle_len = strlen (needle);
+ i = 0;
+
+ while (*p)
+ {
+ if ((strncmp (p, needle, needle_len) == 0))
+ {
+ ret = pointer_from_offset_skipping_decomp (haystack, i);
+ goto finally_1;
+ }
+
+ p = g_utf8_next_char (p);
+ i++;
+ }
+
+finally_1:
+ g_free (caseless_haystack);
+
+ return ret;
+}
+
+static gboolean
+g_utf8_caselessnmatch (const char *s1, const char *s2,
+ gssize n1, gssize n2)
+{
+ gchar *casefold;
+ gchar *normalized_s1;
+ gchar *normalized_s2;
+ gint len_s1;
+ gint len_s2;
+ gboolean ret = FALSE;
+
+ g_return_val_if_fail (s1 != NULL, FALSE);
+ g_return_val_if_fail (s2 != NULL, FALSE);
+ g_return_val_if_fail (n1 > 0, FALSE);
+ g_return_val_if_fail (n2 > 0, FALSE);
+
+ casefold = g_utf8_casefold (s1, n1);
+ normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ casefold = g_utf8_casefold (s2, n2);
+ normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ len_s1 = strlen (normalized_s1);
+ len_s2 = strlen (normalized_s2);
+
+ if (len_s1 < len_s2)
+ goto finally_2;
+
+ ret = (strncmp (normalized_s1, normalized_s2, len_s2) == 0);
+
+finally_2:
+ g_free (normalized_s1);
+ g_free (normalized_s2);
+
+ return ret;
+}
+
+static void
+forward_chars_with_skipping (GtkTextIter *iter,
+ gint count,
+ gboolean skip_invisible,
+ gboolean skip_nontext,
+ gboolean skip_decomp)
+{
+ gint i;
+
+ g_return_if_fail (count >= 0);
+
+ i = count;
+
+ while (i > 0)
+ {
+ gboolean ignored = FALSE;
+
+ /* minimal workaround to avoid the infinite loop of bug #168247.
+ * It doesn't fix the problemjust the symptom...
+ */
+ if (gtk_text_iter_is_end (iter))
+ return;
+
+ if (skip_nontext && gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR)
+ ignored = TRUE;
+
+ if (!ignored && skip_invisible &&
+ /* _gtk_text_btree_char_is_invisible (iter)*/ FALSE)
+ ignored = TRUE;
+
+ if (!ignored && skip_decomp)
+ {
+ /* being UTF8 correct sucks; this accounts for extra
+ offsets coming from canonical decompositions of
+ UTF8 characters (e.g. accented characters) which
+ g_utf8_normalize() performs */
+ gchar *normal;
+ gchar buffer[6];
+ gint buffer_len;
+
+ buffer_len = g_unichar_to_utf8 (gtk_text_iter_get_char (iter), buffer);
+ normal = g_utf8_normalize (buffer, buffer_len, G_NORMALIZE_NFD);
+ i -= (g_utf8_strlen (normal, -1) - 1);
+ g_free (normal);
+ }
+
+ gtk_text_iter_forward_char (iter);
+
+ if (!ignored)
+ --i;
+ }
+}
+
+static gboolean
+lines_match (const GtkTextIter *start,
+ const gchar **lines,
+ gboolean visible_only,
+ gboolean slice,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end)
+{
+ GtkTextIter next;
+ gchar *line_text;
+ const gchar *found;
+ gint offset;
+
+ if (*lines == NULL || **lines == '\0')
+ {
+ if (match_start)
+ *match_start = *start;
+ if (match_end)
+ *match_end = *start;
+ return TRUE;
+ }
+
+ next = *start;
+ gtk_text_iter_forward_line (&next);
+
+ /* No more text in buffer, but *lines is nonempty */
+ if (gtk_text_iter_equal (start, &next))
+ return FALSE;
+
+ if (slice)
+ {
+ if (visible_only)
+ line_text = gtk_text_iter_get_visible_slice (start, &next);
+ else
+ line_text = gtk_text_iter_get_slice (start, &next);
+ }
+ else
+ {
+ if (visible_only)
+ line_text = gtk_text_iter_get_visible_text (start, &next);
+ else
+ line_text = gtk_text_iter_get_text (start, &next);
+ }
+
+ if (match_start) /* if this is the first line we're matching */
+ {
+ found = g_utf8_strcasestr (line_text, *lines);
+ }
+ else
+ {
+ /* If it's not the first line, we have to match from the
+ * start of the line.
+ */
+ if (g_utf8_caselessnmatch (line_text, *lines, strlen (line_text),
+ strlen (*lines)))
+ found = line_text;
+ else
+ found = NULL;
+ }
+
+ if (found == NULL)
+ {
+ g_free (line_text);
+ return FALSE;
+ }
+
+ /* Get offset to start of search string */
+ offset = g_utf8_strlen (line_text, found - line_text);
+
+ next = *start;
+
+ /* If match start needs to be returned, set it to the
+ * start of the search string.
+ */
+ forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
+ if (match_start)
+ {
+ *match_start = next;
+ }
+
+ /* Go to end of search string */
+ forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
+
+ g_free (line_text);
+
+ ++lines;
+
+ if (match_end)
+ *match_end = next;
+
+ /* pass NULL for match_start, since we don't need to find the
+ * start again.
+ */
+ return lines_match (&next, lines, visible_only, slice, NULL, match_end);
+}
+
+/* strsplit () that retains the delimiter as part of the string. */
+static gchar **
+strbreakup (const char *string,
+ const char *delimiter,
+ gint max_tokens)
+{
+ GSList *string_list = NULL, *slist;
+ gchar **str_array, *s, *casefold, *new_string;
+ guint i, n = 1;
+
+ g_return_val_if_fail (string != NULL, NULL);
+ g_return_val_if_fail (delimiter != NULL, NULL);
+
+ if (max_tokens < 1)
+ max_tokens = G_MAXINT;
+
+ s = strstr (string, delimiter);
+ if (s)
+ {
+ guint delimiter_len = strlen (delimiter);
+
+ do
+ {
+ guint len;
+
+ len = s - string + delimiter_len;
+ new_string = g_new (gchar, len + 1);
+ strncpy (new_string, string, len);
+ new_string[len] = 0;
+ casefold = g_utf8_casefold (new_string, -1);
+ g_free (new_string);
+ new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+ string_list = g_slist_prepend (string_list, new_string);
+ n++;
+ string = s + delimiter_len;
+ s = strstr (string, delimiter);
+ } while (--max_tokens && s);
+ }
+
+ if (*string)
+ {
+ n++;
+ casefold = g_utf8_casefold (string, -1);
+ new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+ string_list = g_slist_prepend (string_list, new_string);
+ }
+
+ str_array = g_new (gchar*, n);
+
+ i = n - 1;
+
+ str_array[i--] = NULL;
+ for (slist = string_list; slist; slist = slist->next)
+ str_array[i--] = slist->data;
+
+ g_slist_free (string_list);
+
+ return str_array;
+}
+
+gboolean
+gossip_text_iter_forward_search (const GtkTextIter *iter,
+ const gchar *str,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end,
+ const GtkTextIter *limit)
+{
+ gchar **lines = NULL;
+ GtkTextIter match;
+ gboolean retval = FALSE;
+ GtkTextIter search;
+ gboolean visible_only;
+ gboolean slice;
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (str != NULL, FALSE);
+
+ if (limit && gtk_text_iter_compare (iter, limit) >= 0)
+ return FALSE;
+
+ if (*str == '\0') {
+ /* If we can move one char, return the empty string there */
+ match = *iter;
+
+ if (gtk_text_iter_forward_char (&match)) {
+ if (limit && gtk_text_iter_equal (&match, limit)) {
+ return FALSE;
+ }
+
+ if (match_start) {
+ *match_start = match;
+ }
+ if (match_end) {
+ *match_end = match;
+ }
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+ }
+
+ visible_only = TRUE;
+ slice = FALSE;
+
+ /* locate all lines */
+ lines = strbreakup (str, "\n", -1);
+
+ search = *iter;
+
+ do {
+ /* This loop has an inefficient worst-case, where
+ * gtk_text_iter_get_text () is called repeatedly on
+ * a single line.
+ */
+ GtkTextIter end;
+
+ if (limit && gtk_text_iter_compare (&search, limit) >= 0) {
+ break;
+ }
+
+ if (lines_match (&search, (const gchar**)lines,
+ visible_only, slice, &match, &end)) {
+ if (limit == NULL ||
+ (limit && gtk_text_iter_compare (&end, limit) <= 0)) {
+ retval = TRUE;
+
+ if (match_start) {
+ *match_start = match;
+ }
+ if (match_end) {
+ *match_end = end;
+ }
+ }
+ break;
+ }
+ } while (gtk_text_iter_forward_line (&search));
+
+ g_strfreev ((gchar**)lines);
+
+ return retval;
+}
+
+static const gchar *
+g_utf8_strrcasestr (const gchar *haystack, const gchar *needle)
+{
+ gsize needle_len;
+ gsize haystack_len;
+ const gchar *ret = NULL;
+ gchar *p;
+ gchar *casefold;
+ gchar *caseless_haystack;
+ gint i;
+
+ g_return_val_if_fail (haystack != NULL, NULL);
+ g_return_val_if_fail (needle != NULL, NULL);
+
+ casefold = g_utf8_casefold (haystack, -1);
+ caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
+ g_free (casefold);
+
+ needle_len = g_utf8_strlen (needle, -1);
+ haystack_len = g_utf8_strlen (caseless_haystack, -1);
+
+ if (needle_len == 0)
+ {
+ ret = (gchar *)haystack;
+ goto finally_1;
+ }
+
+ if (haystack_len < needle_len)
+ {
+ ret = NULL;
+ goto finally_1;
+ }
+
+ i = haystack_len - needle_len;
+ p = g_utf8_offset_to_pointer (caseless_haystack, i);
+ needle_len = strlen (needle);
+
+ while (p >= caseless_haystack)
+ {
+ if (strncmp (p, needle, needle_len) == 0)
+ {
+ ret = pointer_from_offset_skipping_decomp (haystack, i);
+ goto finally_1;
+ }
+
+ p = g_utf8_prev_char (p);
+ i--;
+ }
+
+finally_1:
+ g_free (caseless_haystack);
+
+ return ret;
+}
+
+static gboolean
+backward_lines_match (const GtkTextIter *start,
+ const gchar **lines,
+ gboolean visible_only,
+ gboolean slice,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end)
+{
+ GtkTextIter line, next;
+ gchar *line_text;
+ const gchar *found;
+ gint offset;
+
+ if (*lines == NULL || **lines == '\0')
+ {
+ if (match_start)
+ *match_start = *start;
+ if (match_end)
+ *match_end = *start;
+ return TRUE;
+ }
+
+ line = next = *start;
+ if (gtk_text_iter_get_line_offset (&next) == 0)
+ {
+ if (!gtk_text_iter_backward_line (&next))
+ return FALSE;
+ }
+ else
+ gtk_text_iter_set_line_offset (&next, 0);
+
+ if (slice)
+ {
+ if (visible_only)
+ line_text = gtk_text_iter_get_visible_slice (&next, &line);
+ else
+ line_text = gtk_text_iter_get_slice (&next, &line);
+ }
+ else
+ {
+ if (visible_only)
+ line_text = gtk_text_iter_get_visible_text (&next, &line);
+ else
+ line_text = gtk_text_iter_get_text (&next, &line);
+ }
+
+ if (match_start) /* if this is the first line we're matching */
+ {
+ found = g_utf8_strrcasestr (line_text, *lines);
+ }
+ else
+ {
+ /* If it's not the first line, we have to match from the
+ * start of the line.
+ */
+ if (g_utf8_caselessnmatch (line_text, *lines, strlen (line_text),
+ strlen (*lines)))
+ found = line_text;
+ else
+ found = NULL;
+ }
+
+ if (found == NULL)
+ {
+ g_free (line_text);
+ return FALSE;
+ }
+
+ /* Get offset to start of search string */
+ offset = g_utf8_strlen (line_text, found - line_text);
+
+ forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE);
+
+ /* If match start needs to be returned, set it to the
+ * start of the search string.
+ */
+ if (match_start)
+ {
+ *match_start = next;
+ }
+
+ /* Go to end of search string */
+ forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE);
+
+ g_free (line_text);
+
+ ++lines;
+
+ if (match_end)
+ *match_end = next;
+
+ /* try to match the rest of the lines forward, passing NULL
+ * for match_start so lines_match will try to match the entire
+ * line */
+ return lines_match (&next, lines, visible_only,
+ slice, NULL, match_end);
+}
+
+gboolean
+gossip_text_iter_backward_search (const GtkTextIter *iter,
+ const gchar *str,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end,
+ const GtkTextIter *limit)
+{
+ gchar **lines = NULL;
+ GtkTextIter match;
+ gboolean retval = FALSE;
+ GtkTextIter search;
+ gboolean visible_only;
+ gboolean slice;
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (str != NULL, FALSE);
+
+ if (limit && gtk_text_iter_compare (iter, limit) <= 0)
+ return FALSE;
+
+ if (*str == '\0')
+ {
+ /* If we can move one char, return the empty string there */
+ match = *iter;
+
+ if (gtk_text_iter_backward_char (&match))
+ {
+ if (limit && gtk_text_iter_equal (&match, limit))
+ return FALSE;
+
+ if (match_start)
+ *match_start = match;
+ if (match_end)
+ *match_end = match;
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+ }
+
+ visible_only = TRUE;
+ slice = TRUE;
+
+ /* locate all lines */
+ lines = strbreakup (str, "\n", -1);
+
+ search = *iter;
+
+ while (TRUE)
+ {
+ /* This loop has an inefficient worst-case, where
+ * gtk_text_iter_get_text () is called repeatedly on
+ * a single line.
+ */
+ GtkTextIter end;
+
+ if (limit && gtk_text_iter_compare (&search, limit) <= 0)
+ break;
+
+ if (backward_lines_match (&search, (const gchar**)lines,
+ visible_only, slice, &match, &end))
+ {
+ if (limit == NULL || (limit &&
+ gtk_text_iter_compare (&end, limit) > 0))
+ {
+ retval = TRUE;
+
+ if (match_start)
+ *match_start = match;
+ if (match_end)
+ *match_end = end;
+ }
+ break;
+ }
+
+ if (gtk_text_iter_get_line_offset (&search) == 0)
+ {
+ if (!gtk_text_iter_backward_line (&search))
+ break;
+ }
+ else
+ {
+ gtk_text_iter_set_line_offset (&search, 0);
+ }
+ }
+
+ g_strfreev ((gchar**)lines);
+
+ return retval;
+}
+
+static gboolean
+window_get_is_on_current_workspace (GtkWindow *window)
+{
+ GdkWindow *gdk_window;
+
+ gdk_window = GTK_WIDGET (window)->window;
+ if (gdk_window) {
+ return !(gdk_window_get_state (gdk_window) &
+ GDK_WINDOW_STATE_ICONIFIED);
+ } else {
+ return FALSE;
+ }
+}
+
+/* Checks if the window is visible as in visible on the current workspace. */
+gboolean
+gossip_window_get_is_visible (GtkWindow *window)
+{
+ gboolean visible;
+
+ g_return_val_if_fail (window != NULL, FALSE);
+
+ g_object_get (window,
+ "visible", &visible,
+ NULL);
+
+ return visible && window_get_is_on_current_workspace (window);
+}
+
+/* Takes care of moving the window to the current workspace. */
+void
+gossip_window_present (GtkWindow *window,
+ gboolean steal_focus)
+{
+ gboolean visible;
+ gboolean on_current;
+ guint32 timestamp;
+
+ g_return_if_fail (window != NULL);
+
+ /* There are three cases: hidden, visible, visible on another
+ * workspace.
+ */
+
+ g_object_get (window,
+ "visible", &visible,
+ NULL);
+
+ on_current = window_get_is_on_current_workspace (window);
+
+ if (visible && !on_current) {
+ /* Hide it so present brings it to the current workspace. */
+ gtk_widget_hide (GTK_WIDGET (window));
+ }
+
+ timestamp = gtk_get_current_event_time ();
+ if (steal_focus && timestamp != GDK_CURRENT_TIME) {
+ gtk_window_present_with_time (window, timestamp);
+ } else {
+ gtk_window_present (window);
+ }
+}
+
+/* The URL opening code can't handle schemeless strings, so we try to be
+ * smart and add http if there is no scheme or doesn't look like a mail
+ * address. This should work in most cases, and let us click on strings
+ * like "www.gnome.org".
+ */
+static gchar *
+fixup_url (const gchar *url)
+{
+ gchar *real_url;
+
+ if (!g_str_has_prefix (url, "http://") &&
+ !strstr (url, ":/") &&
+ !strstr (url, "@")) {
+ real_url = g_strdup_printf ("http://%s", url);
+ } else {
+ real_url = g_strdup (url);
+ }
+
+ return real_url;
+}
+
+void
+gossip_url_show (const char *url)
+{
+ gchar *real_url;
+ GError *error = NULL;
+
+ real_url = fixup_url (url);
+ gnome_url_show (real_url, &error);
+ if (error) {
+ g_warning ("Couldn't show URL:'%s'", real_url);
+ g_error_free (error);
+ }
+
+ g_free (real_url);
+}
+
+static void
+link_button_hook (GtkLinkButton *button,
+ const gchar *link,
+ gpointer user_data)
+{
+ gossip_url_show (link);
+}
+
+GtkWidget *
+gossip_link_button_new (const gchar *url,
+ const gchar *title)
+{
+ static gboolean hook = FALSE;
+
+ if (!hook) {
+ hook = TRUE;
+ gtk_link_button_set_uri_hook (link_button_hook, NULL, NULL);
+ }
+
+ return gtk_link_button_new_with_label (url, title);
+}
+
+/* FIXME: Do this in a proper way at some point, probably in GTK+? */
+void
+gossip_window_set_default_icon_name (const gchar *name)
+{
+ gtk_window_set_default_icon_name (name);
+}
+
+void
+gossip_toggle_button_set_state_quietly (GtkWidget *widget,
+ GCallback callback,
+ gpointer user_data,
+ gboolean active)
+{
+ g_return_if_fail (GTK_IS_TOGGLE_BUTTON (widget));
+
+ g_signal_handlers_block_by_func (widget, callback, user_data);
+ g_object_set (widget, "active", active, NULL);
+ g_signal_handlers_unblock_by_func (widget, callback, user_data);
+}
+
diff --git a/libempathy-gtk/gossip-ui-utils.h b/libempathy-gtk/gossip-ui-utils.h
new file mode 100644
index 000000000..581587a73
--- /dev/null
+++ b/libempathy-gtk/gossip-ui-utils.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2002-2007 Imendio AB
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ * Richard Hult <richard@imendio.com>
+ * Martyn Russell <martyn@imendio.com>
+ *
+ * Part of this file is copied from GtkSourceView (gtksourceiter.c):
+ * Paolo Maggi
+ * Jeroen Zwartepoorte
+ */
+
+#ifndef __GOSSIP_UI_UTILS_H__
+#define __GOSSIP_UI_UTILS_H__
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include <libmissioncontrol/mc-account.h>
+#include <libmissioncontrol/mc-profile.h>
+
+#include <libempathy/gossip-presence.h>
+#include <libempathy/gossip-contact.h>
+#include <libempathy/gossip-avatar.h>
+
+#include "gossip-chat-view.h"
+
+G_BEGIN_DECLS
+
+#define G_STR_EMPTY(x) ((x) == NULL || (x)[0] == '\0')
+
+/* Glade */
+void gossip_glade_get_file_simple (const gchar *filename,
+ const gchar *root,
+ const gchar *domain,
+ const gchar *first_required_widget,
+ ...);
+GladeXML * gossip_glade_get_file (const gchar *filename,
+ const gchar *root,
+ const gchar *domain,
+ const gchar *first_required_widget,
+ ...);
+void gossip_glade_connect (GladeXML *gui,
+ gpointer user_data,
+ gchar *first_widget,
+ ...);
+void gossip_glade_setup_size_group (GladeXML *gui,
+ GtkSizeGroupMode mode,
+ gchar *first_widget,
+ ...);
+/* Pixbufs */
+GdkPixbuf * gossip_pixbuf_from_smiley (GossipSmiley type,
+ GtkIconSize icon_size);
+GdkPixbuf * gossip_pixbuf_from_profile (McProfile *account,
+ GtkIconSize icon_size);
+GdkPixbuf * gossip_pixbuf_from_account (McAccount *account,
+ GtkIconSize icon_size);
+GdkPixbuf * gossip_pixbuf_for_presence_state (GossipPresenceState state);
+GdkPixbuf * gossip_pixbuf_for_presence (GossipPresence *presence);
+GdkPixbuf * gossip_pixbuf_for_contact (GossipContact *contact);
+GdkPixbuf * gossip_pixbuf_offline (void);
+GdkPixbuf * gossip_pixbuf_from_avatar_scaled (GossipAvatar *avatar,
+ gint width,
+ gint height);
+GdkPixbuf * gossip_pixbuf_avatar_from_contact (GossipContact *contact);
+GdkPixbuf * gossip_pixbuf_avatar_from_contact_scaled (GossipContact *contact,
+ gint width,
+ gint height);
+/* Text view */
+gboolean gossip_text_iter_forward_search (const GtkTextIter *iter,
+ const gchar *str,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end,
+ const GtkTextIter *limit);
+gboolean gossip_text_iter_backward_search (const GtkTextIter *iter,
+ const gchar *str,
+ GtkTextIter *match_start,
+ GtkTextIter *match_end,
+ const GtkTextIter *limit);
+
+/* Windows */
+gboolean gossip_window_get_is_visible (GtkWindow *window);
+void gossip_window_present (GtkWindow *window,
+ gboolean steal_focus);
+void gossip_window_set_default_icon_name (const gchar *name);
+
+void gossip_url_show (const char *url);
+
+void gossip_toggle_button_set_state_quietly (GtkWidget *widget,
+ GCallback callback,
+ gpointer user_data,
+ gboolean active);
+GtkWidget *gossip_link_button_new (const gchar *url,
+ const gchar *title);
+
+
+G_END_DECLS
+
+#endif /* __GOSSIP_UI_UTILS_H__ */
diff --git a/libempathy/Makefile.am b/libempathy/Makefile.am
new file mode 100644
index 000000000..30d40c595
--- /dev/null
+++ b/libempathy/Makefile.am
@@ -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
index 000000000..adc415a93
--- /dev/null
+++ b/libempathy/empathy-chandler.c
@@ -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
index 000000000..ca271613c
--- /dev/null
+++ b/libempathy/empathy-chandler.h
@@ -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
index 000000000..0fb264e5f
--- /dev/null
+++ b/libempathy/empathy-chandler.xml
@@ -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
index 000000000..2cc486de9
--- /dev/null
+++ b/libempathy/empathy-contact-list.c
@@ -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
index 000000000..db8b36e39
--- /dev/null
+++ b/libempathy/empathy-contact-list.h
@@ -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
index 000000000..15fb82eed
--- /dev/null
+++ b/libempathy/empathy-contact-manager.c
@@ -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
index 000000000..ca6cb3fe5
--- /dev/null
+++ b/libempathy/empathy-contact-manager.h
@@ -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
index 000000000..f3ab95f54
--- /dev/null
+++ b/libempathy/empathy-marshal-main.c
@@ -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
index 000000000..6035b9451
--- /dev/null
+++ b/libempathy/empathy-marshal.list
@@ -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
index 000000000..fefda7694
--- /dev/null
+++ b/libempathy/empathy-session.c
@@ -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
index 000000000..0d3a2dce1
--- /dev/null
+++ b/libempathy/empathy-session.h
@@ -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
index 000000000..ad00711d9
--- /dev/null
+++ b/libempathy/empathy-tp-chat.c
@@ -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
index 000000000..c64bbd738
--- /dev/null
+++ b/libempathy/empathy-tp-chat.h
@@ -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
index 000000000..5c17a5176
--- /dev/null
+++ b/libempathy/gossip-avatar.c
@@ -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
index 000000000..44fa9aba3
--- /dev/null
+++ b/libempathy/gossip-avatar.h
@@ -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
index 000000000..9625a700d
--- /dev/null
+++ b/libempathy/gossip-conf.c
@@ -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
index 000000000..35fdfb902
--- /dev/null
+++ b/libempathy/gossip-conf.h
@@ -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
index 000000000..6950c89b7
--- /dev/null
+++ b/libempathy/gossip-contact.c
@@ -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
index 000000000..92caee9ea
--- /dev/null
+++ b/libempathy/gossip-contact.h
@@ -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
index 000000000..a6bbeea2a
--- /dev/null
+++ b/libempathy/gossip-debug.c
@@ -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
index 000000000..39dae0f1c
--- /dev/null
+++ b/libempathy/gossip-debug.h
@@ -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
index 000000000..85889e7da
--- /dev/null
+++ b/libempathy/gossip-message.c
@@ -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
index 000000000..8defba7e7
--- /dev/null
+++ b/libempathy/gossip-message.h
@@ -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
index 000000000..aa97bdcc7
--- /dev/null
+++ b/libempathy/gossip-paths.c
@@ -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
index 000000000..e00a33e28
--- /dev/null
+++ b/libempathy/gossip-paths.h
@@ -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
index 000000000..e41ae5548
--- /dev/null
+++ b/libempathy/gossip-presence.c
@@ -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
index 000000000..64a0b8ec3
--- /dev/null
+++ b/libempathy/gossip-presence.h
@@ -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
index 000000000..4b04ac42f
--- /dev/null
+++ b/libempathy/gossip-telepathy-group.c
@@ -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
index 000000000..9c61bdbc4
--- /dev/null
+++ b/libempathy/gossip-telepathy-group.h
@@ -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
index 000000000..a1956354e
--- /dev/null
+++ b/libempathy/gossip-time.c
@@ -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
index 000000000..06057aa52
--- /dev/null
+++ b/libempathy/gossip-time.h
@@ -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
index 000000000..24f5cd432
--- /dev/null
+++ b/libempathy/gossip-utils.c
@@ -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
index 000000000..7f0ab90ef
--- /dev/null
+++ b/libempathy/gossip-utils.h
@@ -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
index 000000000..11df65cf9
--- /dev/null
+++ b/pixmaps/Makefile.am
@@ -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
index 000000000..c2d074f50
--- /dev/null
+++ b/pixmaps/gossip-available.png
Binary files differ
diff --git a/pixmaps/gossip-away.png b/pixmaps/gossip-away.png
new file mode 100644
index 000000000..ba9ed232a
--- /dev/null
+++ b/pixmaps/gossip-away.png
Binary files differ
diff --git a/pixmaps/gossip-busy.png b/pixmaps/gossip-busy.png
new file mode 100644
index 000000000..190c5ae70
--- /dev/null
+++ b/pixmaps/gossip-busy.png
Binary files differ
diff --git a/pixmaps/gossip-extended-away.png b/pixmaps/gossip-extended-away.png
new file mode 100644
index 000000000..59dbe08f8
--- /dev/null
+++ b/pixmaps/gossip-extended-away.png
Binary files differ
diff --git a/pixmaps/gossip-group-message.png b/pixmaps/gossip-group-message.png
new file mode 100644
index 000000000..ed2d33b9f
--- /dev/null
+++ b/pixmaps/gossip-group-message.png
Binary files differ
diff --git a/pixmaps/gossip-message.png b/pixmaps/gossip-message.png
new file mode 100644
index 000000000..84c3cefff
--- /dev/null
+++ b/pixmaps/gossip-message.png
Binary files differ
diff --git a/pixmaps/gossip-offline.png b/pixmaps/gossip-offline.png
new file mode 100644
index 000000000..528eae409
--- /dev/null
+++ b/pixmaps/gossip-offline.png
Binary files differ
diff --git a/pixmaps/gossip-pending.png b/pixmaps/gossip-pending.png
new file mode 100644
index 000000000..f48de8127
--- /dev/null
+++ b/pixmaps/gossip-pending.png
Binary files differ
diff --git a/pixmaps/gossip-typing.png b/pixmaps/gossip-typing.png
new file mode 100644
index 000000000..defad7b08
--- /dev/null
+++ b/pixmaps/gossip-typing.png
Binary files differ
diff --git a/pixmaps/vcard_16.png b/pixmaps/vcard_16.png
new file mode 100644
index 000000000..0dfdb7780
--- /dev/null
+++ b/pixmaps/vcard_16.png
Binary files differ
diff --git a/pixmaps/vcard_48.png b/pixmaps/vcard_48.png
new file mode 100644
index 000000000..776006cd8
--- /dev/null
+++ b/pixmaps/vcard_48.png
Binary files differ
diff --git a/po/ChangeLog b/po/ChangeLog
new file mode 100644
index 000000000..703e5084d
--- /dev/null
+++ b/po/ChangeLog
@@ -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
index 000000000..40c8833ac
--- /dev/null
+++ b/po/Makefile.in.in
@@ -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
index 000000000..3b7c299a4
--- /dev/null
+++ b/po/POTFILES.in
@@ -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
index 000000000..2536260bb
--- /dev/null
+++ b/profiles/Makefile.am
@@ -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
index 000000000..c70bfa840
--- /dev/null
+++ b/profiles/gtalk.profile
@@ -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
index 000000000..811132cdf
--- /dev/null
+++ b/profiles/jabber.profile
@@ -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
index 000000000..599204c1b
--- /dev/null
+++ b/profiles/msn.profile
@@ -0,0 +1,7 @@
+[Profile]
+Manager=butterfly
+Protocol=msn
+DisplayName=MSN
+IconName = im-msn
+ConfigurationUI = msn
+SupportsInvisible = 1