aboutsummaryrefslogblamecommitdiffstats
path: root/e-util/e-table-sorter.c
blob: d8e50db13d498e783dba3df2789ee75c19eea3ba (plain) (tree)
1
2
3
4
5
6
7
8
  

                   



                                                                
  



                                                                    
  
                                                                   
                                                                             
  
   
 
                    
                   
      
 
                   
                   
 
                       
 
                           
                                  
 

            
      

                      

  









                                                                              
 

























































































                                                                                                                            
                                                
                               
                                      

                                   


                                                                   
                    











                                                                  

                                            


                                                                        


                                             
                                                                    









                                                                                           
           
                                                  
 











                                                                      
         
 
 





                                                          
 






                                                              
 































                                                                


           



                                               
 
                                                             
 
                              
                            









                                                                             

                 
                                                                     
 









                                                                        






                      



                                             
 
                                                             
                              
                            
                                                                    



                      





























































                                                                       
           
                                              

                                       

                                                                   



                                              

                                                     
 

                                                     




                           
                                              

                                       

                                                                   



                                              

                                                 
 

                                                 




                           
                                                        


                                                     

                                                             
                             
                                                     

                          
                                                          
                          
                                                                               



           
                                                        


                                                     

                                                             
                             
                                                 

                          
                                                      
                          
                                                                               



               
                                            
 




                                                                                                                                                   
                    
                                                        
         
                                           
 
 
           
                                                    
 
                                   
 



                                                               
 








                                               


           









                                                                                      
                                                
 
                                         

 
              


                                              
 
                                   
 



                                                                
 
                                                                 
                                        
                                                                          
 
                                                                     
                                            
                                                                              
 
                                                                      
                                             
                                                                               
 
                                                                       
                                              
                                                                                
 
                                                                      
                                             
                                                                               
 
                                                               
                                               
                                                                              
 
                                                                
                                                
                                                                              
 
                            

 
/*
 * e-table-sorter.c
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

#include <glib/gi18n.h>

#include "e-table-sorter.h"
#include "e-table-sorting-utils.h"

#define d(x)

enum {
    PROP_0,
    PROP_SORT_INFO
};

/* Forward Declarations */
static void e_table_sorter_interface_init   (ESorterInterface *interface);

G_DEFINE_TYPE_WITH_CODE (
    ETableSorter,
    e_table_sorter,
    G_TYPE_OBJECT,
    G_IMPLEMENT_INTERFACE (
        E_TYPE_SORTER,
        e_table_sorter_interface_init))

struct qsort_data {
    ETableSorter *table_sorter;
    gpointer *vals;
    gint cols;
    gint *ascending;
    GCompareDataFunc *compare;
    gpointer cmp_cache;
};

/* FIXME: Make it not cache the second and later columns (as if anyone cares.) */

static gint
qsort_callback (gconstpointer data1,
                gconstpointer data2,
                gpointer user_data)
{
    struct qsort_data *qd = (struct qsort_data *) user_data;
    gint row1 = *(gint *) data1;
    gint row2 = *(gint *) data2;
    gint j;
    gint sort_count;
    gint comp_val = 0;
    gint ascending = 1;

    sort_count =
        e_table_sort_info_sorting_get_count (
            qd->table_sorter->sort_info) +
        e_table_sort_info_grouping_get_count (
            qd->table_sorter->sort_info);

    for (j = 0; j < sort_count; j++) {
        comp_val = (*(qd->compare[j]))(qd->vals[qd->cols * row1 + j], qd->vals[qd->cols * row2 + j], qd->cmp_cache);
        ascending = qd->ascending[j];
        if (comp_val != 0)
            break;
    }
    if (comp_val == 0) {
        if (row1 < row2)
            comp_val = -1;
        if (row1 > row2)
            comp_val = 1;
    }
    if (!ascending)
        comp_val = -comp_val;

    return comp_val;
}

static void
table_sorter_clean (ETableSorter *table_sorter)
{
    g_free (table_sorter->sorted);
    table_sorter->sorted = NULL;

    g_free (table_sorter->backsorted);
    table_sorter->backsorted = NULL;

    table_sorter->needs_sorting = -1;
}

