diff options
-rw-r--r-- | ChangeLog | 35 | ||||
-rw-r--r-- | TODO | 8 | ||||
-rw-r--r-- | data/jabber.profile | 1 | ||||
-rw-r--r-- | libempathy-gtk/Makefile.am | 2 | ||||
-rw-r--r-- | libempathy-gtk/empathy-contact-widget.c | 5 | ||||
-rw-r--r-- | libempathy-gtk/empathy-main-window.c | 3 | ||||
-rw-r--r-- | libempathy-gtk/gossip-chat-view.c | 54 | ||||
-rw-r--r-- | libempathy-gtk/gossip-chat.c | 25 | ||||
-rw-r--r-- | libempathy-gtk/gossip-contact-list-store.c | 42 | ||||
-rw-r--r-- | libempathy-gtk/gossip-log-window.c | 1153 | ||||
-rw-r--r-- | libempathy-gtk/gossip-log-window.glade | 470 | ||||
-rw-r--r-- | libempathy-gtk/gossip-log-window.h | 39 | ||||
-rw-r--r-- | libempathy/empathy-log-manager.c | 376 | ||||
-rw-r--r-- | libempathy/empathy-log-manager.h | 23 | ||||
-rw-r--r-- | libempathy/empathy-tp-chat.c | 6 | ||||
-rw-r--r-- | libempathy/empathy-tp-contact-list.c | 6 | ||||
-rw-r--r-- | libempathy/gossip-contact.c | 79 | ||||
-rw-r--r-- | libempathy/gossip-contact.h | 4 | ||||
-rw-r--r-- | libempathy/gossip-message.c | 56 | ||||
-rw-r--r-- | libempathy/gossip-message.h | 3 | ||||
-rw-r--r-- | src/empathy-main.c | 1 |
21 files changed, 2243 insertions, 148 deletions
@@ -1,3 +1,38 @@ +2007-06-13 Xavier Claessens <xclaesse@gmail.com> + + * libempathy-gtk/Makefile.am: + * libempathy-gtk/gossip-log-window.glade: + * libempathy-gtk/gossip-log-window.h: + * libempathy-gtk/gossip-log-window.c: + * libempathy/empathy-log-manager.c: + * libempathy/empathy-log-manager.h: + * libempathy-gtk/gossip-chat.c: + * libempathy-gtk/empathy-main-window.c: New window for viewing logs. + + * libempathy-gtk/gossip-chat-view.c: Do not use smooth scroll when + resizing the view. + + * libempathy-gtk/gossip-contact-list-store.c: Do not set active + contacts when creating the store, and when contact groups changed. + + * src/empathy-main.c: Fix warning when using command-line options. + + * libempathy/empathy-tp-contact-list.c: Check if we have an aliasing + iface before setting the alias of a contact. + + * TODO: Updated. + + * data/jabber.profile: Ignore ssl errors by default. This is a security + vulnerability but we don't really have the choice. + + * libempathy/gossip-contact.h: + * libempathy/gossip-contact.c: Add a "is-user" property to know if + it's our self contact. + + * libempathy/gossip-message.h: + * libempathy/gossip-message.c: Add a "receiver" property like that we + have our self contact for nick highlight. + 2007-06-10 Xavier Claessens <xclaesse@gmail.com> * libempathy-gtk/gossip-spell-dialog.glade: @@ -2,14 +2,18 @@ Things you can do if you want to help: - Rename all files and functions name to use the empathy namespace. Bug #444490. - Porting gossip-account-widget-*.{c,h} from gossip project (Guillaume is already working on IRC widget). - - 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. Need to create a glade catalog. + - UI for inviting a contact in a chatroom. + - UI for accept/refuse invitation to join a chatroom. + - UI to send a message directly to a contact. + - GtkWidget-ify libempathy-gtk. Actually most window/dialog do not inherit from GtkWindow/GtkDialog. Need to create a glade catalog. - 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. For that we need a filter DBus API in MC, not yet written, a draft spec is proposed on the telepathy ML. - Make use of NetworkManager to set the presence - Remove Quit option everywhere, empathy is a session service and shouldn't be leaved. - Add sound events - Add register capability in GossipAccountsDialog if the profile says it's supported. + - Write a manual based on gossip's. - Testing and Bugfixing. + - Update translations. SoC projects: - Adding VoIP support based on the patch proposed for gossip. diff --git a/data/jabber.profile b/data/jabber.profile index 55a1e6978..c91691daa 100644 --- a/data/jabber.profile +++ b/data/jabber.profile @@ -9,4 +9,5 @@ DefaultAccountDomain = jabber.org SupportsInvisible = 0 VCardField = X-Jabber VCardDefault = true +Default-ignore-ssl-errors = 1 diff --git a/libempathy-gtk/Makefile.am b/libempathy-gtk/Makefile.am index 6a6c7203c..113b37c3a 100644 --- a/libempathy-gtk/Makefile.am +++ b/libempathy-gtk/Makefile.am @@ -39,6 +39,7 @@ libempathy_gtk_la_SOURCES = \ gossip-account-chooser.c gossip-account-chooser.h \ gossip-new-chatroom-dialog.c gossip-new-chatroom-dialog.h \ gossip-chatrooms-window.c gossip-chatrooms-window.h \ + gossip-log-window.c gossip-log-window.h \ gossip-ui-utils.c gossip-ui-utils.h libempathy_gtk_la_LIBADD = \ @@ -61,6 +62,7 @@ glade_DATA = \ gossip-group-chat.glade \ gossip-chatrooms-window.glade \ gossip-spell-dialog.glade \ + gossip-log-window.glade \ gossip-chat.glade dtddir = $(datadir)/empathy diff --git a/libempathy-gtk/empathy-contact-widget.c b/libempathy-gtk/empathy-contact-widget.c index e93117b36..ac43700d0 100644 --- a/libempathy-gtk/empathy-contact-widget.c +++ b/libempathy-gtk/empathy-contact-widget.c @@ -148,10 +148,7 @@ empathy_contact_widget_new (GossipContact *contact, information = g_slice_new0 (EmpathyContactWidget); information->editable = editable; if (contact) { - GossipContact *user_contact; - - user_contact = gossip_contact_get_user (contact); - information->is_user = gossip_contact_equal (contact, user_contact); + information->is_user = gossip_contact_is_user (contact); information->can_change_contact = FALSE; } else { information->is_user = FALSE; diff --git a/libempathy-gtk/empathy-main-window.c b/libempathy-gtk/empathy-main-window.c index 7f8693278..0e7b1fdda 100644 --- a/libempathy-gtk/empathy-main-window.c +++ b/libempathy-gtk/empathy-main-window.c @@ -50,6 +50,7 @@ #include "gossip-about-dialog.h" #include "gossip-new-chatroom-dialog.h" #include "gossip-chatrooms-window.h" +#include "gossip-log-window.h" #define DEBUG_DOMAIN "MainWindow" @@ -533,7 +534,7 @@ static void main_window_chat_history_cb (GtkWidget *widget, EmpathyMainWindow *window) { - //gossip_log_window_show (NULL, NULL); + gossip_log_window_show (NULL, NULL, FALSE, GTK_WINDOW (window->window)); } static void diff --git a/libempathy-gtk/gossip-chat-view.c b/libempathy-gtk/gossip-chat-view.c index 9fea2fbf1..e8db2628b 100644 --- a/libempathy-gtk/gossip-chat-view.c +++ b/libempathy-gtk/gossip-chat-view.c @@ -79,7 +79,7 @@ struct _GossipChatViewPriv { BlockType last_block_type; gboolean allow_scrolling; - guint scroll_src; + guint scroll_timeout; GTimer *scroll_time; gboolean is_group_chat; @@ -350,8 +350,8 @@ chat_view_finalize (GObject *object) if (priv->scroll_time) { g_timer_destroy (priv->scroll_time); } - if (priv->scroll_src) { - g_source_remove (priv->scroll_src); + if (priv->scroll_timeout) { + g_source_remove (priv->scroll_timeout); } G_OBJECT_CLASS (gossip_chat_view_parent_class)->finalize (object); @@ -382,7 +382,10 @@ chat_view_size_allocate (GtkWidget *widget, GTK_WIDGET_CLASS (gossip_chat_view_parent_class)->size_allocate (widget, alloc); if (down) { - gossip_chat_view_scroll_down (GOSSIP_CHAT_VIEW (widget)); + GtkAdjustment *adj; + + adj = GTK_TEXT_VIEW (widget)->vadjustment; + gtk_adjustment_set_value (adj, adj->upper - adj->page_size); } } @@ -1059,7 +1062,6 @@ chat_view_maybe_append_fancy_header (GossipChatView *view, { GossipChatViewPriv *priv; GossipContact *sender; - GossipContact *my_contact; const gchar *name; gboolean header; GtkTextIter iter; @@ -1075,9 +1077,8 @@ chat_view_maybe_append_fancy_header (GossipChatView *view, priv = GET_PRIV (view); sender = gossip_message_get_sender (msg); - my_contact = gossip_contact_get_user (sender); name = gossip_contact_get_name (sender); - from_self = gossip_contact_equal (sender, my_contact); + from_self = gossip_contact_is_user (sender); gossip_debug (DEBUG_DOMAIN, "Maybe add fancy header"); @@ -1186,7 +1187,6 @@ chat_view_append_irc_action (GossipChatView *view, GossipMessage *msg) { GossipChatViewPriv *priv; - GossipContact *my_contact; GossipContact *sender; const gchar *name; GtkTextIter iter; @@ -1199,11 +1199,9 @@ chat_view_append_irc_action (GossipChatView *view, gossip_debug (DEBUG_DOMAIN, "Add IRC action"); sender = gossip_message_get_sender (msg); - my_contact = gossip_contact_get_user (sender); name = gossip_contact_get_name (sender); - /* Skip the "/me ". */ - if (gossip_contact_equal (sender, my_contact)) { + if (gossip_contact_is_user (sender)) { tag = "irc-action-self"; } else { tag = "irc-action-other"; @@ -1236,7 +1234,6 @@ chat_view_append_fancy_action (GossipChatView *view, { GossipChatViewPriv *priv; GossipContact *sender; - GossipContact *my_contact; const gchar *name; const gchar *body; GtkTextIter iter; @@ -1249,10 +1246,9 @@ chat_view_append_fancy_action (GossipChatView *view, gossip_debug (DEBUG_DOMAIN, "Add fancy action"); sender = gossip_message_get_sender (msg); - my_contact = gossip_contact_get_user (sender); name = gossip_contact_get_name (sender); - if (gossip_contact_equal (sender, my_contact)) { + if (gossip_contact_is_user (sender)) { tag = "fancy-action-self"; line_tag = "fancy-line-self"; } else { @@ -1280,7 +1276,6 @@ chat_view_append_irc_message (GossipChatView *view, { GossipChatViewPriv *priv; GossipContact *sender; - GossipContact *my_contact; const gchar *name; const gchar *body; const gchar *nick_tag; @@ -1294,10 +1289,9 @@ chat_view_append_irc_message (GossipChatView *view, body = gossip_message_get_body (msg); sender = gossip_message_get_sender (msg); - my_contact = gossip_contact_get_user (sender); name = gossip_contact_get_name (sender); - if (gossip_contact_equal (sender, my_contact)) { + if (gossip_contact_is_user (sender)) { nick_tag = "irc-nick-self"; body_tag = "irc-body-self"; } else { @@ -1338,16 +1332,14 @@ chat_view_append_fancy_message (GossipChatView *view, { GossipChatViewPriv *priv; GossipContact *sender; - GossipContact *my_contact; const gchar *body; const gchar *tag; priv = GET_PRIV (view); sender = gossip_message_get_sender (msg); - my_contact = gossip_contact_get_user (sender); - if (gossip_contact_equal (sender, my_contact)) { + if (gossip_contact_is_user (sender)) { tag = "fancy-body-self"; } else { tag = "fancy-body-other"; @@ -1441,7 +1433,6 @@ gossip_chat_view_append_message (GossipChatView *view, { GossipChatViewPriv *priv; GossipContact *sender; - GossipContact *my_contact; const gchar *body; gboolean scroll_down; @@ -1480,14 +1471,12 @@ gossip_chat_view_append_message (GossipChatView *view, } } - my_contact = gossip_contact_get_user (sender); - /* Reset the last inserted contact. */ if (priv->last_contact) { g_object_unref (priv->last_contact); } - if (gossip_contact_equal (my_contact, sender)) { + if (gossip_contact_is_user (sender)) { priv->last_block_type = BLOCK_TYPE_SELF; priv->last_contact = NULL; } else { @@ -1658,7 +1647,7 @@ chat_view_scroll_cb (GossipChatView *view) gtk_adjustment_set_value (adj, max_val); g_timer_destroy (priv->scroll_time); priv->scroll_time = NULL; - priv->scroll_src = 0; + priv->scroll_timeout = 0; return FALSE; } @@ -1683,16 +1672,15 @@ gossip_chat_view_scroll_down (GossipChatView *view) gossip_debug (DEBUG_DOMAIN, "Scrolling down"); if (priv->scroll_time) { - g_timer_destroy (priv->scroll_time); + g_timer_reset (priv->scroll_time); + } else { + priv->scroll_time = g_timer_new(); } - if (priv->scroll_src) { - g_source_remove (priv->scroll_src); + if (!priv->scroll_timeout) { + priv->scroll_timeout = g_timeout_add (SCROLL_DELAY, + (GSourceFunc) chat_view_scroll_cb, + view); } - - priv->scroll_time = g_timer_new(); - priv->scroll_src = g_timeout_add (SCROLL_DELAY, - (GSourceFunc) chat_view_scroll_cb, - view); } gboolean diff --git a/libempathy-gtk/gossip-chat.c b/libempathy-gtk/gossip-chat.c index 0f6335617..657535d5a 100644 --- a/libempathy-gtk/gossip-chat.c +++ b/libempathy-gtk/gossip-chat.c @@ -341,9 +341,8 @@ static void chat_send (GossipChat *chat, const gchar *msg) { - GossipChatPriv *priv; - GossipMessage *message; - GossipContact *own_contact; + GossipChatPriv *priv; + GossipMessage *message; priv = GET_PRIV (chat); @@ -361,9 +360,7 @@ chat_send (GossipChat *chat, /* FIXME: add here something to let group/privrate chat handle * some special messages */ - own_contact = empathy_contact_manager_get_user (priv->manager, chat->account); message = gossip_message_new (msg); - gossip_message_set_sender (message, own_contact); empathy_tp_chat_send (priv->tp_chat, message); @@ -415,6 +412,7 @@ chat_message_received_cb (EmpathyTpChat *tp_chat, if (timestamp >= priv->time_joined) { empathy_log_manager_add_message (priv->log_manager, gossip_chat_get_id (chat), + gossip_chat_is_group_chat (chat), message); } @@ -1035,14 +1033,12 @@ chat_state_changed_cb (EmpathyTpChat *tp_chat, GossipChat *chat) { GossipChatPriv *priv; - GossipContact *own_contact; GList *l; gboolean was_composing; priv = GET_PRIV (chat); - own_contact = gossip_contact_get_user (contact); - if (gossip_contact_equal (own_contact, contact)) { + if (gossip_contact_is_user (contact)) { /* We don't care about our own chat state */ return; } @@ -1112,7 +1108,8 @@ chat_add_logs (GossipChat *chat) /* Add messages from last conversation */ messages = empathy_log_manager_get_last_messages (priv->log_manager, chat->account, - gossip_chat_get_id (chat)); + gossip_chat_get_id (chat), + gossip_chat_is_group_chat (chat)); num_messages = g_list_length (messages); for (l = messages, i = 0; l; l = l->next, i++) { @@ -1498,7 +1495,7 @@ gossip_chat_should_play_sound (GossipChat *chat) gboolean gossip_chat_should_highlight_nick (GossipMessage *message) { - GossipContact *my_contact; + GossipContact *contact; const gchar *msg, *to; gchar *cf_msg, *cf_to; gchar *ch; @@ -1515,8 +1512,12 @@ gossip_chat_should_highlight_nick (GossipMessage *message) return FALSE; } - my_contact = gossip_contact_get_user (gossip_message_get_sender (message)); - to = gossip_contact_get_name (my_contact); + contact = gossip_message_get_receiver (message); + if (!contact || !gossip_contact_is_user (contact)) { + return FALSE; + } + + to = gossip_contact_get_name (contact); if (!to) { return FALSE; } diff --git a/libempathy-gtk/gossip-contact-list-store.c b/libempathy-gtk/gossip-contact-list-store.c index 4a5b5506e..887ce13e4 100644 --- a/libempathy-gtk/gossip-contact-list-store.c +++ b/libempathy-gtk/gossip-contact-list-store.c @@ -57,6 +57,7 @@ struct _GossipContactListStorePriv { gboolean is_compact; gboolean show_active; GossipContactListStoreSort sort_criterium; + guint inhibit_active; GossipContactGroupsFunc get_contact_groups; gpointer get_contact_groups_data; @@ -92,6 +93,7 @@ static void contact_list_store_set_property (GObject const GValue *value, GParamSpec *pspec); static void contact_list_store_setup (GossipContactListStore *store); +static gboolean contact_list_store_inibit_active_cb (GossipContactListStore *store); static void contact_list_store_contact_added_cb (EmpathyContactList *list_iface, GossipContact *contact, GossipContactListStore *store); @@ -231,9 +233,9 @@ gossip_contact_list_store_init (GossipContactListStore *store) priv = GET_PRIV (store); - priv->is_compact = FALSE; - priv->show_active = TRUE; - priv->show_avatars = TRUE; + priv->inhibit_active = g_timeout_add (1000, + (GSourceFunc) contact_list_store_inibit_active_cb, + store); } static void @@ -249,6 +251,10 @@ contact_list_store_finalize (GObject *object) g_object_unref (priv->list); } + if (priv->inhibit_active) { + g_source_remove (priv->inhibit_active); + } + G_OBJECT_CLASS (gossip_contact_list_store_parent_class)->finalize (object); } @@ -320,7 +326,6 @@ gossip_contact_list_store_new (EmpathyContactList *list_iface) GossipContactListStore *store; GossipContactListStorePriv *priv; GList *contacts, *l; - gboolean show_active; g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list_iface), NULL); @@ -340,9 +345,7 @@ gossip_contact_list_store_new (EmpathyContactList *list_iface) G_CALLBACK (contact_list_store_contact_removed_cb), store); - /* Add contacts already created. Do not highlight them. */ - show_active = priv->show_active; - priv->show_active = FALSE; + /* Add contacts already created. */ contacts = empathy_contact_list_get_members (priv->list); for (l = contacts; l; l = l->next) { GossipContact *contact; @@ -354,7 +357,6 @@ gossip_contact_list_store_new (EmpathyContactList *list_iface) g_object_unref (contact); } g_list_free (contacts); - priv->show_active = show_active; return store; } @@ -681,6 +683,14 @@ void gossip_contact_list_store_update_contact_groups (GossipContactListStore *store, GossipContact *contact) { + GossipContactListStorePriv *priv; + gboolean show_active; + + g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store)); + g_return_if_fail (GOSSIP_IS_CONTACT (contact)); + + priv = GET_PRIV (store); + gossip_debug (DEBUG_DOMAIN, "Contact:'%s' updating groups", gossip_contact_get_name (contact)); @@ -688,8 +698,11 @@ gossip_contact_list_store_update_contact_groups (GossipContactListStore *store, * would have to check the groups already set up for each * contact and then see what has been updated. */ + show_active = priv->show_active; + priv->show_active = FALSE; contact_list_store_remove_contact (store, contact); contact_list_store_add_contact (store, contact); + priv->show_active = show_active; } static void @@ -726,6 +739,19 @@ contact_list_store_setup (GossipContactListStore *store) gossip_contact_list_store_set_sort_criterium (store, priv->sort_criterium); } +static gboolean +contact_list_store_inibit_active_cb (GossipContactListStore *store) +{ + GossipContactListStorePriv *priv; + + priv = GET_PRIV (store); + + priv->show_active = TRUE; + priv->inhibit_active = 0; + + return FALSE; +} + static void contact_list_store_contact_added_cb (EmpathyContactList *list_iface, GossipContact *contact, diff --git a/libempathy-gtk/gossip-log-window.c b/libempathy-gtk/gossip-log-window.c new file mode 100644 index 000000000..3ec7cdb1d --- /dev/null +++ b/libempathy-gtk/gossip-log-window.c @@ -0,0 +1,1153 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2006-2007 Imendio AB + * Copyright (C) 2007 Collabora Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Martyn Russell <martyn@imendio.com> + * Xavier Claessens <xclaesse@gmail.com> + */ + +#include "config.h" + +#include <string.h> +#include <stdlib.h> + +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <glade/glade.h> + +#include <libempathy/empathy-log-manager.h> +#include <libempathy/gossip-chatroom-manager.h> +#include <libempathy/gossip-chatroom.h> +#include <libempathy/gossip-message.h> +#include <libempathy/gossip-debug.h> +#include <libempathy/gossip-utils.h> +#include <libempathy/gossip-time.h> + +#include "gossip-log-window.h" +#include "gossip-account-chooser.h" +#include "gossip-chat-view.h" +#include "gossip-ui-utils.h" + +#define DEBUG_DOMAIN "LogWindow" + +typedef struct { + GtkWidget *window; + + GtkWidget *notebook; + + GtkWidget *entry_find; + GtkWidget *button_find; + GtkWidget *treeview_find; + GtkWidget *scrolledwindow_find; + GossipChatView *chatview_find; + GtkWidget *button_previous; + GtkWidget *button_next; + + GtkWidget *vbox_chats; + GtkWidget *account_chooser_chats; + GtkWidget *entry_chats; + GtkWidget *calendar_chats; + GtkWidget *treeview_chats; + GtkWidget *scrolledwindow_chats; + GossipChatView *chatview_chats; + + gchar *last_find; + + EmpathyLogManager *log_manager; +} GossipLogWindow; + +static void +log_window_destroy_cb (GtkWidget *widget, + GossipLogWindow *window); +static void +log_window_entry_find_changed_cb (GtkWidget *entry, + GossipLogWindow *window); +static void +log_window_find_changed_cb (GtkTreeSelection *selection, + GossipLogWindow *window); +static void +log_window_find_populate (GossipLogWindow *window, + const gchar *search_criteria); +static void +log_window_find_setup (GossipLogWindow *window); +static void +log_window_button_find_clicked_cb (GtkWidget *widget, + GossipLogWindow *window); +static void +log_window_button_next_clicked_cb (GtkWidget *widget, + GossipLogWindow *window); +static void +log_window_button_previous_clicked_cb (GtkWidget *widget, + GossipLogWindow *window); +static void +log_window_chats_changed_cb (GtkTreeSelection *selection, + GossipLogWindow *window); +static void +log_window_chats_populate (GossipLogWindow *window); +static void +log_window_chats_setup (GossipLogWindow *window); +static void +log_window_chats_accounts_changed_cb (GtkWidget *combobox, + GossipLogWindow *window); +static void +log_window_chats_new_message_cb (GossipContact *own_contact, + GossipMessage *message, + GossipLogWindow *window); +//static gboolean +//log_window_chats_is_today_selected (GossipLogWindow *window); +static void +log_window_chats_set_selected (GossipLogWindow *window, + McAccount *account, + const gchar *chat_id, + gboolean is_chatroom); +static gboolean +log_window_chats_get_selected (GossipLogWindow *window, + McAccount **account, + gchar **chat_id, + gboolean *is_chatroom); +static void +log_window_chats_get_messages (GossipLogWindow *window, + const gchar *date_to_show); +static void +log_window_calendar_chats_day_selected_cb (GtkWidget *calendar, + GossipLogWindow *window); +static void +log_window_calendar_chats_month_changed_cb (GtkWidget *calendar, + GossipLogWindow *window); +static void +log_window_entry_chats_changed_cb (GtkWidget *entry, + GossipLogWindow *window); +static void +log_window_entry_chats_activate_cb (GtkWidget *entry, + GossipLogWindow *window); + +enum { + COL_FIND_ACCOUNT_ICON, + COL_FIND_ACCOUNT_NAME, + COL_FIND_ACCOUNT, + COL_FIND_CHAT_NAME, + COL_FIND_CHAT_ID, + COL_FIND_IS_CHATROOM, + COL_FIND_DATE, + COL_FIND_DATE_READABLE, + COL_FIND_COUNT +}; + +enum { + COL_CHAT_ICON, + COL_CHAT_NAME, + COL_CHAT_ACCOUNT, + COL_CHAT_ID, + COL_CHAT_IS_CHATROOM, + COL_CHAT_COUNT +}; + +void +gossip_log_window_show (McAccount *account, + const gchar *chat_id, + gboolean is_chatroom, + GtkWindow *parent) +{ + static GossipLogWindow *window = NULL; + GossipAccountChooser *account_chooser; + GList *accounts; + gint account_num; + GladeXML *glade; + + if (window) { + gtk_window_present (GTK_WINDOW (window->window)); + + if (account && chat_id) { + gtk_notebook_set_current_page (GTK_NOTEBOOK (window->notebook), 1); + log_window_chats_set_selected (window, account, + chat_id, is_chatroom); + } + + return; + } + + window = g_new0 (GossipLogWindow, 1); + window->log_manager = empathy_log_manager_new (); + + glade = gossip_glade_get_file ("gossip-log-window.glade", + "log_window", + NULL, + "log_window", &window->window, + "notebook", &window->notebook, + "entry_find", &window->entry_find, + "button_find", &window->button_find, + "treeview_find", &window->treeview_find, + "scrolledwindow_find", &window->scrolledwindow_find, + "button_previous", &window->button_previous, + "button_next", &window->button_next, + "entry_chats", &window->entry_chats, + "calendar_chats", &window->calendar_chats, + "vbox_chats", &window->vbox_chats, + "treeview_chats", &window->treeview_chats, + "scrolledwindow_chats", &window->scrolledwindow_chats, + NULL); + gossip_glade_connect (glade, + window, + "log_window", "destroy", log_window_destroy_cb, + "entry_find", "changed", log_window_entry_find_changed_cb, + "button_previous", "clicked", log_window_button_previous_clicked_cb, + "button_next", "clicked", log_window_button_next_clicked_cb, + "button_find", "clicked", log_window_button_find_clicked_cb, + "entry_chats", "changed", log_window_entry_chats_changed_cb, + "entry_chats", "activate", log_window_entry_chats_activate_cb, + NULL); + + g_object_unref (glade); + + g_object_add_weak_pointer (G_OBJECT (window->window), + (gpointer) &window); + + /* We set this up here so we can block it when needed. */ + g_signal_connect (window->calendar_chats, "day-selected", + G_CALLBACK (log_window_calendar_chats_day_selected_cb), + window); + g_signal_connect (window->calendar_chats, "month-changed", + G_CALLBACK (log_window_calendar_chats_month_changed_cb), + window); + + /* Configure Search GossipChatView */ + window->chatview_find = gossip_chat_view_new (); + gtk_container_add (GTK_CONTAINER (window->scrolledwindow_find), + GTK_WIDGET (window->chatview_find)); + gtk_widget_show (GTK_WIDGET (window->chatview_find)); + + /* Configure Contacts GossipChatView */ + window->chatview_chats = gossip_chat_view_new (); + gtk_container_add (GTK_CONTAINER (window->scrolledwindow_chats), + GTK_WIDGET (window->chatview_chats)); + gtk_widget_show (GTK_WIDGET (window->chatview_chats)); + + /* Account chooser for chats */ + window->account_chooser_chats = gossip_account_chooser_new (); + account_chooser = GOSSIP_ACCOUNT_CHOOSER (window->account_chooser_chats); + gossip_account_chooser_set_can_select_all (account_chooser, TRUE); + + gtk_box_pack_start (GTK_BOX (window->vbox_chats), + window->account_chooser_chats, + FALSE, TRUE, 0); + + g_signal_connect (window->account_chooser_chats, "changed", + G_CALLBACK (log_window_chats_accounts_changed_cb), + window); + + /* Populate */ + accounts = mc_accounts_list (); + account_num = g_list_length (accounts); + mc_accounts_list_free (accounts); + + if (account_num > 1) { + gtk_widget_show (window->vbox_chats); + gtk_widget_show (window->account_chooser_chats); + } else { + gtk_widget_hide (window->vbox_chats); + gtk_widget_hide (window->account_chooser_chats); + } + + /* Search List */ + log_window_find_setup (window); + + /* Contacts */ + log_window_chats_setup (window); + log_window_chats_populate (window); + + /* Select chat */ + if (account && chat_id) { + gtk_notebook_set_current_page (GTK_NOTEBOOK (window->notebook), 1); + log_window_chats_set_selected (window, account, + chat_id, is_chatroom); + } + + if (parent) { + gtk_window_set_transient_for (GTK_WINDOW (window->window), + GTK_WINDOW (parent)); + } + + gtk_widget_show (window->window); +} + +static void +log_window_destroy_cb (GtkWidget *widget, + GossipLogWindow *window) +{ + g_signal_handlers_disconnect_by_func (window->log_manager, + log_window_chats_new_message_cb, + window); + + g_free (window->last_find); + g_object_unref (window->log_manager); + + g_free (window); +} + +/* + * Search code. + */ +static void +log_window_entry_find_changed_cb (GtkWidget *entry, + GossipLogWindow *window) +{ + const gchar *str; + gboolean is_sensitive = TRUE; + + str = gtk_entry_get_text (GTK_ENTRY (window->entry_find)); + + is_sensitive &= !G_STR_EMPTY (str); + is_sensitive &= + !window->last_find || + (window->last_find && strcmp (window->last_find, str) != 0); + + gtk_widget_set_sensitive (window->button_find, is_sensitive); +} + +static void +log_window_find_changed_cb (GtkTreeSelection *selection, + GossipLogWindow *window) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeIter iter; + McAccount *account; + gchar *chat_id; + gboolean is_chatroom; + gchar *date; + GossipMessage *message; + GList *messages; + GList *l; + gboolean can_do_previous; + gboolean can_do_next; + + /* Get selected information */ + view = GTK_TREE_VIEW (window->treeview_find); + model = gtk_tree_view_get_model (view); + + if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) { + gtk_widget_set_sensitive (window->button_previous, FALSE); + gtk_widget_set_sensitive (window->button_next, FALSE); + + gossip_chat_view_clear (window->chatview_find); + + return; + } + + gtk_widget_set_sensitive (window->button_previous, TRUE); + gtk_widget_set_sensitive (window->button_next, TRUE); + + gtk_tree_model_get (model, &iter, + COL_FIND_ACCOUNT, &account, + COL_FIND_CHAT_ID, &chat_id, + COL_FIND_IS_CHATROOM, &is_chatroom, + COL_FIND_DATE, &date, + -1); + + /* Clear all current messages shown in the textview */ + gossip_chat_view_clear (window->chatview_find); + + /* Turn off scrolling temporarily */ + gossip_chat_view_scroll (window->chatview_find, FALSE); + + /* Get messages */ + messages = empathy_log_manager_get_messages_for_date (window->log_manager, + account, + chat_id, + is_chatroom, + date); + g_object_unref (account); + g_free (date); + g_free (chat_id); + + for (l = messages; l; l = l->next) { + message = l->data; + gossip_chat_view_append_message (window->chatview_find, message); + g_object_unref (message); + } + g_list_free (messages); + + /* Scroll to the most recent messages */ + gossip_chat_view_scroll (window->chatview_find, TRUE); + + /* Highlight and find messages */ + gossip_chat_view_highlight (window->chatview_find, + window->last_find); + gossip_chat_view_find_next (window->chatview_find, + window->last_find, + TRUE); + gossip_chat_view_find_abilities (window->chatview_find, + window->last_find, + &can_do_previous, + &can_do_next); + gtk_widget_set_sensitive (window->button_previous, can_do_previous); + gtk_widget_set_sensitive (window->button_next, can_do_next); + gtk_widget_set_sensitive (window->button_find, FALSE); +} + +static void +log_window_find_populate (GossipLogWindow *window, + const gchar *search_criteria) +{ + GList *hits, *l; + + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkListStore *store; + GtkTreeIter iter; + + view = GTK_TREE_VIEW (window->treeview_find); + model = gtk_tree_view_get_model (view); + selection = gtk_tree_view_get_selection (view); + store = GTK_LIST_STORE (model); + + gossip_chat_view_clear (window->chatview_find); + + gtk_list_store_clear (store); + + if (G_STR_EMPTY (search_criteria)) { + /* Just clear the search. */ + return; + } + + hits = empathy_log_manager_search_new (window->log_manager, search_criteria); + + for (l = hits; l; l = l->next) { + EmpathyLogSearchHit *hit; + const gchar *account_name; + const gchar *account_icon; + gchar *date_readable; + + hit = l->data; + + /* Protect against invalid data (corrupt or old log files. */ + if (!hit->account || !hit->chat_id) { + continue; + } + + date_readable = empathy_log_manager_get_date_readable (hit->date); + account_name = mc_account_get_display_name (hit->account); + account_icon = gossip_icon_name_from_account (hit->account); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COL_FIND_ACCOUNT_ICON, account_icon, + COL_FIND_ACCOUNT_NAME, account_name, + COL_FIND_ACCOUNT, hit->account, + COL_FIND_CHAT_NAME, hit->chat_id, /* FIXME */ + COL_FIND_CHAT_ID, hit->chat_id, + COL_FIND_IS_CHATROOM, hit->is_chatroom, + COL_FIND_DATE, hit->date, + COL_FIND_DATE_READABLE, date_readable, + -1); + + g_free (date_readable); + + /* FIXME: Update COL_FIND_CHAT_NAME */ + if (hit->is_chatroom) { + } else { + } + } + + if (hits) { + empathy_log_manager_search_free (hits); + } +} + +static void +log_window_find_setup (GossipLogWindow *window) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeSortable *sortable; + GtkTreeViewColumn *column; + GtkListStore *store; + GtkCellRenderer *cell; + gint offset; + + view = GTK_TREE_VIEW (window->treeview_find); + selection = gtk_tree_view_get_selection (view); + + /* New store */ + store = gtk_list_store_new (COL_FIND_COUNT, + G_TYPE_STRING, /* account icon name */ + G_TYPE_STRING, /* account name */ + MC_TYPE_ACCOUNT, /* account */ + G_TYPE_STRING, /* chat name */ + G_TYPE_STRING, /* chat id */ + G_TYPE_BOOLEAN, /* is chatroom */ + G_TYPE_STRING, /* date */ + G_TYPE_STRING); /* date_readable */ + + model = GTK_TREE_MODEL (store); + sortable = GTK_TREE_SORTABLE (store); + + gtk_tree_view_set_model (view, model); + + /* New column */ + column = gtk_tree_view_column_new (); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_add_attribute (column, cell, + "icon-name", + COL_FIND_ACCOUNT_ICON); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, + "text", + COL_FIND_ACCOUNT_NAME); + + gtk_tree_view_column_set_title (column, _("Account")); + gtk_tree_view_append_column (view, column); + + gtk_tree_view_column_set_resizable (column, TRUE); + gtk_tree_view_column_set_clickable (column, TRUE); + + cell = gtk_cell_renderer_text_new (); + offset = gtk_tree_view_insert_column_with_attributes (view, -1, _("Conversation"), + cell, "text", COL_FIND_CHAT_NAME, + NULL); + + column = gtk_tree_view_get_column (view, offset - 1); + gtk_tree_view_column_set_sort_column_id (column, COL_FIND_CHAT_NAME); + gtk_tree_view_column_set_resizable (column, TRUE); + gtk_tree_view_column_set_clickable (column, TRUE); + + cell = gtk_cell_renderer_text_new (); + offset = gtk_tree_view_insert_column_with_attributes (view, -1, _("Date"), + cell, "text", COL_FIND_DATE_READABLE, + NULL); + + column = gtk_tree_view_get_column (view, offset - 1); + gtk_tree_view_column_set_sort_column_id (column, COL_FIND_DATE); + gtk_tree_view_column_set_resizable (column, TRUE); + gtk_tree_view_column_set_clickable (column, TRUE); + + /* Set up treeview properties */ + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + gtk_tree_sortable_set_sort_column_id (sortable, + COL_FIND_DATE, + GTK_SORT_ASCENDING); + + /* Set up signals */ + g_signal_connect (selection, "changed", + G_CALLBACK (log_window_find_changed_cb), + window); + + g_object_unref (store); +} + +static void +log_window_button_find_clicked_cb (GtkWidget *widget, + GossipLogWindow *window) +{ + const gchar *str; + + str = gtk_entry_get_text (GTK_ENTRY (window->entry_find)); + + /* Don't find the same crap again */ + if (window->last_find && strcmp (window->last_find, str) == 0) { + return; + } + + g_free (window->last_find); + window->last_find = g_strdup (str); + + log_window_find_populate (window, str); +} + +static void +log_window_button_next_clicked_cb (GtkWidget *widget, + GossipLogWindow *window) +{ + if (window->last_find) { + gboolean can_do_previous; + gboolean can_do_next; + + gossip_chat_view_find_next (window->chatview_find, + window->last_find, + FALSE); + gossip_chat_view_find_abilities (window->chatview_find, + window->last_find, + &can_do_previous, + &can_do_next); + gtk_widget_set_sensitive (window->button_previous, can_do_previous); + gtk_widget_set_sensitive (window->button_next, can_do_next); + } +} + +static void +log_window_button_previous_clicked_cb (GtkWidget *widget, + GossipLogWindow *window) +{ + if (window->last_find) { + gboolean can_do_previous; + gboolean can_do_next; + + gossip_chat_view_find_previous (window->chatview_find, + window->last_find, + FALSE); + gossip_chat_view_find_abilities (window->chatview_find, + window->last_find, + &can_do_previous, + &can_do_next); + gtk_widget_set_sensitive (window->button_previous, can_do_previous); + gtk_widget_set_sensitive (window->button_next, can_do_next); + } +} + +/* + * Chats Code + */ + +static void +log_window_chats_changed_cb (GtkTreeSelection *selection, + GossipLogWindow *window) +{ + /* Use last date by default */ + gtk_calendar_clear_marks (GTK_CALENDAR (window->calendar_chats)); + + log_window_chats_get_messages (window, NULL); +} + +static void +log_window_chats_populate (GossipLogWindow *window) +{ + GossipAccountChooser *account_chooser; + McAccount *account; + GList *chats, *l; + + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkListStore *store; + GtkTreeIter iter; + + account_chooser = GOSSIP_ACCOUNT_CHOOSER (window->account_chooser_chats); + account = gossip_account_chooser_get_account (account_chooser); + + view = GTK_TREE_VIEW (window->treeview_chats); + model = gtk_tree_view_get_model (view); + selection = gtk_tree_view_get_selection (view); + store = GTK_LIST_STORE (model); + + /* Block signals to stop the logs being retrieved prematurely */ + g_signal_handlers_block_by_func (selection, + log_window_chats_changed_cb, + window); + + gtk_list_store_clear (store); + + chats = empathy_log_manager_get_chats (window->log_manager, account); + for (l = chats; l; l = l->next) { + EmpathyLogSearchHit *hit; + + hit = l->data; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COL_CHAT_ICON, "empathy-available", + COL_CHAT_NAME, hit->chat_id, + COL_CHAT_ACCOUNT, account, + COL_CHAT_ID, hit->chat_id, + COL_CHAT_IS_CHATROOM, hit->is_chatroom, + -1); + + /* FIXME: Update COL_CHAT_ICON/NAME */ + if (hit->is_chatroom) { + } else { + } + } + empathy_log_manager_search_free (chats); + + /* Unblock signals */ + g_signal_handlers_unblock_by_func (selection, + log_window_chats_changed_cb, + window); + + + g_object_unref (account); +} + +static void +log_window_chats_setup (GossipLogWindow *window) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeSortable *sortable; + GtkTreeViewColumn *column; + GtkListStore *store; + GtkCellRenderer *cell; + + view = GTK_TREE_VIEW (window->treeview_chats); + selection = gtk_tree_view_get_selection (view); + + /* new store */ + store = gtk_list_store_new (COL_CHAT_COUNT, + G_TYPE_STRING, /* icon */ + G_TYPE_STRING, /* name */ + MC_TYPE_ACCOUNT, /* account */ + G_TYPE_STRING, /* id */ + G_TYPE_BOOLEAN); /* is chatroom */ + + model = GTK_TREE_MODEL (store); + sortable = GTK_TREE_SORTABLE (store); + + gtk_tree_view_set_model (view, model); + + /* new column */ + column = gtk_tree_view_column_new (); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_add_attribute (column, cell, + "icon-name", + COL_CHAT_ICON); + + 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_CHAT_NAME); + + gtk_tree_view_append_column (view, column); + + /* set up treeview properties */ + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + gtk_tree_sortable_set_sort_column_id (sortable, + COL_CHAT_NAME, + GTK_SORT_ASCENDING); + + /* set up signals */ + g_signal_connect (selection, "changed", + G_CALLBACK (log_window_chats_changed_cb), + window); + + g_object_unref (store); +} + +static void +log_window_chats_accounts_changed_cb (GtkWidget *combobox, + GossipLogWindow *window) +{ + /* Clear all current messages shown in the textview */ + gossip_chat_view_clear (window->chatview_chats); + + log_window_chats_populate (window); +} + +static void +log_window_chats_new_message_cb (GossipContact *own_contact, + GossipMessage *message, + GossipLogWindow *window) +{ + gossip_chat_view_append_message (window->chatview_chats, message); + + /* Scroll to the most recent messages */ + gossip_chat_view_scroll_down (window->chatview_chats); +} + +#if 0 +static gboolean +log_window_chats_is_today_selected (GossipLogWindow *window) +{ + GossipTime t; + gchar *timestamp; + guint year_selected; + guint year; + guint month; + guint month_selected; + guint day; + guint day_selected; + gboolean selected; + + t = gossip_time_get_current (); + timestamp = gossip_time_to_string_local (t, "%Y%m%d"); + + sscanf (timestamp, "%4d%2d%2d", &year, &month, &day); + + gtk_calendar_get_date (GTK_CALENDAR (window->calendar_chats), + &year_selected, + &month_selected, + &day_selected); + + /* Hack since this starts at 0 */ + month_selected++; + + selected = (day_selected == day && + month_selected == month && + year_selected == year); + + g_free (timestamp); + + return selected; +} +#endif + +static void +log_window_chats_set_selected (GossipLogWindow *window, + McAccount *account, + const gchar *chat_id, + gboolean is_chatroom) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + GtkTreePath *path; + gboolean ok; + + view = GTK_TREE_VIEW (window->treeview_chats); + model = gtk_tree_view_get_model (view); + selection = gtk_tree_view_get_selection (view); + + if (!gtk_tree_model_get_iter_first (model, &iter)) { + return; + } + + for (ok = TRUE; ok; ok = gtk_tree_model_iter_next (model, &iter)) { + McAccount *this_account; + gchar *this_chat_id; + gboolean this_is_chatroom; + + gtk_tree_model_get (model, &iter, + COL_CHAT_ACCOUNT, &this_account, + COL_CHAT_ID, &this_chat_id, + COL_CHAT_IS_CHATROOM, &this_is_chatroom, + -1); + + if (gossip_account_equal (this_account, account) && + strcmp (this_chat_id, chat_id) == 0 && + this_is_chatroom == is_chatroom) { + gtk_tree_selection_select_iter (selection, &iter); + path = gtk_tree_model_get_path (model, &iter); + gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 0.5, 0.0); + gtk_tree_path_free (path); + g_object_unref (this_account); + g_free (this_chat_id); + break; + } + + g_object_unref (this_account); + g_free (this_chat_id); + } +} + +static gboolean +log_window_chats_get_selected (GossipLogWindow *window, + McAccount **account, + gchar **chat_id, + gboolean *is_chatroom) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + gchar *id = NULL; + McAccount *acc = NULL; + gboolean room = FALSE; + + view = GTK_TREE_VIEW (window->treeview_chats); + model = gtk_tree_view_get_model (view); + selection = gtk_tree_view_get_selection (view); + + if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) { + return FALSE; + } + + gtk_tree_model_get (model, &iter, + COL_CHAT_ACCOUNT, &acc, + COL_CHAT_ID, &id, + COL_CHAT_IS_CHATROOM, &room, + -1); + + if (chat_id) { + *chat_id = id; + } else { + g_free (id); + } + if (account) { + *account = acc; + } else { + g_object_unref (acc); + } + if (is_chatroom) { + *is_chatroom = room; + } + + return TRUE; +} + +static void +log_window_chats_get_messages (GossipLogWindow *window, + const gchar *date_to_show) +{ + McAccount *account; + gchar *chat_id; + gboolean is_chatroom; + GossipMessage *message; + GList *messages; + GList *dates = NULL; + GList *l; + const gchar *date; + guint year_selected; + guint year; + guint month; + guint month_selected; + guint day; + + if (!log_window_chats_get_selected (window, &account, + &chat_id, &is_chatroom)) { + return; + } + + g_signal_handlers_block_by_func (window->calendar_chats, + log_window_calendar_chats_day_selected_cb, + window); + + /* Either use the supplied date or get the last */ + date = date_to_show; + if (!date) { + gboolean day_selected = FALSE; + + /* Get a list of dates and show them on the calendar */ + dates = empathy_log_manager_get_dates (window->log_manager, + account, chat_id, + is_chatroom); + + for (l = dates; l; l = l->next) { + const gchar *str; + + str = l->data; + if (!str) { + continue; + } + + sscanf (str, "%4d%2d%2d", &year, &month, &day); + gtk_calendar_get_date (GTK_CALENDAR (window->calendar_chats), + &year_selected, + &month_selected, + NULL); + + month_selected++; + + if (!l->next) { + date = str; + } + + if (year != year_selected || month != month_selected) { + continue; + } + + + gossip_debug (DEBUG_DOMAIN, "Marking date:'%s'", str); + gtk_calendar_mark_day (GTK_CALENDAR (window->calendar_chats), day); + + if (l->next) { + continue; + } + + day_selected = TRUE; + + gtk_calendar_select_day (GTK_CALENDAR (window->calendar_chats), day); + } + + if (!day_selected) { + /* Unselect the day in the calendar */ + gtk_calendar_select_day (GTK_CALENDAR (window->calendar_chats), 0); + } + } else { + sscanf (date, "%4d%2d%2d", &year, &month, &day); + gtk_calendar_get_date (GTK_CALENDAR (window->calendar_chats), + &year_selected, + &month_selected, + NULL); + + month_selected++; + + if (year != year_selected && month != month_selected) { + day = 0; + } + + gtk_calendar_select_day (GTK_CALENDAR (window->calendar_chats), day); + } + g_signal_handlers_unblock_by_func (window->calendar_chats, + log_window_calendar_chats_day_selected_cb, + window); + + /* Clear all current messages shown in the textview */ + gossip_chat_view_clear (window->chatview_chats); + + /* Turn off scrolling temporarily */ + gossip_chat_view_scroll (window->chatview_find, FALSE); + + /* Get messages */ + messages = empathy_log_manager_get_messages_for_date (window->log_manager, + account, chat_id, + is_chatroom, + date); + + for (l = messages; l; l = l->next) { + message = l->data; + + gossip_chat_view_append_message (window->chatview_chats, + message); + g_object_unref (message); + } + g_list_free (messages); + + g_list_foreach (dates, (GFunc) g_free, NULL); + g_list_free (dates); + + g_object_unref (account); + g_free (chat_id); + + /* Turn back on scrolling */ + gossip_chat_view_scroll (window->chatview_find, TRUE); + + /* Scroll to the most recent messages */ + gossip_chat_view_scroll_down (window->chatview_chats); + + /* Give the search entry main focus */ + gtk_widget_grab_focus (window->entry_chats); +} + +static void +log_window_calendar_chats_day_selected_cb (GtkWidget *calendar, + GossipLogWindow *window) +{ + guint year; + guint month; + guint day; + + gchar *date; + + gtk_calendar_get_date (GTK_CALENDAR (calendar), &year, &month, &day); + + /* We need this hear because it appears that the months start from 0 */ + month++; + + date = g_strdup_printf ("%4.4d%2.2d%2.2d", year, month, day); + + gossip_debug (DEBUG_DOMAIN, "Currently selected date is:'%s'", date); + + log_window_chats_get_messages (window, date); + + g_free (date); +} + +static void +log_window_calendar_chats_month_changed_cb (GtkWidget *calendar, + GossipLogWindow *window) +{ + McAccount *account; + gchar *chat_id; + gboolean is_chatroom; + guint year_selected; + guint month_selected; + + GList *dates; + GList *l; + + gtk_calendar_clear_marks (GTK_CALENDAR (calendar)); + + if (!log_window_chats_get_selected (window, &account, + &chat_id, &is_chatroom)) { + gossip_debug (DEBUG_DOMAIN, "No chat selected to get dates for..."); + return; + } + + g_object_get (calendar, + "month", &month_selected, + "year", &year_selected, + NULL); + + /* We need this hear because it appears that the months start from 0 */ + month_selected++; + + /* Get the log object for this contact */ + dates = empathy_log_manager_get_dates (window->log_manager, account, + chat_id, is_chatroom); + g_object_unref (account); + g_free (chat_id); + + for (l = dates; l; l = l->next) { + const gchar *str; + guint year; + guint month; + guint day; + + str = l->data; + if (!str) { + continue; + } + + sscanf (str, "%4d%2d%2d", &year, &month, &day); + + if (year == year_selected && month == month_selected) { + gossip_debug (DEBUG_DOMAIN, "Marking date:'%s'", str); + gtk_calendar_mark_day (GTK_CALENDAR (window->calendar_chats), day); + } + } + + g_list_foreach (dates, (GFunc) g_free, NULL); + g_list_free (dates); + + gossip_debug (DEBUG_DOMAIN, + "Currently showing month %d and year %d", + month_selected, year_selected); +} + +static void +log_window_entry_chats_changed_cb (GtkWidget *entry, + GossipLogWindow *window) +{ + const gchar *str; + + str = gtk_entry_get_text (GTK_ENTRY (window->entry_chats)); + gossip_chat_view_highlight (window->chatview_chats, str); + + if (str) { + gossip_chat_view_find_next (window->chatview_chats, + str, + TRUE); + } +} + +static void +log_window_entry_chats_activate_cb (GtkWidget *entry, + GossipLogWindow *window) +{ + const gchar *str; + + str = gtk_entry_get_text (GTK_ENTRY (window->entry_chats)); + + if (str) { + gossip_chat_view_find_next (window->chatview_chats, + str, + FALSE); + } +} + diff --git a/libempathy-gtk/gossip-log-window.glade b/libempathy-gtk/gossip-log-window.glade new file mode 100644 index 000000000..4309a3544 --- /dev/null +++ b/libempathy-gtk/gossip-log-window.glade @@ -0,0 +1,470 @@ +<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> +<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd"> + +<glade-interface> + +<widget class="GtkWindow" id="log_window"> + <property name="title" translatable="yes">View Previous Conversations</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">640</property> + <property name="default_height">450</property> + <property name="resizable">True</property> + <property name="destroy_with_parent">False</property> + <property name="icon_name">gtk-justify-left</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="GtkNotebook" id="notebook"> + <property name="border_width">2</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="show_tabs">True</property> + <property name="show_border">True</property> + <property name="tab_pos">GTK_POS_TOP</property> + <property name="scrollable">False</property> + <property name="enable_popup">False</property> + + <child> + <widget class="GtkVBox" id="vbox192"> + <property name="border_width">12</property> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + + <child> + <widget class="GtkHBox" id="hbox144"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + + <child> + <widget class="GtkLabel" id="label628"> + <property name="visible">True</property> + <property name="label" translatable="yes">_For:</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="mnemonic_widget">entry_find</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_find"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char">*</property> + <property name="activates_default">True</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + + <child> + <widget class="GtkButton" id="button_find"> + <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">gtk-find</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">False</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkVPaned" id="vpaned1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="position">120</property> + + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow14"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_NEVER</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <property name="window_placement">GTK_CORNER_TOP_LEFT</property> + + <child> + <widget class="GtkTreeView" id="treeview_find"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">True</property> + <property name="rules_hint">False</property> + <property name="reorderable">False</property> + <property name="enable_search">False</property> + <property name="fixed_height_mode">False</property> + <property name="hover_selection">False</property> + <property name="hover_expand">False</property> + </widget> + </child> + </widget> + <packing> + <property name="shrink">True</property> + <property name="resize">False</property> + </packing> + </child> + + <child> + <widget class="GtkVBox" id="vbox215"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow_find"> + <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_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="GtkHBox" id="hbox171"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">12</property> + + <child> + <placeholder/> + </child> + + <child> + <widget class="GtkButton" id="button_next"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="label">gtk-media-next</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">False</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + + <child> + <widget class="GtkButton" id="button_previous"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="label">gtk-media-previous</property> + <property name="use_stock">True</property> + <property name="relief">GTK_RELIEF_NORMAL</property> + <property name="focus_on_click">False</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + <packing> + <property name="shrink">True</property> + <property name="resize">True</property> + </packing> + </child> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="tab_expand">False</property> + <property name="tab_fill">True</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label595"> + <property name="visible">True</property> + <property name="label" translatable="yes">Search</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="type">tab</property> + </packing> + </child> + + <child> + <widget class="GtkTable" id="table7"> + <property name="border_width">12</property> + <property name="visible">True</property> + <property name="n_rows">2</property> + <property name="n_columns">2</property> + <property name="homogeneous">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">6</property> + + <child> + <widget class="GtkHBox" id="hbox143"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + + <child> + <widget class="GtkImage" id="image247"> + <property name="visible">True</property> + <property name="stock">gtk-find</property> + <property name="icon_size">4</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + + <child> + <widget class="GtkEntry" id="entry_chats"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char">*</property> + <property name="activates_default">True</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">True</property> + <property name="fill">True</property> + </packing> + </child> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">fill</property> + <property name="y_options">fill</property> + </packing> + </child> + + <child> + <widget class="GtkVBox" id="vbox191"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow13"> + <property name="width_request">150</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_NEVER</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <property name="window_placement">GTK_CORNER_TOP_LEFT</property> + + <child> + <widget class="GtkTreeView" id="treeview_chats"> + <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> + + <child> + <widget class="GtkCalendar" id="calendar_chats"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="display_options">GTK_CALENDAR_SHOW_HEADING|GTK_CALENDAR_SHOW_DAY_NAMES</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">fill</property> + </packing> + </child> + + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow_chats"> + <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_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="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + + <child> + <widget class="GtkVBox" id="vbox_chats"> + <property name="visible">True</property> + <property name="homogeneous">False</property> + <property name="spacing">6</property> + + <child> + <placeholder/> + </child> + </widget> + <packing> + <property name="left_attach">0</property> + <property name="right_attach">1</property> + <property name="top_attach">0</property> + <property name="bottom_attach">1</property> + <property name="x_options">fill</property> + <property name="y_options">fill</property> + </packing> + </child> + </widget> + <packing> + <property name="tab_expand">False</property> + <property name="tab_fill">True</property> + </packing> + </child> + + <child> + <widget class="GtkLabel" id="label596"> + <property name="visible">True</property> + <property name="label" translatable="yes">Conversations</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="type">tab</property> + </packing> + </child> + </widget> + </child> +</widget> + +</glade-interface> diff --git a/libempathy-gtk/gossip-log-window.h b/libempathy-gtk/gossip-log-window.h new file mode 100644 index 000000000..7801072fd --- /dev/null +++ b/libempathy-gtk/gossip-log-window.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2006-2007 Imendio AB + * Copyright (C) 2007 Collabora Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Martyn Russell <martyn@imendio.com> + * Xavier Claessens <xclaesse@gmail.com> + */ + +#ifndef __GOSSIP_LOG_WINDOW_H__ +#define __GOSSIP_LOG_WINDOW_H__ + +#include <libmissioncontrol/mc-account.h> + +G_BEGIN_DECLS + +void gossip_log_window_show (McAccount *account, + const gchar *chat_id, + gboolean chatroom, + GtkWindow *parent); + +G_END_DECLS + +#endif /* __GOSSIP_LOG_WINDOW_H__ */ diff --git a/libempathy/empathy-log-manager.c b/libempathy/empathy-log-manager.c index ca5297ea7..49c67ec4e 100644 --- a/libempathy/empathy-log-manager.c +++ b/libempathy/empathy-log-manager.c @@ -39,6 +39,7 @@ #define LOG_DIR_CREATE_MODE (S_IRUSR | S_IWUSR | S_IXUSR) #define LOG_FILE_CREATE_MODE (S_IRUSR | S_IWUSR) +#define LOG_DIR_CHATROOMS "chatrooms" #define LOG_FILENAME_SUFFIX ".log" #define LOG_TIME_FORMAT_FULL "%Y%m%dT%H:%M:%S" #define LOG_TIME_FORMAT "%Y%m%d" @@ -51,21 +52,35 @@ "</log>\n" struct _EmpathyLogManagerPriv { - gboolean dummy; + gchar *basedir; }; -static void empathy_log_manager_class_init (EmpathyLogManagerClass *klass); -static void empathy_log_manager_init (EmpathyLogManager *manager); -static void log_manager_finalize (GObject *object); -static gchar * log_manager_get_dir (McAccount *account, - const gchar *chat_id); -static gchar * log_manager_get_filename (McAccount *account, - const gchar *chat_id); -static gchar * log_manager_get_filename_for_date (McAccount *account, - const gchar *chat_id, - const gchar *date); -static gchar * log_manager_get_timestamp_filename (void); -static gchar * log_manager_get_timestamp_from_message (GossipMessage *message); +static void empathy_log_manager_class_init (EmpathyLogManagerClass *klass); +static void empathy_log_manager_init (EmpathyLogManager *manager); +static void log_manager_finalize (GObject *object); +static const gchar * log_manager_get_basedir (EmpathyLogManager *manager); +static GList * log_manager_get_all_files (EmpathyLogManager *manager, + const gchar *dir); +static GList * log_manager_get_chats (EmpathyLogManager *manager, + const gchar *dir, + gboolean is_chatroom); +static gchar * log_manager_get_dir (EmpathyLogManager *manager, + McAccount *account, + const gchar *chat_id, + gboolean chatroom); +static gchar * log_manager_get_filename (EmpathyLogManager *manager, + McAccount *account, + const gchar *chat_id, + gboolean chatroom); +static gchar * log_manager_get_filename_for_date (EmpathyLogManager *manager, + McAccount *account, + const gchar *chat_id, + gboolean chatroom, + const gchar *date); +static gchar * log_manager_get_timestamp_filename (void); +static gchar * log_manager_get_timestamp_from_message (GossipMessage *message); +static EmpathyLogSearchHit *log_manager_search_hit_new (EmpathyLogManager *manager, + const gchar *filename); G_DEFINE_TYPE (EmpathyLogManager, empathy_log_manager, G_TYPE_OBJECT); @@ -87,6 +102,11 @@ empathy_log_manager_init (EmpathyLogManager *manager) static void log_manager_finalize (GObject *object) { + EmpathyLogManagerPriv *priv; + + priv = GET_PRIV (object); + + g_free (priv->basedir); } EmpathyLogManager * @@ -107,6 +127,7 @@ empathy_log_manager_new (void) void empathy_log_manager_add_message (EmpathyLogManager *manager, const gchar *chat_id, + gboolean chatroom, GossipMessage *message) { FILE *file; @@ -132,7 +153,7 @@ empathy_log_manager_add_message (EmpathyLogManager *manager, return; } - filename = log_manager_get_filename (account, chat_id); + filename = log_manager_get_filename (manager, account, chat_id, chatroom); gossip_debug (DEBUG_DOMAIN, "Adding message: '%s' to file: '%s'", body_str, filename); @@ -154,24 +175,17 @@ empathy_log_manager_add_message (EmpathyLogManager *manager, timestamp = log_manager_get_timestamp_from_message (message); str = gossip_contact_get_name (sender); - if (!str) { - contact_name = g_strdup (""); - } else { - contact_name = g_markup_escape_text (str, -1); - } + contact_name = g_markup_escape_text (str, -1); str = gossip_contact_get_id (sender); - if (!str) { - contact_id = g_strdup (""); - } else { - contact_id = g_markup_escape_text (str, -1); - } + contact_id = g_markup_escape_text (str, -1); g_fprintf (file, - "<message time='%s' id='%s' name='%s'>%s</message>\n" LOG_FOOTER, + "<message time='%s' id='%s' name='%s' isuser='%s'>%s</message>\n" LOG_FOOTER, timestamp, contact_id, contact_name, + gossip_contact_is_user (sender) ? "true" : "false", body); fclose (file); @@ -185,7 +199,8 @@ empathy_log_manager_add_message (EmpathyLogManager *manager, GList * empathy_log_manager_get_dates (EmpathyLogManager *manager, McAccount *account, - const gchar *chat_id) + const gchar *chat_id, + gboolean chatroom) { GList *dates = NULL; gchar *date; @@ -198,7 +213,7 @@ empathy_log_manager_get_dates (EmpathyLogManager *manager, g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL); g_return_val_if_fail (chat_id != NULL, NULL); - directory = log_manager_get_dir (account, chat_id); + directory = log_manager_get_dir (manager, account, chat_id, chatroom); if (!directory) { return NULL; } @@ -238,6 +253,7 @@ GList * empathy_log_manager_get_messages_for_date (EmpathyLogManager *manager, McAccount *account, const gchar *chat_id, + gboolean chatroom, const gchar *date) { gchar *filename; @@ -251,7 +267,7 @@ empathy_log_manager_get_messages_for_date (EmpathyLogManager *manager, g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL); g_return_val_if_fail (chat_id != NULL, NULL); - filename = log_manager_get_filename_for_date (account, chat_id, date); + filename = log_manager_get_filename_for_date (manager, account, chat_id, chatroom, date); gossip_debug (DEBUG_DOMAIN, "Attempting to parse filename:'%s'...", filename); @@ -291,6 +307,8 @@ empathy_log_manager_get_messages_for_date (EmpathyLogManager *manager, gchar *sender_id; gchar *sender_name; gchar *body; + gchar *is_user_str; + gboolean is_user = FALSE; if (strcmp (node->name, "message") != 0) { continue; @@ -300,10 +318,16 @@ empathy_log_manager_get_messages_for_date (EmpathyLogManager *manager, time = xmlGetProp (node, "time"); sender_id = xmlGetProp (node, "id"); sender_name = xmlGetProp (node, "name"); + is_user_str = xmlGetProp (node, "isuser"); + + if (is_user_str) { + is_user = strcmp (is_user_str, "true") == 0; + } t = gossip_time_parse (time); sender = gossip_contact_new_full (account, sender_id, sender_name); + gossip_contact_set_is_user (sender, is_user); message = gossip_message_new (body); gossip_message_set_sender (message, sender); gossip_message_set_timestamp (message, t); @@ -329,7 +353,8 @@ empathy_log_manager_get_messages_for_date (EmpathyLogManager *manager, GList * empathy_log_manager_get_last_messages (EmpathyLogManager *manager, McAccount *account, - const gchar *chat_id) + const gchar *chat_id, + gboolean chatroom) { GList *messages = NULL; GList *dates; @@ -339,13 +364,14 @@ empathy_log_manager_get_last_messages (EmpathyLogManager *manager, g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL); g_return_val_if_fail (chat_id != NULL, NULL); - dates = empathy_log_manager_get_dates (manager, account, chat_id); + dates = empathy_log_manager_get_dates (manager, account, chat_id, chatroom); l = g_list_last (dates); if (l) { messages = empathy_log_manager_get_messages_for_date (manager, account, chat_id, + chatroom, l->data); } @@ -355,22 +381,238 @@ empathy_log_manager_get_last_messages (EmpathyLogManager *manager, return messages; } +GList * +empathy_log_manager_get_chats (EmpathyLogManager *manager, + McAccount *account) +{ + const gchar *basedir; + gchar *dir; + + basedir = log_manager_get_basedir (manager); + dir = g_build_filename (basedir, + mc_account_get_unique_name (account), + NULL); + + return log_manager_get_chats (manager, dir, FALSE); +} + +GList * +empathy_log_manager_search_new (EmpathyLogManager *manager, + const gchar *text) +{ + GList *files, *l; + GList *hits = NULL; + gchar *text_casefold; + + g_return_val_if_fail (EMPATHY_IS_LOG_MANAGER (manager), NULL); + g_return_val_if_fail (!G_STR_EMPTY (text), NULL); + + text_casefold = g_utf8_casefold (text, -1); + + files = log_manager_get_all_files (manager, NULL); + gossip_debug (DEBUG_DOMAIN, "Found %d log files in total", + g_list_length (files)); + + for (l = files; l; l = l->next) { + gchar *filename; + GMappedFile *file; + gsize length; + gchar *contents; + gchar *contents_casefold; + + filename = l->data; + + file = g_mapped_file_new (filename, FALSE, NULL); + if (!file) { + continue; + } + + length = g_mapped_file_get_length (file); + contents = g_mapped_file_get_contents (file); + contents_casefold = g_utf8_casefold (contents, length); + + g_mapped_file_free (file); + + if (strstr (contents_casefold, text_casefold)) { + EmpathyLogSearchHit *hit; + + hit = log_manager_search_hit_new (manager, filename); + + if (hit) { + hits = g_list_prepend (hits, hit); + gossip_debug (DEBUG_DOMAIN, + "Found text:'%s' in file:'%s' on date:'%s'...", + text, hit->filename, hit->date); + } + } + + g_free (contents_casefold); + g_free (filename); + } + g_list_free (files); + + g_free (text_casefold); + + return hits; +} + +void +empathy_log_manager_search_free (GList *hits) +{ + GList *l; + EmpathyLogSearchHit *hit; + + for (l = hits; l; l = l->next) { + hit = l->data; + + if (hit->account) { + g_object_unref (hit->account); + } + + g_free (hit->date); + g_free (hit->filename); + g_free (hit->chat_id); + + g_slice_free (EmpathyLogSearchHit, hit); + } + + g_list_free (hits); +} + +/* Format is just date, 20061201. */ +gchar * +empathy_log_manager_get_date_readable (const gchar *date) +{ + GossipTime t; + + t = gossip_time_parse (date); + + return gossip_time_to_string_local (t, "%a %d %b %Y"); +} + +static const gchar * +log_manager_get_basedir (EmpathyLogManager *manager) +{ + EmpathyLogManagerPriv *priv; + + priv = GET_PRIV (manager); + + if (priv->basedir) { + return priv->basedir; + } + + priv->basedir = g_build_path (G_DIR_SEPARATOR_S, + g_get_home_dir (), + ".gnome2", + PACKAGE_NAME, + "logs", + NULL); + + return priv->basedir; +} + +static GList * +log_manager_get_all_files (EmpathyLogManager *manager, + const gchar *dir) +{ + GDir *gdir; + GList *files = NULL; + const gchar *name; + + if (!dir) { + dir = log_manager_get_basedir (manager); + } + + gdir = g_dir_open (dir, 0, NULL); + if (!gdir) { + return NULL; + } + + while ((name = g_dir_read_name (gdir)) != NULL) { + gchar *filename; + + filename = g_build_filename (dir, name, NULL); + if (g_str_has_suffix (filename, LOG_FILENAME_SUFFIX)) { + files = g_list_prepend (files, filename); + continue; + } + + if (g_file_test (filename, G_FILE_TEST_IS_DIR)) { + /* Recursively get all log files */ + files = g_list_concat (files, log_manager_get_all_files (manager, filename)); + } + g_free (filename); + } + + g_dir_close (gdir); + + return files; +} + +static GList * +log_manager_get_chats (EmpathyLogManager *manager, + const gchar *dir, + gboolean is_chatroom) +{ + GDir *gdir; + GList *hits = NULL; + const gchar *name; + + gdir = g_dir_open (dir, 0, NULL); + if (!gdir) { + return NULL; + } + + while ((name = g_dir_read_name (gdir)) != NULL) { + EmpathyLogSearchHit *hit; + gchar *filename; + + filename = g_build_filename (dir, name, NULL); + if (strcmp (name, LOG_DIR_CHATROOMS) == 0) { + hits = g_list_concat (hits, log_manager_get_chats (manager, filename, TRUE)); + g_free (filename); + continue; + } + + hit = g_slice_new0 (EmpathyLogSearchHit); + hit->chat_id = g_strdup (name); + hit->is_chatroom = is_chatroom; + + hits = g_list_prepend (hits, hit); + } + + g_dir_close (gdir); + + return hits; +} + static gchar * -log_manager_get_dir (McAccount *account, - const gchar *chat_id) +log_manager_get_dir (EmpathyLogManager *manager, + McAccount *account, + const gchar *chat_id, + gboolean chatroom) { const gchar *account_id; gchar *basedir; + gchar *str; account_id = mc_account_get_unique_name (account); - basedir = g_build_path (G_DIR_SEPARATOR_S, - g_get_home_dir (), - ".gnome2", - PACKAGE_NAME, - "logs", - account_id, - chat_id, - NULL); + basedir = + str = g_build_path (G_DIR_SEPARATOR_S, + log_manager_get_basedir (manager), + account_id, + chat_id, + NULL); + + if (chatroom) { + basedir = g_build_path (G_DIR_SEPARATOR_S, + str, + LOG_DIR_CHATROOMS, + NULL); + g_free (str); + } else { + basedir = str; + } if (!g_file_test (basedir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) { gossip_debug (DEBUG_DOMAIN, "Creating directory:'%s'", basedir); @@ -382,14 +624,16 @@ log_manager_get_dir (McAccount *account, } static gchar * -log_manager_get_filename (McAccount *account, - const gchar *chat_id) +log_manager_get_filename (EmpathyLogManager *manager, + McAccount *account, + const gchar *chat_id, + gboolean chatroom) { gchar *basedir; gchar *timestamp; gchar *filename; - basedir = log_manager_get_dir (account, chat_id); + basedir = log_manager_get_dir (manager, account, chat_id, chatroom); timestamp = log_manager_get_timestamp_filename (); filename = g_build_filename (basedir, timestamp, NULL); @@ -400,15 +644,17 @@ log_manager_get_filename (McAccount *account, } static gchar * -log_manager_get_filename_for_date (McAccount *account, - const gchar *chat_id, - const gchar *date) +log_manager_get_filename_for_date (EmpathyLogManager *manager, + McAccount *account, + const gchar *chat_id, + gboolean chatroom, + const gchar *date) { gchar *basedir; gchar *timestamp; gchar *filename; - basedir = log_manager_get_dir (account, chat_id); + basedir = log_manager_get_dir (manager, account, chat_id, chatroom); timestamp = g_strconcat (date, LOG_FILENAME_SUFFIX, NULL); filename = g_build_filename (basedir, timestamp, NULL); @@ -445,3 +691,39 @@ log_manager_get_timestamp_from_message (GossipMessage *message) return gossip_time_to_string_utc (t, LOG_TIME_FORMAT_FULL); } +static EmpathyLogSearchHit * +log_manager_search_hit_new (EmpathyLogManager *manager, + const gchar *filename) +{ + EmpathyLogSearchHit *hit; + const gchar *account_name; + const gchar *end; + gchar **strv; + guint len; + + if (!g_str_has_suffix (filename, LOG_FILENAME_SUFFIX)) { + return NULL; + } + + strv = g_strsplit (filename, G_DIR_SEPARATOR_S, -1); + len = g_strv_length (strv); + + hit = g_slice_new0 (EmpathyLogSearchHit); + + end = strstr (strv[len-1], LOG_FILENAME_SUFFIX); + hit->date = g_strndup (strv[len-1], end - strv[len-1]); + hit->chat_id = g_strdup (strv[len-2]); + hit->is_chatroom = (strcmp (strv[len-3], LOG_DIR_CHATROOMS) == 0); + if (hit->is_chatroom) { + account_name = strv[len-4]; + } else { + account_name = strv[len-3]; + } + hit->account = mc_account_lookup (account_name); + hit->filename = g_strdup (filename); + + g_strfreev (strv); + + return hit; +} + diff --git a/libempathy/empathy-log-manager.h b/libempathy/empathy-log-manager.h index 8df68d5ed..9a163fd36 100644 --- a/libempathy/empathy-log-manager.h +++ b/libempathy/empathy-log-manager.h @@ -41,6 +41,7 @@ G_BEGIN_DECLS typedef struct _EmpathyLogManager EmpathyLogManager; typedef struct _EmpathyLogManagerClass EmpathyLogManagerClass; typedef struct _EmpathyLogManagerPriv EmpathyLogManagerPriv; +typedef struct _EmpathyLogSearchHit EmpathyLogSearchHit; struct _EmpathyLogManager { GObject parent; @@ -50,21 +51,39 @@ struct _EmpathyLogManagerClass { GObjectClass parent_class; }; +struct _EmpathyLogSearchHit { + McAccount *account; + gchar *chat_id; + gboolean is_chatroom; + gchar *filename; + gchar *date; +}; + GType empathy_log_manager_get_type (void) G_GNUC_CONST; EmpathyLogManager *empathy_log_manager_new (void); void empathy_log_manager_add_message (EmpathyLogManager *manager, const gchar *chat_id, + gboolean chatroom, GossipMessage *message); GList * empathy_log_manager_get_dates (EmpathyLogManager *manager, McAccount *account, - const gchar *chat_id); + const gchar *chat_id, + gboolean chatroom); GList * empathy_log_manager_get_messages_for_date (EmpathyLogManager *manager, McAccount *account, const gchar *chat_id, + gboolean chatroom, const gchar *date); GList * empathy_log_manager_get_last_messages (EmpathyLogManager *manager, McAccount *account, - const gchar *chat_id); + const gchar *chat_id, + gboolean chatroom); +GList * empathy_log_manager_get_chats (EmpathyLogManager *manager, + McAccount *account); +GList * empathy_log_manager_search_new (EmpathyLogManager *manager, + const gchar *text); +void empathy_log_manager_search_free (GList *hits); +gchar * empathy_log_manager_get_date_readable (const gchar *date); G_END_DECLS diff --git a/libempathy/empathy-tp-chat.c b/libempathy/empathy-tp-chat.c index d63e82551..a2852e1d8 100644 --- a/libempathy/empathy-tp-chat.c +++ b/libempathy/empathy-tp-chat.c @@ -790,12 +790,13 @@ tp_chat_emit_message (EmpathyTpChat *chat, EmpathyTpChatPriv *priv; GossipMessage *message; GossipContact *sender; + GossipContact *receiver; priv = GET_PRIV (chat); + receiver = empathy_tp_contact_list_get_user (priv->list); if (from_handle == 0) { - sender = empathy_tp_contact_list_get_user (priv->list); - g_object_ref (sender); + sender = g_object_ref (receiver); } else { sender = empathy_tp_contact_list_get_from_handle (priv->list, from_handle); @@ -804,6 +805,7 @@ tp_chat_emit_message (EmpathyTpChat *chat, message = gossip_message_new (message_body); gossip_message_set_type (message, type); gossip_message_set_sender (message, sender); + gossip_message_set_receiver (message, receiver); gossip_message_set_timestamp (message, (GossipTime) timestamp); g_signal_emit (chat, signals[MESSAGE_RECEIVED], 0, message); diff --git a/libempathy/empathy-tp-contact-list.c b/libempathy/empathy-tp-contact-list.c index c81279043..b696e990c 100644 --- a/libempathy/empathy-tp-contact-list.c +++ b/libempathy/empathy-tp-contact-list.c @@ -380,8 +380,8 @@ empathy_tp_contact_list_new (McAccount *account) error ? error->message : "No error given"); g_clear_error (&error); } else { - /* FIXME: this adds the handle to the roster */ priv->user_contact = empathy_tp_contact_list_get_from_handle (list, handle); + gossip_contact_set_is_user (priv->user_contact, TRUE); } return list; @@ -1276,6 +1276,10 @@ tp_contact_list_name_updated_cb (GossipContact *contact, priv = GET_PRIV (list); + if (!priv->aliasing_iface) { + return; + } + handle = gossip_contact_get_handle (contact); new_name = gossip_contact_get_name (contact); diff --git a/libempathy/gossip-contact.c b/libempathy/gossip-contact.c index 82147f67e..53fe7e182 100644 --- a/libempathy/gossip-contact.c +++ b/libempathy/gossip-contact.c @@ -30,7 +30,6 @@ #include "gossip-contact.h" #include "gossip-utils.h" #include "gossip-debug.h" -#include "empathy-contact-manager.h" #define DEBUG_DOMAIN "Contact" @@ -47,6 +46,7 @@ struct _GossipContactPriv { GList *groups; GossipSubscription subscription; guint handle; + gboolean is_user; }; static void contact_class_init (GossipContactClass *class); @@ -70,7 +70,8 @@ enum { PROP_PRESENCE, PROP_GROUPS, PROP_SUBSCRIPTION, - PROP_HANDLE + PROP_HANDLE, + PROP_IS_USER }; static gpointer parent_class = NULL; @@ -180,6 +181,13 @@ contact_class_init (GossipContactClass *class) G_MAXUINT, 0, G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_IS_USER, + g_param_spec_boolean ("is-user", + "Contact is-user", + "Is contact the user", + FALSE, + G_PARAM_READWRITE)); g_type_class_add_private (object_class, sizeof (GossipContactPriv)); } @@ -187,17 +195,6 @@ contact_class_init (GossipContactClass *class) static void contact_init (GossipContact *contact) { - GossipContactPriv *priv; - - priv = GET_PRIV (contact); - - priv->id = NULL; - priv->name = NULL; - priv->avatar = NULL; - priv->account = NULL; - priv->presence = NULL; - priv->groups = NULL; - priv->handle = 0; } static void @@ -269,6 +266,9 @@ contact_get_property (GObject *object, case PROP_HANDLE: g_value_set_uint (value, priv->handle); break; + case PROP_IS_USER: + g_value_set_boolean (value, priv->is_user); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; @@ -318,6 +318,10 @@ contact_set_property (GObject *object, gossip_contact_set_handle (GOSSIP_CONTACT (object), g_value_get_uint (value)); break; + case PROP_IS_USER: + gossip_contact_set_is_user (GOSSIP_CONTACT (object), + g_value_get_boolean (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); break; @@ -449,6 +453,18 @@ gossip_contact_get_handle (GossipContact *contact) return priv->handle; } +gboolean +gossip_contact_is_user (GossipContact *contact) +{ + GossipContactPriv *priv; + + g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), FALSE); + + priv = GET_PRIV (contact); + + return priv->is_user; +} + void gossip_contact_set_id (GossipContact *contact, const gchar *id) @@ -630,6 +646,25 @@ gossip_contact_set_handle (GossipContact *contact, } void +gossip_contact_set_is_user (GossipContact *contact, + gboolean is_user) +{ + GossipContactPriv *priv; + + g_return_if_fail (GOSSIP_IS_CONTACT (contact)); + + priv = GET_PRIV (contact); + + if (priv->is_user == is_user) { + return; + } + + priv->is_user = is_user; + + g_object_notify (G_OBJECT (contact), "is-user"); +} + +void gossip_contact_add_group (GossipContact *contact, const gchar *group) { @@ -722,24 +757,6 @@ gossip_contact_get_status (GossipContact *contact) return gossip_presence_state_get_default_status (MC_PRESENCE_OFFLINE); } -GossipContact * -gossip_contact_get_user (GossipContact *contact) -{ - GossipContactPriv *priv; - EmpathyContactManager *manager; - GossipContact *user_contact; - - g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL); - - priv = GET_PRIV (contact); - - manager = empathy_contact_manager_new (); - user_contact = empathy_contact_manager_get_user (manager, priv->account); - g_object_unref (manager); - - return user_contact; -} - gboolean gossip_contact_equal (gconstpointer v1, gconstpointer v2) diff --git a/libempathy/gossip-contact.h b/libempathy/gossip-contact.h index 23da37538..14b32fdc2 100644 --- a/libempathy/gossip-contact.h +++ b/libempathy/gossip-contact.h @@ -69,6 +69,7 @@ GossipPresence * gossip_contact_get_presence (GossipContact GList * gossip_contact_get_groups (GossipContact *contact); GossipSubscription gossip_contact_get_subscription (GossipContact *contact); guint gossip_contact_get_handle (GossipContact *contact); +gboolean gossip_contact_is_user (GossipContact *contact); void gossip_contact_set_id (GossipContact *contact, const gchar *id); void gossip_contact_set_name (GossipContact *contact, @@ -85,6 +86,8 @@ void gossip_contact_set_subscription (GossipContact GossipSubscription subscription); void gossip_contact_set_handle (GossipContact *contact, guint handle); +void gossip_contact_set_is_user (GossipContact *contact, + gboolean is_user); void gossip_contact_add_group (GossipContact *contact, const gchar *group); void gossip_contact_remove_group (GossipContact *contact, @@ -93,7 +96,6 @@ gboolean gossip_contact_is_online (GossipContact gboolean gossip_contact_is_in_group (GossipContact *contact, const gchar *group); const gchar * gossip_contact_get_status (GossipContact *contact); -GossipContact * gossip_contact_get_user (GossipContact *contact); gboolean gossip_contact_equal (gconstpointer v1, gconstpointer v2); guint gossip_contact_hash (gconstpointer key); diff --git a/libempathy/gossip-message.c b/libempathy/gossip-message.c index c4844e655..a46a2a5dc 100644 --- a/libempathy/gossip-message.c +++ b/libempathy/gossip-message.c @@ -33,6 +33,7 @@ typedef struct _GossipMessagePriv GossipMessagePriv; struct _GossipMessagePriv { GossipMessageType type; GossipContact *sender; + GossipContact *receiver; gchar *body; GossipTime timestamp; @@ -54,6 +55,7 @@ enum { PROP_0, PROP_TYPE, PROP_SENDER, + PROP_RECEIVER, PROP_BODY, PROP_TIMESTAMP, }; @@ -114,7 +116,13 @@ gossip_message_class_init (GossipMessageClass *class) "The sender of the message", GOSSIP_TYPE_CONTACT, G_PARAM_READWRITE)); - + g_object_class_install_property (object_class, + PROP_RECEIVER, + g_param_spec_object ("receiver", + "Message Receiver", + "The receiver of the message", + GOSSIP_TYPE_CONTACT, + G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_BODY, g_param_spec_string ("body", @@ -144,9 +152,6 @@ gossip_message_init (GossipMessage *message) priv = GET_PRIV (message); - priv->type = GOSSIP_MESSAGE_TYPE_NORMAL; - priv->sender = NULL; - priv->body = NULL; priv->timestamp = gossip_time_get_current (); } @@ -160,6 +165,9 @@ gossip_message_finalize (GObject *object) if (priv->sender) { g_object_unref (priv->sender); } + if (priv->receiver) { + g_object_unref (priv->receiver); + } g_free (priv->body); @@ -183,6 +191,9 @@ message_get_property (GObject *object, case PROP_SENDER: g_value_set_object (value, priv->sender); break; + case PROP_RECEIVER: + g_value_set_object (value, priv->receiver); + break; case PROP_BODY: g_value_set_string (value, priv->body); break; @@ -211,6 +222,10 @@ message_set_property (GObject *object, gossip_message_set_sender (GOSSIP_MESSAGE (object), GOSSIP_CONTACT (g_value_get_object (value))); break; + case PROP_RECEIVER: + gossip_message_set_receiver (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)); @@ -290,6 +305,39 @@ gossip_message_set_sender (GossipMessage *message, GossipContact *contact) g_object_notify (G_OBJECT (message), "sender"); } +GossipContact * +gossip_message_get_receiver (GossipMessage *message) +{ + GossipMessagePriv *priv; + + g_return_val_if_fail (GOSSIP_IS_MESSAGE (message), NULL); + + priv = GET_PRIV (message); + + return priv->receiver; +} + +void +gossip_message_set_receiver (GossipMessage *message, GossipContact *contact) +{ + GossipMessagePriv *priv; + GossipContact *old_receiver; + + g_return_if_fail (GOSSIP_IS_MESSAGE (message)); + g_return_if_fail (GOSSIP_IS_CONTACT (contact)); + + priv = GET_PRIV (message); + + old_receiver = priv->receiver; + priv->receiver = g_object_ref (contact); + + if (old_receiver) { + g_object_unref (old_receiver); + } + + g_object_notify (G_OBJECT (message), "receiver"); +} + const gchar * gossip_message_get_body (GossipMessage *message) { diff --git a/libempathy/gossip-message.h b/libempathy/gossip-message.h index 770ecfe1b..aa4948025 100644 --- a/libempathy/gossip-message.h +++ b/libempathy/gossip-message.h @@ -66,6 +66,9 @@ void gossip_message_set_type (GossipMessage * GossipContact * gossip_message_get_sender (GossipMessage *message); void gossip_message_set_sender (GossipMessage *message, GossipContact *contact); +GossipContact * gossip_message_get_receiver (GossipMessage *message); +void gossip_message_set_receiver (GossipMessage *message, + GossipContact *contact); const gchar * gossip_message_get_body (GossipMessage *message); void gossip_message_set_body (GossipMessage *message, const gchar *body); diff --git a/src/empathy-main.c b/src/empathy-main.c index f64b2d6e9..92a66b2c8 100644 --- a/src/empathy-main.c +++ b/src/empathy-main.c @@ -127,6 +127,7 @@ main (int argc, char *argv[]) 0, G_OPTION_ARG_NONE, &no_connect, N_("Don't connect on startup"), NULL }, + { NULL } }; bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); |