aboutsummaryrefslogblamecommitdiffstats
path: root/libempathy-gtk/empathy-log-window.c
blob: 986a3af2cddafec76803da4ba1ea5112cbe1157c (plain) (tree)
1
2
3
4


                                                                           
                                         












                                                                     


                                                               








                                                 
                           
                    
 
                                           
                                         
 


                                                





                                     
                                  
                             
 

                                      









                                               
                                          








                                                 
                                           


                                     
                                       




                                                                                   
                   
 










                                                                                               

                                                                                      









                                                                                        
                                                                                     
                                                                                      


                                                                                           
                                                                                      


                                                                                           
                                                                                      







                                                                                       





















                               

                                           








                                                                               
                             
 


                               


                                                                                   















                                                                           








                                                                                               
           
                                             



                                                 
                                                 
                                                 
                                    
                                         
                                       
 

                                                                     

                                         

                                                                                               


                                                                             
                                          

         



                                                                   
 
                                                                
                                                          
                                                 













                                                                                             

                          
                                             

                                                                                        
                                                                                          






                                                                                                  
                             

                                                             
                                                           








                                                                                  
                                              
                                                                                                 



                                                                       
                                                
                                                                                                  




                                                                        

                                                                                  









                                                                            


                                                                               
                                         
 






                                           
                                 


                                                              






                                                                                         







                                                                          

                              



                                               
                                                
 

                                             
                                          








                                                         
                                                           





                                                                  
                                             

                                     




                                                                            











                                             


                               

                                                                                     










                                                                                                 
                                                          



























                                                                                          



                                 

                               
 
                                        

                            


                                  
 
                                                 

                            
                                                 

 

                                                        
                                                      



                             
                               


                                   
                             








                                                                          
                                                                
 













                                                                 
                                                        

                                            
                                                                

                          












                                                                                               
 


                                 


 










                                                  


                               
                                                                              
                                 








                                                               

                                                               







                                                                                     
                                                                               

                                                                             
                                                           

                                                     










                                                                                     
                                                           


                                                                              








                                                              


           
                                                   

                                                           



                                      





                                                       
                                                        


                                     
                                              



                                            
                                                                           
                                                                       


           
                                                
















                                                                                   
                                                                         


































































                                                                                                   
                                            
















                                                                        













                                                            
                                                           
                                                            




                                         
                                                                   
                                                              
                                                  
                                                   
                                                                        
                                                                   
                                                       








                                                                                    
                                                                




                                         
                                                                       
                                                                  
                                                      
                                                       
                                                                        
                                                                   
                                                       












                                                                                    
                                                       






                                                                         
















                                                  


                               

                                                                         








































                                                                                       
 
           
                                                    
 
                                               
                                       




                                        
 
                                                                                  
                                                                        





                                                       




                                             






                                                                        

                                                                      


           
                                                 













                                                       

                                                                    
                                                                       

                                                                           







































                                                                      
                                                               

                                                              
                                                         




                                           
                                                         
                                                          


                                                             
                                               






                                        

                                                                                  









                                                                           
                                        








                                                                            
                                              
















                                                                                        
                                                         
                                                         







                                                             
                                     
































                                                                        









                                                      


                         

                                                                               


























                                                                               
                                              




                       




























                                                                   
                                
                                      
                             

                              


                         

                                                                   







                                                              
                         
 







                                                                   
                   

       

                                                   


                   

                                                                 


                                                                   






                          

                                                                     











                                                                         
                                                 









                                                        
                                               



                                   
                                     













                                                                                   
                           






                                                                                                    
                                            






                                                                             

                                                                    









                                                                                     
                           

                                                                





                                 

                                                                     
                                                                    



                     
                    

                                                                             


                                      



                                                                               
                                                 
 
                                                                               


                                                     
                           

 










                                                                


                               

                                                                         

















                                                                                  
                                              
 

                                                                        

                                                                                                      
                                                                                                                             








                                                                        
 

                                                                      
                                                                     
 
                               

                                   




                                                                      
                                                               


                       
                                                 



                                                                                     


                                 



                                                          
                                                            



                                                                   
                                                                         

                  
                                                                    
                                                

                                                   




                                                           
                                                             





                                                                   
                                                                    
                                                
                                                  


                                                   
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Copyright (C) 2006-2007 Imendio AB
 * Copyright (C) 2007-2008 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., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301  USA
 *
 * Authors: Martyn Russell <martyn@imendio.com>
 *          Xavier Claessens <xclaesse@gmail.com>
 */