static void
table_sorter_sort (ETableSorter *table_sorter)
{
    gint rows;
    gint i;
    gint j;
    gint cols;
    gint group_cols;
    struct qsort_data qd;

    if (table_sorter->sorted)
        return;

    rows = e_table_model_row_count (table_sorter->source);
    group_cols = e_table_sort_info_grouping_get_count (table_sorter->sort_info);
    cols = e_table_sort_info_sorting_get_count (table_sorter->sort_info) + group_cols;

    table_sorter->sorted = g_new (int, rows);
    for (i = 0; i < rows; i++)
        table_sorter->sorted[i] = i;

    qd.cols = cols;
    qd.table_sorter = table_sorter;

    qd.vals = g_new (gpointer , rows * cols);
    qd.ascending = g_new (int, cols);
    qd.compare = g_new (GCompareDataFunc, cols);
    qd.cmp_cache = e_table_sorting_utils_create_cmp_cache ();

    for (j = 0; j < cols; j++) {
        ETableColumnSpecification *spec;
        ETableCol *col;
        GtkSortType sort_type;

        if (j < group_cols)
            spec = e_table_sort_info_grouping_get_nth (
                table_sorter->sort_info,
                j, &sort_type);
        else
            spec = e_table_sort_info_sorting_get_nth (
                table_sorter->sort_info,
                j - group_cols, &sort_type);

        col = e_table_header_get_column_by_spec (
            table_sorter->full_header, spec);
        if (col == NULL) {
            gint last = e_table_header_count (
                table_sorter->full_header) - 1;
            col = e_table_header_get_column (
                table_sorter->full_header, last);
        }

        for (i = 0; i < rows; i++) {
            qd.vals[i * cols + j] = e_table_model_value_at (
                table_sorter->source,
                col->spec->model_col, i);
        }

        qd.compare[j] = col->compare;
        qd.ascending[j] = (sort_type == GTK_SORT_ASCENDING);
    }

    g_qsort_with_data (table_sorter->sorted, rows, sizeof (gint), qsort_callback, &qd);

    g_free (qd.vals);
    g_free (qd.ascending);
    g_free (qd.compare);
    e_table_sorting_utils_free_cmp_cache (qd.cmp_cache);
}

static void
table_sorter_backsort (ETableSorter *table_sorter)
{
    gint i, rows;

    if (table_sorter->backsorted)
        return;

    table_sorter_sort (table_sorter);

    rows = e_table_model_row_count (table_sorter->source);
    table_sorter->backsorted = g_new0 (int, rows);

    for (i = 0; i < rows; i++) {
        table_sorter->backsorted[table_sorter->sorted[i]] = i;
    }
}

static void
table_sorter_model_changed_cb (ETableModel *table_model,
                               ETableSorter *table_sorter)
{
    table_sorter_clean (table_sorter);
}

static void
table_sorter_model_row_changed_cb (ETableModel *table_model,
                                   gint row,
                                   ETableSorter *table_sorter)
{
    table_sorter_clean (table_sorter);
}

static void
table_sorter_model_cell_changed_cb (ETableModel *table_model,
                                    gint col,
                                    gint row,
                                    ETableSorter *table_sorter)
{
    table_sorter_clean (table_sorter);
}

static void
table_sorter_model_rows_inserted_cb (ETableModel *table_model,
                                     gint row,
                                     gint count,
                                     ETableSorter *table_sorter)
{
    table_sorter_clean (table_sorter);
}

static void
table_sorter_model_rows_deleted_cb (ETableModel *table_model,
                                    gint row,
                                    gint count,
                                    ETableSorter *table_sorter)
{
    table_sorter_clean (table_sorter);
}

static void
table_sorter_sort_info_changed_cb (ETableSortInfo *sort_info,
                                   ETableSorter *table_sorter)
{
    table_sorter_clean (table_sorter);
}