#include "config.h"

#include <string.h>
#include <stdlib.h>

#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>

#include <telepathy-glib/account-manager.h>
#include <telepathy-logger/log-manager.h>

#include <libempathy/empathy-chatroom-manager.h>
#include <libempathy/empathy-chatroom.h>
#include <libempathy/empathy-message.h>
#include <libempathy/empathy-utils.h>
#include <libempathy/empathy-time.h>

#include "empathy-log-window.h"
#include "empathy-account-chooser.h"
#include "empathy-chat-view.h"
#include "empathy-theme-manager.h"
#include "empathy-ui-utils.h"

#define DEBUG_FLAG EMPATHY_DEBUG_OTHER
#include <libempathy/empathy-debug.h>

typedef struct {
    GtkWidget         *window;

    GtkWidget         *notebook;

    GtkWidget         *entry_find;
    GtkWidget         *button_find;
    GtkWidget         *treeview_find;
    GtkWidget         *scrolledwindow_find;
    EmpathyChatView    *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;
    EmpathyChatView    *chatview_chats;

    gchar             *last_find;

    TplLogManager     *log_manager;

    /* Those are only used while waiting for the account chooser to be ready */
    TpAccount         *selected_account;
    gchar             *selected_chat_id;
    gboolean          selected_is_chatroom;
} EmpathyLogWindow;

static void     log_window_destroy_cb                      (GtkWidget        *widget,
                                EmpathyLogWindow *window);
static void     log_window_entry_find_changed_cb           (GtkWidget        *entry,
                                EmpathyLogWindow *window);
static void     log_window_find_changed_cb                 (GtkTreeSelection *selection,
                                EmpathyLogWindow *window);
static void     log_window_find_populate                   (EmpathyLogWindow *window,
                                const gchar      *search_criteria);
static void     log_window_find_setup                      (EmpathyLogWindow *window);
static void     log_window_button_find_clicked_cb          (GtkWidget        *widget,
                                EmpathyLogWindow *window);
static void     log_window_entry_find_activate_cb          (GtkWidget        *widget,
                                EmpathyLogWindow *window);
static void     log_window_button_next_clicked_cb          (GtkWidget        *widget,
                                EmpathyLogWindow *window);
static void     log_window_button_previous_clicked_cb      (GtkWidget        *widget,
                                EmpathyLogWindow *window);
static void     log_window_chats_changed_cb                (GtkTreeSelection *selection,
                                EmpathyLogWindow *window);
static void     log_window_chats_populate                  (EmpathyLogWindow *window);
static void     log_window_chats_setup                     (EmpathyLogWindow *window);
static void     log_window_chats_accounts_changed_cb       (GtkWidget        *combobox,
                                EmpathyLogWindow *window);
static void     log_window_chats_set_selected              (EmpathyLogWindow *window,
                                TpAccount        *account,
                                const gchar      *chat_id,
                                gboolean          is_chatroom);
static gboolean log_window_chats_get_selected              (EmpathyLogWindow *window,
                                TpAccount       **account,
                                gchar           **chat_id,
                                gboolean         *is_chatroom);
static void     log_window_chats_get_messages              (EmpathyLogWindow *window,
                                GDate      *date_to_show);
static void     log_window_calendar_chats_day_selected_cb  (GtkWidget        *calendar,
                                EmpathyLogWindow *window);
static void     log_window_calendar_chats_month_changed_cb (GtkWidget        *calendar,
                                EmpathyLogWindow *window);
static void     log_window_entry_chats_changed_cb          (GtkWidget        *entry,
                                EmpathyLogWindow *window);
static void     log_window_entry_chats_activate_cb         (GtkWidget        *entry,
                                EmpathyLogWindow *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
};

static EmpathyLogWindow *log_window = NULL;

static void
account_manager_prepared_cb (GObject *source_object,
                 GAsyncResult *result,
                 gpointer user_data)
{
    TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
    EmpathyLogWindow *window = user_data;
    guint account_num;
    GList *accounts;
    GError *error = NULL;

    if (log_window == NULL)
        return;

    if (!tp_account_manager_prepare_finish (account_manager, result, &error)) {
        DEBUG ("Failed to prepare account manager: %s", error->message);
        g_error_free (error);
        return;
    }

    accounts = tp_account_manager_get_valid_accounts (account_manager);
    account_num = g_list_length (accounts);
    g_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);
    }
}

static void
account_chooser_ready_cb (EmpathyAccountChooser *chooser,
            EmpathyLogWindow *window)
{
    gtk_notebook_set_current_page (GTK_NOTEBOOK (window->notebook), 1);
    log_window_chats_set_selected (window, window->selected_account,
                       window->selected_chat_id, window->selected_is_chatroom);
}