static void
table_sorter_set_property (GObject *object,
                           guint property_id,
                           const GValue *value,
                           GParamSpec *pspec)
{
    ETableSorter *table_sorter = E_TABLE_SORTER (object);

    switch (property_id) {
    case PROP_SORT_INFO:
        if (table_sorter->sort_info) {
            if (table_sorter->sort_info_changed_id)
                g_signal_handler_disconnect (
                    table_sorter->sort_info,
                    table_sorter->sort_info_changed_id);
            if (table_sorter->group_info_changed_id)
                g_signal_handler_disconnect (
                    table_sorter->sort_info,
                    table_sorter->group_info_changed_id);
            g_object_unref (table_sorter->sort_info);
        }

        table_sorter->sort_info = g_value_dup_object (value);

        table_sorter->sort_info_changed_id = g_signal_connect (
            table_sorter->sort_info, "sort_info_changed",
            G_CALLBACK (table_sorter_sort_info_changed_cb),
            table_sorter);
        table_sorter->group_info_changed_id = g_signal_connect (
            table_sorter->sort_info, "group_info_changed",
            G_CALLBACK (table_sorter_sort_info_changed_cb),
            table_sorter);

        table_sorter_clean (table_sorter);
        break;
    default:
        break;
    }
}

static void
table_sorter_get_property (GObject *object,
                           guint property_id,
                           GValue *value,
                           GParamSpec *pspec)
{
    ETableSorter *table_sorter = E_TABLE_SORTER (object);
    switch (property_id) {
    case PROP_SORT_INFO:
        g_value_set_object (value, table_sorter->sort_info);
        break;
    }
}

static void
table_sorter_dispose (GObject *object)
{
    ETableSorter *table_sorter = E_TABLE_SORTER (object);

    if (table_sorter->table_model_changed_id > 0) {
        g_signal_handler_disconnect (
            table_sorter->source,
            table_sorter->table_model_changed_id);
        table_sorter->table_model_changed_id = 0;
    }

    if (table_sorter->table_model_row_changed_id > 0) {
        g_signal_handler_disconnect (
            table_sorter->source,
            table_sorter->table_model_row_changed_id);
        table_sorter->table_model_row_changed_id = 0;
    }

    if (table_sorter->table_model_cell_changed_id > 0) {
        g_signal_handler_disconnect (
            table_sorter->source,
            table_sorter->table_model_cell_changed_id);
        table_sorter->table_model_cell_changed_id = 0;
    }

    if (table_sorter->table_model_rows_inserted_id > 0) {
        g_signal_handler_disconnect (
            table_sorter->source,
            table_sorter->table_model_rows_inserted_id);
        table_sorter->table_model_rows_inserted_id = 0;
    }

    if (table_sorter->table_model_rows_deleted_id > 0) {
        g_signal_handler_disconnect (
            table_sorter->source,
            table_sorter->table_model_rows_deleted_id);
        table_sorter->table_model_rows_deleted_id = 0;
    }

    if (table_sorter->sort_info_changed_id > 0) {
        g_signal_handler_disconnect (
            table_sorter->sort_info,
            table_sorter->sort_info_changed_id);
        table_sorter->sort_info_changed_id = 0;
    }

    if (table_sorter->group_info_changed_id > 0) {
        g_signal_handler_disconnect (
            table_sorter->sort_info,
            table_sorter->group_info_changed_id);
        table_sorter->group_info_changed_id = 0;
    }

    g_clear_object (&table_sorter->sort_info);
    g_clear_object (&table_sorter->full_header);
    g_clear_object (&table_sorter->source);

    /* Chain up to parent's dispose() method. */
    G_OBJECT_CLASS (e_table_sorter_parent_class)->dispose (object);
}

static gint
table_sorter_model_to_sorted (ESorter *sorter,
                              gint row)
{
    ETableSorter *table_sorter = E_TABLE_SORTER (sorter);
    gint rows = e_table_model_row_count (table_sorter->source);

    g_return_val_if_fail (row >= 0, -1);
    g_return_val_if_fail (row < rows, -1);

    if (e_sorter_needs_sorting (sorter))
        table_sorter_backsort (table_sorter);

    if (table_sorter->backsorted)
        return table_sorter->backsorted[row];
    else
        return row;
}

static gint
table_sorter_sorted_to_model (ESorter *sorter,
                              gint row)
{
    ETableSorter *table_sorter = E_TABLE_SORTER (sorter);
    gint rows = e_table_model_row_count (table_sorter->source);

    g_return_val_if_fail (row >= 0, -1);
    g_return_val_if_fail (row < rows, -1);

    if (e_sorter_needs_sorting (sorter))
        table_sorter_sort (table_sorter);

    if (table_sorter->sorted)
        return table_sorter->sorted[row];
    else
        return row;
}