GtkWidget *
empathy_log_window_show (TpAccount  *account,
            const gchar *chat_id,
            gboolean     is_chatroom,
            GtkWindow   *parent)
{
    EmpathyAccountChooser   *account_chooser;
    TpAccountManager        *account_manager;
    GtkBuilder             *gui;
    gchar                  *filename;
    EmpathyLogWindow       *window;

    if (log_window) {
        gtk_window_present (GTK_WINDOW (log_window->window));

        if (account && chat_id) {
            gtk_notebook_set_current_page (GTK_NOTEBOOK (log_window->notebook), 1);
            log_window_chats_set_selected (log_window, account,
                               chat_id, is_chatroom);
        }

        return log_window->window;
    }

    log_window = g_new0 (EmpathyLogWindow, 1);
    log_window->log_manager = tpl_log_manager_dup_singleton ();

    window = log_window;

    filename = empathy_file_lookup ("empathy-log-window.ui",
                    "libempathy-gtk");
    gui = empathy_builder_get_file (filename,
                       "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);
    g_free (filename);

    empathy_builder_connect (gui, window,
                  "log_window", "destroy", log_window_destroy_cb,
                  "entry_find", "changed", log_window_entry_find_changed_cb,
                  "entry_find", "activate", log_window_entry_find_activate_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 (gui);

    g_object_add_weak_pointer (G_OBJECT (window->window),
                   (gpointer) &log_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 EmpathyChatView */
    window->chatview_find = empathy_theme_manager_create_view (empathy_theme_manager_get ());
    gtk_container_add (GTK_CONTAINER (window->scrolledwindow_find),
               GTK_WIDGET (window->chatview_find));
    gtk_widget_show (GTK_WIDGET (window->chatview_find));

    /* Configure Contacts EmpathyChatView */
    window->chatview_chats = empathy_theme_manager_create_view (empathy_theme_manager_get ());
    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 = empathy_account_chooser_new ();
    account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser_chats);

    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 */
    account_manager = tp_account_manager_dup ();
    tp_account_manager_prepare_async (account_manager, NULL,
                      account_manager_prepared_cb, window);
    g_object_unref (account_manager);

    /* Search List */
    log_window_find_setup (window);

    /* Contacts */
    log_window_chats_setup (window);
    log_window_chats_populate (window);

    if (account && chat_id) {
        window->selected_account = account;
        window->selected_chat_id = g_strdup (chat_id);
        window->selected_is_chatroom = is_chatroom;

        if (empathy_account_chooser_is_ready (account_chooser))
            account_chooser_ready_cb (account_chooser, window);
        else
            /* Chat will be selected once the account chooser is ready */
            g_signal_connect (account_chooser, "ready",
                      G_CALLBACK (account_chooser_ready_cb), window);
    }

    if (parent) {
        gtk_window_set_transient_for (GTK_WINDOW (window->window),
                          GTK_WINDOW (parent));
    }

    gtk_widget_show (window->window);

    return window->window;
}

static void
log_window_destroy_cb (GtkWidget       *widget,
               EmpathyLogWindow *window)
{
    g_free (window->last_find);
    g_object_unref (window->log_manager);
    g_free (window->selected_chat_id);

    g_free (window);
}

/*
 * Search code.
 */
static void
log_window_entry_find_changed_cb (GtkWidget       *entry,
                  EmpathyLogWindow *window)
{
    const gchar *str;
    gboolean     is_sensitive = TRUE;

    str = gtk_entry_get_text (GTK_ENTRY (window->entry_find));

    is_sensitive &= !EMP_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
got_messages_for_date_cb (GObject *manager,
                       GAsyncResult *result,
                       gpointer user_data)
{
    EmpathyLogWindow *window = user_data;
    GList         *messages;
    GList         *l;
    gboolean       can_do_previous;
    gboolean       can_do_next;
    GError        *error = NULL;

    if (log_window == NULL)
        return;

    if (!tpl_log_manager_get_messages_for_date_finish (TPL_LOG_MANAGER (manager),
        result, &messages, &error)) {
            DEBUG ("Unable to retrieve messages for the selected date: %s. Aborting",
                    error->message);
            empathy_chat_view_append_event (window->chatview_find,
                    "Unable to retrieve messages for the selected date");
            g_error_free (error);
            return;
    }

    for (l = messages; l; l = l->next) {
            EmpathyMessage *message;

            g_assert (TPL_IS_ENTRY (l->data));

            message = empathy_message_from_tpl_log_entry (l->data);
            g_object_unref (l->data);
            empathy_chat_view_append_message (window->chatview_find, message);
            g_object_unref (message);
    }
    g_list_free (messages);

    /* Scroll to the most recent messages */
    empathy_chat_view_scroll (window->chatview_find, TRUE);

    /* Highlight and find messages */
    empathy_chat_view_highlight (window->chatview_find,
            window->last_find,
            FALSE);
    empathy_chat_view_find_next (window->chatview_find,
            window->last_find,
            TRUE,
            FALSE);
    empathy_chat_view_find_abilities (window->chatview_find,
            window->last_find,
            FALSE,
            &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 GDate *
gdate_from_str (const gchar *str)
{
    guint u;
    guint day, month, year;

    if (sscanf (str, "%u", &u) != 1)
        return NULL;

    day = (u % 100);
    month = ((u / 100) % 100);
    year = (u / 10000);

    if (!g_date_valid_dmy (day, month, year))
        return NULL;

    return g_date_new_dmy (day, month, year);
}

static void
log_window_find_changed_cb (GtkTreeSelection *selection,
                EmpathyLogWindow  *window)
{
    GtkTreeView   *view;
    GtkTreeModel  *model;
    GtkTreeIter    iter;
    TpAccount     *account;
    gchar         *chat_id;
    gboolean       is_chatroom;
    gchar         *date;
    GDate         *gdate;

    /* 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);

        empathy_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 */
    empathy_chat_view_clear (window->chatview_find);

    /* Turn off scrolling temporarily */
    empathy_chat_view_scroll (window->chatview_find, FALSE);

    /* Get messages */
    gdate = gdate_from_str (date);

    if (gdate != NULL) {
        tpl_log_manager_get_messages_for_date_async (window->log_manager,
                                      account,
                                      chat_id,
                                      is_chatroom,
                                      gdate,
                                      got_messages_for_date_cb,
                                      window);

        g_date_free (gdate);
    }

    g_object_unref (account);
    g_free (date);
    g_free (chat_id);
}


static void
log_manager_searched_new_cb (GObject *manager,
                             GAsyncResult *result,
                             gpointer user_data)
{
    GList               *hits;
    GList               *l;
    GtkTreeIter          iter;
    GtkListStore        *store = user_data;
    GError              *error = NULL;

    if (log_window == NULL)
        return;

    if (!tpl_log_manager_search_finish (TPL_LOG_MANAGER (manager), result,
        &hits, &error)) {
            DEBUG ("%s. Aborting", error->message);
            g_error_free (error);
            return;
    }

    for (l = hits; l; l = l->next) {
            TplLogSearchHit *hit;
            const gchar         *account_name;
            const gchar         *account_icon;
            gchar               date_readable[255];
            gchar               tmp[255];

            hit = l->data;

            /* Protect against invalid data (corrupt or old log files. */
            if (!hit->account || !hit->chat_id) {
                    continue;
            }

            g_date_strftime (date_readable, sizeof (date_readable),
                EMPATHY_TIME_FORMAT_DISPLAY_LONG, hit->date);

            g_date_strftime (tmp, sizeof (tmp),
                "%Y%m%d", hit->date);

            account_name = tp_account_get_display_name (hit->account);
            account_icon = tp_account_get_icon_name (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, tmp,
                    COL_FIND_DATE_READABLE, date_readable,
                    -1);

            /* FIXME: Update COL_FIND_CHAT_NAME */
            if (hit->is_chatroom) {
            } else {
            }
    }

    if (hits) {
            tpl_log_manager_search_free (hits);
    }
}

static void
log_window_find_populate (EmpathyLogWindow *window,
              const gchar     *search_criteria)
{
    GtkTreeView        *view;
    GtkTreeModel       *model;
    GtkTreeSelection   *selection;
    GtkListStore       *store;

    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);

    empathy_chat_view_clear (window->chatview_find);

    gtk_list_store_clear (store);

    if (EMP_STR_EMPTY (search_criteria)) {
        /* Just clear the search. */
        return;
    }

    tpl_log_manager_search_async (window->log_manager, search_criteria,
            log_manager_searched_new_cb, (gpointer) store);
}

static void
log_window_find_setup (EmpathyLogWindow *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 */
                    TP_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
start_find_search (EmpathyLogWindow *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_find_clicked_cb (GtkWidget       *widget,
                   EmpathyLogWindow *window)
{
    start_find_search (window);
}

static void
log_window_entry_find_activate_cb (GtkWidget *entry,
                   EmpathyLogWindow *self)
{
    start_find_search (self);
}

static void
log_window_button_next_clicked_cb (GtkWidget       *widget,
                   EmpathyLogWindow *window)
{
    if (window->last_find) {
        gboolean can_do_previous;
        gboolean can_do_next;

        empathy_chat_view_find_next (window->chatview_find,
                        window->last_find,
                        FALSE,
                        FALSE);
        empathy_chat_view_find_abilities (window->chatview_find,
                         window->last_find,
                         FALSE,
                         &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,
                       EmpathyLogWindow *window)
{
    if (window->last_find) {
        gboolean can_do_previous;
        gboolean can_do_next;

        empathy_chat_view_find_previous (window->chatview_find,
                        window->last_find,
                        FALSE,
                        FALSE);
        empathy_chat_view_find_abilities (window->chatview_find,
                         window->last_find,
                         FALSE,
                         &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,
                 EmpathyLogWindow  *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_manager_got_chats_cb (GObject *manager,
                       GAsyncResult *result,
                       gpointer user_data)
{
    EmpathyLogWindow      *window = user_data;
    GList                 *chats;
    GList                 *l;
    EmpathyAccountChooser *account_chooser;
    TpAccount             *account;
    GtkTreeView           *view;
    GtkTreeModel          *model;
    GtkTreeSelection      *selection;
    GtkListStore          *store;
    GtkTreeIter            iter;
    GError                *error = NULL;

    if (log_window == NULL)
        return;

    if (!tpl_log_manager_get_chats_finish (TPL_LOG_MANAGER (manager),
        result, &chats, &error)) {
            DEBUG ("%s. Aborting", error->message);
            g_error_free (error);
            return;
    }

    account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser_chats);
    account = empathy_account_chooser_dup_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);

    for (l = chats; l; l = l->next) {
            TplLogSearchHit *hit;

            hit = l->data;

            gtk_list_store_append (store, &iter);
            gtk_list_store_set (store, &iter,
                    COL_CHAT_ICON, "empathy-available", /* FIXME */
                    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 {
            }
    }
    tpl_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_populate (EmpathyLogWindow *window)
{
    EmpathyAccountChooser *account_chooser;
    TpAccount             *account;

    GtkTreeView          *view;
    GtkTreeModel         *model;
    GtkTreeSelection     *selection;
    GtkListStore         *store;

    account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser_chats);
    account = empathy_account_chooser_dup_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);

    if (account == NULL) {
        gtk_list_store_clear (store);
        return;
    }

    /* 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);

    tpl_log_manager_get_chats_async (window->log_manager, account,
            log_manager_got_chats_cb, (gpointer) window);
}

static void
log_window_chats_setup (EmpathyLogWindow *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 */
                    TP_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,
                      EmpathyLogWindow *window)
{
    /* Clear all current messages shown in the textview */
    empathy_chat_view_clear (window->chatview_chats);

    log_window_chats_populate (window);
}

static void
log_window_chats_set_selected  (EmpathyLogWindow *window,
                TpAccount        *account,
                const gchar     *chat_id,
                gboolean         is_chatroom)
{
    EmpathyAccountChooser *account_chooser;
    GtkTreeView          *view;
    GtkTreeModel         *model;
    GtkTreeSelection     *selection;
    GtkTreeIter           iter;
    GtkTreePath          *path;
    gboolean              ok;

    account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser_chats);
    empathy_account_chooser_set_account (account_chooser, account);

    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)) {
        TpAccount *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 (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 (EmpathyLogWindow  *window,
                   TpAccount       **account,
                   gchar           **chat_id,
                   gboolean         *is_chatroom)
{
    GtkTreeView      *view;
    GtkTreeModel     *model;
    GtkTreeSelection *selection;
    GtkTreeIter       iter;
    gchar            *id = NULL;
    TpAccount        *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_got_messages_for_date_cb (GObject *manager,
    GAsyncResult *result,
    gpointer user_data)
{
  EmpathyLogWindow *window = user_data;
  GList *messages;
  GList *l;
  GError *error = NULL;

  if (log_window == NULL)
    return;

  if (!tpl_log_manager_get_messages_for_date_finish (TPL_LOG_MANAGER (manager),
        result, &messages, &error)) {
      DEBUG ("Unable to retrieve messages for the selected date: %s. Aborting",
          error->message);
      empathy_chat_view_append_event (window->chatview_find,
          "Unable to retrieve messages for the selected date");
      g_error_free (error);
      return;
  }

  for (l = messages; l; l = l->next) {
      EmpathyMessage *message = empathy_message_from_tpl_log_entry (l->data);
      g_object_unref (l->data);
      empathy_chat_view_append_message (window->chatview_chats,
          message);
      g_object_unref (message);
  }
  g_list_free (messages);

  /* Turn back on scrolling */
  empathy_chat_view_scroll (window->chatview_find, TRUE);

  /* Give the search entry main focus */
  gtk_widget_grab_focus (window->entry_chats);
}


static void
log_window_get_messages_for_date (EmpathyLogWindow *window,
                  GDate *date)
{
  TpAccount *account;
  gchar *chat_id;
  gboolean is_chatroom;

  if (!log_window_chats_get_selected (window, &account,
        &chat_id, &is_chatroom)) {
      return;
  }

  /* Clear all current messages shown in the textview */
  empathy_chat_view_clear (window->chatview_chats);

  /* Turn off scrolling temporarily */
  empathy_chat_view_scroll (window->chatview_find, FALSE);

  /* Get messages */
  tpl_log_manager_get_messages_for_date_async (window->log_manager,
      account, chat_id,
      is_chatroom,
      date,
      log_window_got_messages_for_date_cb,
      (gpointer) window);
}

static void
log_manager_got_dates_cb (GObject *manager,
                          GAsyncResult *result,
                          gpointer user_data)
{
  EmpathyLogWindow *window = user_data;
  GList         *dates;
  GList         *l;
  guint          year_selected;
  guint          month_selected;
  gboolean       day_selected = FALSE;
  GDate         *date = NULL;
  GError        *error = NULL;

  if (log_window == NULL)
    return;

  if (!tpl_log_manager_get_dates_finish (TPL_LOG_MANAGER (manager),
        result, &dates, &error)) {
    DEBUG ("Unable to retrieve messages' dates: %s. Aborting",
        error->message);
    empathy_chat_view_append_event (window->chatview_find,
        "Unable to retrieve messages' dates");
      return;
  }

  for (l = dates; l; l = l->next) {
      GDate *d = l->data;

      gtk_calendar_get_date (GTK_CALENDAR (window->calendar_chats),
          &year_selected,
          &month_selected,
          NULL);

      month_selected++;

      if (!l->next) {
          date = d;
      }

      if (g_date_get_year (d) != year_selected ||
          g_date_get_month (d) != month_selected) {
          continue;
      }

      DEBUG ("Marking date: %04u-%02u-%02u", g_date_get_year (d),
          g_date_get_month (d), g_date_get_day (d));

      gtk_calendar_mark_day (GTK_CALENDAR (window->calendar_chats),
          g_date_get_day (d));

      if (l->next) {
          continue;
      }

      day_selected = TRUE;

      gtk_calendar_select_day (GTK_CALENDAR (window->calendar_chats),
          g_date_get_day (d));
  }

  if (!day_selected) {
      /* Unselect the day in the calendar */
      gtk_calendar_select_day (GTK_CALENDAR (window->calendar_chats), 0);
  }

  g_signal_handlers_unblock_by_func (window->calendar_chats,
      log_window_calendar_chats_day_selected_cb,
      window);

  if (date) {
      /* Show messages of the most recent date */
      log_window_get_messages_for_date (window, date);
  }

  g_list_foreach (dates, (GFunc) g_free, NULL);
  g_list_free (dates);
}


static void
log_window_chats_get_messages (EmpathyLogWindow *window,
                   GDate     *date)
{
    TpAccount     *account;
    gchar         *chat_id;
    gboolean       is_chatroom;
    guint          year_selected;
    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 */
    if (date == NULL) {
        /* Get a list of dates and show them on the calendar */
        tpl_log_manager_get_dates_async (window->log_manager,
                               account, chat_id,
                               is_chatroom,
                               log_manager_got_dates_cb, (gpointer) window);
    /* signal unblocked at the end of the CB flow */
    } else {
        day = g_date_get_day (date);
        gtk_calendar_get_date (GTK_CALENDAR (window->calendar_chats),
                &year_selected,
                &month_selected,
                NULL);

        month_selected++;

        if (g_date_get_year (date) != year_selected &&
            g_date_get_month (date) != 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);
    }

    if (date != NULL) {
        /* Show messages of the selected date */
        log_window_get_messages_for_date (window, date);
    }

    g_object_unref (account);
    g_free (chat_id);
}

static void
log_window_calendar_chats_day_selected_cb (GtkWidget       *calendar,
                       EmpathyLogWindow *window)
{
    guint  year;
    guint  month;
    guint  day;
    GDate *date;

    gtk_calendar_get_date (GTK_CALENDAR (calendar), &year, &month, &day);
    if (day == 0)
        /* No date selected */
        return;

    /* We need this hear because it appears that the months start from 0 */
    month++;

    date = g_date_new_dmy (day, month, year);

    DEBUG ("Currently selected date is: %04u-%02u-%02u", year, month, day);

    log_window_chats_get_messages (window, date);

    g_date_free (date);
}

static void
log_window_updating_calendar_month_cb (GObject *manager,
        GAsyncResult *result, gpointer user_data)
{
    EmpathyLogWindow *window = user_data;
    GList                   *dates;
    GList                   *l;
    guint                    year_selected;
    guint                    month_selected;
    GError              *error = NULL;

    if (log_window == NULL)
        return;

    if (!tpl_log_manager_get_dates_finish (TPL_LOG_MANAGER (manager),
        result, &dates, &error)) {
            DEBUG ("Unable to retrieve messages' dates: %s. Aborting",
                    error->message);
            empathy_chat_view_append_event (window->chatview_find,
                    "Unable to retrieve messages' dates");
            g_error_free (error);
            return;
    }

    gtk_calendar_clear_marks (GTK_CALENDAR (window->calendar_chats));
    g_object_get (window->calendar_chats,
            "month", &month_selected,
            "year", &year_selected,
            NULL);

    /* We need this here because it appears that the months start from 0 */
    month_selected++;

    for (l = dates; l; l = l->next) {
            GDate *date = l->data;

            if (g_date_get_year (date) == year_selected &&
                g_date_get_month (date) == month_selected) {
                    DEBUG ("Marking date: %04u-%02u-%02u", g_date_get_year (date),
                        g_date_get_month (date), g_date_get_day (date));
                    gtk_calendar_mark_day (GTK_CALENDAR (window->calendar_chats), g_date_get_day (date));
            }
    }

    g_list_foreach (dates, (GFunc) g_free, NULL);
    g_list_free (dates);

    DEBUG ("Currently showing month %d and year %d", month_selected,
            year_selected);
}

static void
log_window_calendar_chats_month_changed_cb (GtkWidget       *calendar,
                        EmpathyLogWindow *window)
{
    TpAccount     *account;
    gchar         *chat_id;
    gboolean       is_chatroom;

    gtk_calendar_clear_marks (GTK_CALENDAR (calendar));

    if (!log_window_chats_get_selected (window, &account,
                        &chat_id, &is_chatroom)) {
        DEBUG ("No chat selected to get dates for...");
        return;
    }

    /* Get the log object for this contact */
    tpl_log_manager_get_dates_async (window->log_manager, account,
                           chat_id, is_chatroom,
                           log_window_updating_calendar_month_cb,
                           (gpointer) window);

    g_object_unref (account);
    g_free (chat_id);
}

static void
log_window_entry_chats_changed_cb (GtkWidget       *entry,
                   EmpathyLogWindow *window)
{
    const gchar *str;

    str = gtk_entry_get_text (GTK_ENTRY (window->entry_chats));
    empathy_chat_view_highlight (window->chatview_chats, str, FALSE);

    if (str) {
        empathy_chat_view_find_next (window->chatview_chats,
                        str,
                        TRUE,
                        FALSE);
    }
}

static void
log_window_entry_chats_activate_cb (GtkWidget       *entry,
                    EmpathyLogWindow *window)
{
    const gchar *str;

    str = gtk_entry_get_text (GTK_ENTRY (window->entry_chats));

    if (str) {
        empathy_chat_view_find_next (window->chatview_chats,
                        str,
                        FALSE,
                        FALSE);
    }
}