static void
table_sorter_get_model_to_sorted_array (ESorter *sorter,
                                        gint **array,
                                        gint *count)
{
    ETableSorter *table_sorter = E_TABLE_SORTER (sorter);

    if (array || count) {
        table_sorter_backsort (table_sorter);

        if (array)
            *array = table_sorter->backsorted;
        if (count)
            *count = e_table_model_row_count(table_sorter->source);
    }
}

static void
table_sorter_get_sorted_to_model_array (ESorter *sorter,
                                        gint **array,
                                        gint *count)
{
    ETableSorter *table_sorter = E_TABLE_SORTER (sorter);

    if (array || count) {
        table_sorter_sort (table_sorter);

        if (array)
            *array = table_sorter->sorted;
        if (count)
            *count = e_table_model_row_count(table_sorter->source);
    }
}

static gboolean
table_sorter_needs_sorting (ESorter *sorter)
{
    ETableSorter *table_sorter = E_TABLE_SORTER (sorter);

    if (table_sorter->needs_sorting < 0) {
        if (e_table_sort_info_sorting_get_count (table_sorter->sort_info) + e_table_sort_info_grouping_get_count (table_sorter->sort_info))
            table_sorter->needs_sorting = 1;
        else
            table_sorter->needs_sorting = 0;
    }
    return table_sorter->needs_sorting;
}

static void
e_table_sorter_class_init (ETableSorterClass *class)
{
    GObjectClass *object_class;

    object_class = G_OBJECT_CLASS (class);
    object_class->set_property = table_sorter_set_property;
    object_class->get_property = table_sorter_get_property;
    object_class->dispose = table_sorter_dispose;

    g_object_class_install_property (
        object_class,
        PROP_SORT_INFO,
        g_param_spec_object (
            "sort_info",
            "Sort Info",
            NULL,
            E_TYPE_TABLE_SORT_INFO,
            G_PARAM_READWRITE));
}

static void
e_table_sorter_interface_init (ESorterInterface *interface)
{
    interface->model_to_sorted = table_sorter_model_to_sorted;
    interface->sorted_to_model = table_sorter_sorted_to_model;
    interface->get_model_to_sorted_array = table_sorter_get_model_to_sorted_array;
    interface->get_sorted_to_model_array = table_sorter_get_sorted_to_model_array;
    interface->needs_sorting = table_sorter_needs_sorting;
}

static void
e_table_sorter_init (ETableSorter *table_sorter)
{
    table_sorter->needs_sorting = -1;
}

ETableSorter *
e_table_sorter_new (ETableModel *source,
                    ETableHeader *full_header,
                    ETableSortInfo *sort_info)
{
    ETableSorter *table_sorter;

    table_sorter = g_object_new (E_TYPE_TABLE_SORTER, NULL);
    table_sorter->sort_info = g_object_ref (sort_info);
    table_sorter->full_header = g_object_ref (full_header);
    table_sorter->source = g_object_ref (source);

    table_sorter->table_model_changed_id = g_signal_connect (
        source, "model_changed",
        G_CALLBACK (table_sorter_model_changed_cb), table_sorter);

    table_sorter->table_model_row_changed_id = g_signal_connect (
        source, "model_row_changed",
        G_CALLBACK (table_sorter_model_row_changed_cb), table_sorter);

    table_sorter->table_model_cell_changed_id = g_signal_connect (
        source, "model_cell_changed",
        G_CALLBACK (table_sorter_model_cell_changed_cb), table_sorter);

    table_sorter->table_model_rows_inserted_id = g_signal_connect (
        source, "model_rows_inserted",
        G_CALLBACK (table_sorter_model_rows_inserted_cb), table_sorter);

    table_sorter->table_model_rows_deleted_id = g_signal_connect (
        source, "model_rows_deleted",
        G_CALLBACK (table_sorter_model_rows_deleted_cb), table_sorter);

    table_sorter->sort_info_changed_id = g_signal_connect (
        sort_info, "sort_info_changed",
        G_CALLBACK (table_sorter_sort_info_changed_cb), table_sorter);

    table_sorter->group_info_changed_id = g_signal_connect (
        sort_info, "group_info_changed",
        G_CALLBACK (table_sorter_sort_info_changed_cb), table_sorter);

    return table_sorter;
}