/*
 |  Copyright (C) 2002-2010 Jorg Schuler <jcsjcs at users sourceforge net>
 |                                          Paul Richardson  <phantom_sf at users sourceforge net>
 |  Part of the gtkpod project.
 |
 |  URL: http://www.gtkpod.org/
 |  URL: http://gtkpod.sourceforge.net/
 |
 |  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
 |
 |  iTunes and iPod are trademarks of Apple
 |
 |  This product is not supported/written/published by Apple!
 |
 |  $Id$
 */

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

#include <gdk/gdkkeysyms.h>
#include <string.h>
#include <stdlib.h>
#include "libgtkpod/prefs.h"
#include "libgtkpod/misc.h"
#include "libgtkpod/misc_track.h"
#include "libgtkpod/itdb.h"
#include "libgtkpod/gp_private.h"
#include "libgtkpod/gtkpod_app_iface.h"
#include "display_sorttabs.h"
#include "sorttab_display_context_menu.h"
#include "plugin.h"

typedef struct {
    GtkTreeView *tree_view;
    guint32 inst;
} StSelectionEvent;

#define ST_AUTOSELECT(i) TRUE
/* #define ST_AUTOSELECT(i) (prefs_get_int_index("st_autoselect", (i))) */

static gchar *glade_file_path;

/* array with pointers to the sort tabs */
static SortTab *sorttab[SORT_TAB_MAX];
/* pointer to paned elements holding the sort tabs */
static GtkPaned *st_paned[PANED_NUM_ST];
/* compare function to be used for string comparisons */

static void sp_store_sp_entries(gint inst);
static void st_page_selected(GtkNotebook *notebook, guint page);
static void st_create_notebook(gint inst);
static TimeInfo *sp_update_date_interval_from_string(guint32 inst, T_item item, gboolean force_update);
void st_show_hide_tooltips(void);

/* Drag and drop definitions */
static GtkTargetEntry st_drag_types[] =
    {
        { DND_GTKPOD_TRACKLIST_TYPE, 0, DND_GTKPOD_TRACKLIST },
        { "text/uri-list", 0, DND_TEXT_URI_LIST },
        { "text/plain", 0, DND_TEXT_PLAIN },
        { "STRING", 0, DND_TEXT_PLAIN } };

typedef enum {
    IS_INSIDE, /* track's timestamp is inside the specified interval  */
    IS_OUTSIDE, /* track's timestamp is outside the specified interval */
    IS_ERROR,
/* error parsing date string (or wrong parameters)    */
} IntervalState;

GtkBuilder *cal_xml;

/* ---------------------------------------------------------------- */
/*                                                                  */
/* Section for filter tab display (callback)                        */
/*                                                                  */
/* ---------------------------------------------------------------- */

/*
 * utility function for appending ipod track for st treeview callback
 */
static void on_st_dnd_get_track_foreach(GtkTreeModel *tm, GtkTreePath *tp, GtkTreeIter *i, gpointer data) {
    GList *gl;
    TabEntry *entry = NULL;
    GString *tracklist = (GString *) data;

    g_return_if_fail (tracklist);

    gtk_tree_model_get(tm, i, ST_COLUMN_ENTRY, &entry, -1);
    g_return_if_fail (entry);

    /* add all member tracks of entry to tracklist */
    for (gl = entry->members; gl; gl = gl->next) {
        Track *tr = gl->data;
        g_return_if_fail (tr);
        g_string_append_printf(tracklist, "%p\n", tr);
    }
}

/*
 * utility function for appending filenames for st treeview callback
 */
static void on_st_dnd_get_file_foreach(GtkTreeModel *tm, GtkTreePath *tp, GtkTreeIter *iter, gpointer data) {
    GList *gl;
    TabEntry *entry = NULL;
    GString *filelist = data;

    g_return_if_fail (tm);
    g_return_if_fail (iter);
    g_return_if_fail (data);

    gtk_tree_model_get(tm, iter, ST_COLUMN_ENTRY, &entry, -1);
    g_return_if_fail (entry);

    /* add all member tracks of entry to tracklist */
    for (gl = entry->members; gl; gl = gl->next) {
        gchar *name;
        Track *tr = gl->data;

        g_return_if_fail (tr);
        name = get_file_name_from_source(tr, SOURCE_PREFER_LOCAL);
        if (name) {
            g_string_append_printf(filelist, "file:%s\n", name);
            g_free(name);
        }
    }
}

static void st_drag_end(GtkWidget *widget, GdkDragContext *dc, gpointer user_data) {
    /*     puts ("st_drag_end"); */
    gtkpod_tracks_statusbar_update();
}

/*
 * utility function for appending uris for st treeview callback
 */
static void on_st_dnd_get_uri_foreach(GtkTreeModel *tm, GtkTreePath *tp, GtkTreeIter *iter, gpointer data) {
    GList *gl;
    TabEntry *entry = NULL;
    GString *filelist = data;

    g_return_if_fail (tm);
    g_return_if_fail (iter);
    g_return_if_fail (data);

    gtk_tree_model_get(tm, iter, ST_COLUMN_ENTRY, &entry, -1);
    g_return_if_fail (entry);

    /* add all member tracks of entry to tracklist */
    for (gl = entry->members; gl; gl = gl->next) {
        gchar *name;
        Track *tr = gl->data;

        g_return_if_fail (tr);
        name = get_file_name_from_source(tr, SOURCE_PREFER_LOCAL);
        if (name) {
            gchar *uri = g_filename_to_uri(name, NULL, NULL);
            if (uri) {
                g_string_append_printf(filelist, "file:%s\n", name);
                g_free(uri);
            }
            g_free(name);
        }
    }
}

static void st_drag_data_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data, guint info, guint time, gpointer user_data) {
    GtkTreeSelection *ts = NULL;
    GString *reply = g_string_sized_new(2000);

    if (!data)
        return;

    ts = gtk_tree_view_get_selection(GTK_TREE_VIEW (widget));
    if (ts) {
        switch (info) {
        case DND_GTKPOD_TRACKLIST:
            gtk_tree_selection_selected_foreach(ts, on_st_dnd_get_track_foreach, reply);
            break;
        case DND_TEXT_URI_LIST:
            gtk_tree_selection_selected_foreach(ts, on_st_dnd_get_uri_foreach, reply);
            break;
        case DND_TEXT_PLAIN:
            gtk_tree_selection_selected_foreach(ts, on_st_dnd_get_file_foreach, reply);
            break;
        default:
            g_warning ("Programming error: st_drag_data_get received unknown info type (%d)\n", info);
            break;
        }
    }
    gtk_selection_data_set(data, gtk_selection_data_get_target(data), 8, reply->str, reply->len);
    g_string_free(reply, TRUE);
}

/* delete selected entry in sort tab */
static gboolean on_st_treeview_key_release_event(GtkWidget *widget, GdkEventKey *event, gpointer user_data) {
    guint mods;
    mods = event->state;

    if (!widgets_blocked && (mods & GDK_CONTROL_MASK)) {
        switch (event->keyval) {
        /* 	    case GDK_u: */
        /* 		gp_do_selected_entry (update_tracks, */
        /* 				   st_get_instance_from_treeview ( */
        /* 				       GTK_TREE_VIEW (widget))); */
        /* 		break; */
        default:
            break;
        }

    }
    return FALSE;
}

static void st_build_sortkeys(TabEntry *entry) {
    C_FREE (entry->name_sortkey);
    C_FREE (entry->name_fuzzy_sortkey);
    gint case_sensitive = prefs_get_int("st_case_sensitive");
    entry->name_sortkey = make_sortkey(entry->name, case_sensitive);
    if (entry->name != fuzzy_skip_prefix(entry->name)) {
        entry->name_fuzzy_sortkey = make_sortkey(fuzzy_skip_prefix(entry->name), case_sensitive);
    }
}

void st_rebuild_sortkeys() {
    gint inst;
    for (inst = 0; inst < prefs_get_int("sort_tab_num"); inst++) {
        SortTab *st = sorttab[inst];
        GList *entries;

        for (entries = st->entries; entries; entries = g_list_next (entries)) {
            TabEntry *entry = (TabEntry *) entries->data;
            st_build_sortkeys(entry);
        }
    }
}

gint compare_entry(const TabEntry *a, const TabEntry *b) {
    return strcmp(a->name_sortkey, b->name_sortkey);
}

gint compare_entry_fuzzy(const TabEntry *a, const TabEntry *b) {
    const gchar *ka, *kb;
    ka = a->name_fuzzy_sortkey ? a->name_fuzzy_sortkey : a->name_sortkey;
    kb = b->name_fuzzy_sortkey ? b->name_fuzzy_sortkey : b->name_sortkey;
    return strcmp(ka, kb);
}

/* set string compare function according to whether the ignore field
 is set or not */
static void st_set_string_compare_func(guint inst, guint page_num) {
    gchar *buf;
    if (page_num != ST_CAT_SPECIAL) {
        buf = g_strdup_printf("sort_ign_field_%d", ST_to_T(page_num));
        if (prefs_get_int(buf))
            sorttab[inst]->entry_compare_func = compare_entry_fuzzy;
        else
            sorttab[inst]->entry_compare_func = compare_entry;
        g_free(buf);
    }
}

/* callback */
static void on_st_switch_page(GtkNotebook *notebook, GtkWidget *page, guint page_num, gpointer user_data) {
    guint inst = GPOINTER_TO_UINT( user_data );

    /*     printf ("switch_page: inst/page: %d/%d\n", inst, page_num); */
    /* set compare function for strings (to speed up sorting) */
    if (page_num != ST_CAT_SPECIAL) {
        st_set_string_compare_func(inst, page_num);
    }
    st_page_selected(notebook, page_num);
}

/* ---------------------------------------------------------------- */
/*                                                                  */
/* Section for filter tab display (special sort tab)                */
/*                                                                  */
/* ---------------------------------------------------------------- */

/* Set rating  for tab @inst and rating @n */
static void set_sp_rating_n(guint32 inst, gint n, gboolean state) {
    guint32 rating;

    if ((inst <= SORT_TAB_MAX) && (n <= RATING_MAX)) {
        rating = (guint32) prefs_get_int_index("sp_rating_state", inst);

        if (state)
            rating |= (1 << n);
        else
            rating &= ~(1 << n);

        prefs_set_int_index("sp_rating_state", inst, rating);
    }
}

static gboolean get_sp_rating_n(guint32 inst, gint n) {
    guint32 rating;

    if ((inst <= SORT_TAB_MAX) && (n <= RATING_MAX)) {
        rating = (guint32) prefs_get_int_index("sp_rating_state", inst);

        if ((rating & (1 << n)) != 0)
            return TRUE;
        else
            return FALSE;
    }

    return FALSE;
}

/* Remove all members of special sort tab (ST_CAT_SPECIAL) in instance
 * @inst */
static void sp_remove_all_members(guint32 inst) {
    SortTab *st;

    /* Sanity */
    if (inst >= prefs_get_int("sort_tab_num"))
        return;

    st = sorttab[inst];

    if (!st)
        return;

    g_list_free(st->sp_members);
    st->sp_members = NULL;
    g_list_free(st->sp_selected);
    st->sp_selected = NULL;
}

/* Return a pointer to ti_added, ti_modified or ti_played. Returns
 NULL if either inst or item are out of range */
static TimeInfo *sp_get_timeinfo_ptr(guint32 inst, T_item item) {
    if (inst >= SORT_TAB_MAX) {
        fprintf(stderr, "Programming error: st_get_timeinfo_ptr: inst out of range: %d\n", inst);
    }
    else {
        SortTab *st = sorttab[inst];
        switch (item) {
        case T_TIME_ADDED:
            return &st->ti_added;
        case T_TIME_PLAYED:
            return &st->ti_played;
        case T_TIME_MODIFIED:
            return &st->ti_modified;
        default:
            fprintf(stderr, "Programming error: st_get_timeinfo_ptr: item invalid: %d\n", item);
        }
    }
    return NULL;
}

/* Update the date interval from the string provided by
 prefs_get_sp_entry() */
/* @inst: instance
 @item: T_TIME_PLAYED, or T_TIME_MODIFIED,
 @force_update: usually the update is only performed if the string
 has changed. TRUE will re-evaluate the string (and print an error
 message again, if necessary */
/* Return value: pointer to the corresponding TimeInfo struct (for
 convenience) or NULL if error occurred */
static TimeInfo *sp_update_date_interval_from_string(guint32 inst, T_item item, gboolean force_update) {
    SortTab *st;
    TimeInfo *ti;

    if (inst >= SORT_TAB_MAX)
        return NULL;

    st = sorttab[inst];
    ti = sp_get_timeinfo_ptr(inst, item);

    if (ti) {
        gchar *new_string = NULL;
        switch (item) {
        case T_TIME_PLAYED:
            new_string = prefs_get_string_index("sp_played_state", inst);
            break;
        case T_TIME_MODIFIED:
            new_string = prefs_get_string_index("sp_modified_state", inst);
            break;
        case T_TIME_ADDED:
            new_string = prefs_get_string_index("sp_added_state", inst);
            ;
            break;
        default:
            break;
        }

        if (!new_string) {
            new_string = g_strdup("");
        }

        if (force_update || !ti->int_str || (strcmp(new_string, ti->int_str) != 0)) { /* Re-evaluate the interval */
            g_free(ti->int_str);
            ti->int_str = g_strdup(new_string);
            dp2_parse(ti);
        }
        g_free(new_string);
    }

    return ti;
}

/* check if @track's timestamp is within the interval given for @item.
 *
 * Return value:
 *
 * IS_ERROR:   error parsing date string (or wrong parameters)
 * IS_INSIDE:  track's timestamp is inside the specified interval
 * IS_OUTSIDE: track's timestamp is outside the specified interval
 */
static IntervalState sp_check_time(guint32 inst, T_item item, Track *track) {
    TimeInfo *ti;
    IntervalState result = IS_ERROR;

    ti = sp_update_date_interval_from_string(inst, item, FALSE);
    if (ti && ti->valid) {
        guint32 stamp = track_get_timestamp(track, item);
        if (stamp && (ti->lower <= stamp) && (stamp <= ti->upper))
            result = IS_INSIDE;
        else
            result = IS_OUTSIDE;
    }
    if (result == IS_ERROR) {
        switch (item) {
        case T_TIME_PLAYED:
            gtkpod_statusbar_message(_("'Played' condition ignored because of error."));
            break;
        case T_TIME_MODIFIED:
            gtkpod_statusbar_message(_("'Modified' condition ignored because of error."));
            break;
        case T_TIME_ADDED:
            gtkpod_statusbar_message(_("'Added' condition ignored because of error."));
            break;
        default:
            break;
        }
    }
    return result;
}

/* decide whether or not @track satisfies the conditions specified in
 * the special sort tab of instance @inst.
 * Return value:  TRUE: satisfies, FALSE: does not satisfy */
static gboolean sp_check_track(Track *track, guint32 inst) {
    gboolean sp_or = prefs_get_int_index("sp_or", inst);
    gboolean result, cond, checked = FALSE;

    if (!track)
        return FALSE;

    /* Initial state depends on logical operation */
    if (sp_or)
        result = FALSE; /* OR  */
    else
        result = TRUE; /* AND */

    /* RATING */
    if (prefs_get_int_index("sp_rating_cond", inst)) {
        /* checked = TRUE: at least one condition was checked */
        checked = TRUE;
        cond = get_sp_rating_n(inst, track->rating / ITDB_RATING_STEP);
        /* If one of the two combinations occur, we can take a
         shortcut and stop checking the other conditions */
        if (sp_or && cond)
            return TRUE;
        if ((!sp_or) && (!cond))
            return FALSE;
        /* We don't have to calculate a new 'result' value because for
         the other two combinations it does not change */
    }

    /* PLAYCOUNT */
    if (prefs_get_int_index("sp_playcount_cond", inst)) {
        guint32 low = prefs_get_int_index("sp_playcount_low", inst);
        /* "-1" will translate into about 4 billion because I use
         guint32 instead of gint32. Since 4 billion means "no upper
         limit" the logic works fine */
        guint32 high = prefs_get_int_index("sp_playcount_high", inst);
        checked = TRUE;
        if ((low <= track->playcount) && (track->playcount <= high))
            cond = TRUE;
        else
            cond = FALSE;
        if (sp_or && cond)
            return TRUE;
        if ((!sp_or) && (!cond))
            return FALSE;
    }
    /* time played */
    if (prefs_get_int_index("sp_played_cond", inst)) {
        IntervalState result = sp_check_time(inst, T_TIME_PLAYED, track);
        if (sp_or && (result == IS_INSIDE))
            return TRUE;
        if ((!sp_or) && (result == IS_OUTSIDE))
            return FALSE;
        if (result != IS_ERROR)
            checked = TRUE;
    }
    /* time modified */
    if (prefs_get_int_index("sp_modified_cond", inst)) {
        IntervalState result = sp_check_time(inst, T_TIME_MODIFIED, track);
        if (sp_or && (result == IS_INSIDE))
            return TRUE;
        if ((!sp_or) && (result == IS_OUTSIDE))
            return FALSE;
        if (result != IS_ERROR)
            checked = TRUE;
    }
    /* time added */
    if (prefs_get_int_index("sp_added_cond", inst)) {
        IntervalState result = sp_check_time(inst, T_TIME_ADDED, track);
        if (sp_or && (result == IS_INSIDE))
            return TRUE;
        if ((!sp_or) && (result == IS_OUTSIDE))
            return FALSE;
        if (result != IS_ERROR)
            checked = TRUE;
    }

    if (checked)
        return result;
    else
        return FALSE;
}

/* called by st_add_track(): add a track to ST_CAT_SPECIAL */
static void st_add_track_special(Track *track, gboolean final, gboolean display, guint32 inst) {
    SortTab *st;

    /* Sanity */
    if (inst >= prefs_get_int("sort_tab_num"))
        return;

    st = sorttab[inst];

    /* Sanity */
    if (!st)
        return;

    /* Sanity */
    if (st->current_category != ST_CAT_SPECIAL)
        return;

    st->final = final;

    if (track != NULL) {
        /* Add track to member list */
        st->sp_members = g_list_append(st->sp_members, track);
        /* Check if track is to be passed on to next sort tab */
        if (st->is_go || prefs_get_int_index("sp_autodisplay", inst)) { /* Check if track matches sort criteria to be displayed */
            if (sp_check_track(track, inst)) {
                st->sp_selected = g_list_append(st->sp_selected, track);
                st_add_track(track, final, display, inst + 1);
            }
        }
    }
    if (!track && final) {
        if (st->is_go || prefs_get_int_index("sp_autodisplay", inst))
            st_add_track(NULL, final, display, inst + 1);

    }
}

/* Callback for sp_go() */
static void sp_go_cb(gpointer user_data1, gpointer user_data2) {
    guint32 inst = (guint32) GPOINTER_TO_UINT(user_data1);
    SortTab *st = sorttab[inst];

#if DEBUG_TIMING
    GTimeVal time;
    g_get_current_time (&time);
    printf ("sp_go_cb enter: %ld.%06ld sec\n",
            time.tv_sec % 3600, time.tv_usec);
#endif

    /* Sanity */
    if (st == NULL)
        return;

    /* remember that "Display" was already pressed */
    st->is_go = TRUE;

    /* Clear the sp_selected list */
    g_list_free(st->sp_selected);
    st->sp_selected = NULL;

    /* initialize next instance */
    st_init(-1, inst + 1);

    if (st->sp_members) {
        GList *gl;

        st_enable_disable_view_sort(inst + 1, FALSE);
        for (gl = st->sp_members; gl; gl = gl->next) { /* add all member tracks to next instance */
            Track *track = (Track *) gl->data;
            if (sp_check_track(track, inst)) {
                st->sp_selected = g_list_append(st->sp_selected, track);
                st_add_track(track, FALSE, TRUE, inst + 1);
            }
        }
        gtkpod_set_displayed_tracks(st->sp_members);
        st_enable_disable_view_sort(inst + 1, TRUE);
        st_add_track(NULL, TRUE, st->final, inst + 1);
    }
    gtkpod_tracks_statusbar_update();
#if DEBUG_TIMING
    g_get_current_time (&time);
    printf ("st_selection_changed_cb exit:  %ld.%06ld sec\n",
            time.tv_sec % 3600, time.tv_usec);
#endif
}

/* save the contents of the entry to prefs */
static void sp_store_sp_entries(gint inst) {
    SortTab *st;

    /* Sanity */
    if (inst >= prefs_get_int("sort_tab_num"))
        return;

    st = sorttab[inst];

    /* Sanity */
    if (!st || (st->current_category != ST_CAT_SPECIAL))
        return;

    prefs_set_string_index("sp_played_state", inst, gtk_entry_get_text(GTK_ENTRY(st->ti_played.entry)));
    prefs_set_string_index("sp_modified_state", inst, gtk_entry_get_text(GTK_ENTRY(st->ti_modified.entry)));
    prefs_set_string_index("sp_added_state", inst, gtk_entry_get_text(GTK_ENTRY(st->ti_added.entry)));
}

/* display the members satisfying the conditions specified in the
 * special sort tab of instance @inst */
void sp_go(guint32 inst) {
    SortTab *st;

#if DEBUG_CB_INIT
    printf ("st_go: inst: %d\n", inst);
#endif

    /* Sanity */
    if (inst >= prefs_get_int("sort_tab_num"))
        return;

    st = sorttab[inst];

    /* Sanity */
    if (st->current_category != ST_CAT_SPECIAL)
        return;

    /* Make sure the information typed into the entries is actually
     * being used (maybe the user 'forgot' to press enter */
    sp_store_sp_entries(inst);

    sp_go_cb(GUINT_TO_POINTER(inst), NULL);
}

/* called by st_remove_track() */
static void st_remove_track_special(Track *track, guint32 inst) {
    SortTab *st;
    GList *link;

    /* Sanity */
    if (inst >= prefs_get_int("sort_tab_num"))
        return;

    st = sorttab[inst];

    /* Sanity */
    if (st->current_category != ST_CAT_SPECIAL)
        return;

    /* Remove track from member list */
    link = g_list_find(st->sp_members, track);
    if (link) { /* only remove track from next sort tab if it was a member of
     this sort tab (slight performance improvement when being
     called with non-existing tracks */
        st->sp_members = g_list_delete_link(st->sp_members, link);
        st_remove_track(track, inst + 1);
    }
}

/* called by st_track_changed */
static void st_track_changed_special(Track *track, gboolean removed, guint32 inst) {
    SortTab *st;

    /* Sanity */
    if (inst >= prefs_get_int("sort_tab_num"))
        return;

    st = sorttab[inst];

    /* Sanity */
    if (st->current_category != ST_CAT_SPECIAL)
        return;

    if (g_list_find(st->sp_members, track)) { /* only do anything if @track was a member of this sort tab
     (slight performance improvement when being called with
     non-existing tracks */
        if (removed) {
            /* Remove track from member list */
            st->sp_members = g_list_remove(st->sp_members, track);
            if (g_list_find(st->sp_selected, track)) { /* only remove from next sort tab if it was passed on */
                st->sp_selected = g_list_remove(st->sp_selected, track);
                st_track_changed(track, TRUE, inst + 1);
            }
        }
        else {
            if (g_list_find(st->sp_selected, track)) { /* track is being passed on to next sort tab */
                if (sp_check_track(track, inst)) { /* only changed */
                    st_track_changed(track, FALSE, inst + 1);
                }
                else { /* has to be removed */
                    st->sp_selected = g_list_remove(st->sp_selected, track);
                    st_track_changed(track, TRUE, inst + 1);
                }
            }
            else { /* track is not being passed on to next sort tab */
                if (sp_check_track(track, inst)) { /* add to next sort tab */
                    st->sp_selected = g_list_append(st->sp_selected, track);
                    st_add_track(track, TRUE, TRUE, inst + 1);
                }
            }
        }
    }
}

/* Called when the user changed the sort conditions in the special
 * sort tab */
void sp_conditions_changed(guint32 inst) {
    SortTab *st;

    /* Sanity */
    if (inst >= prefs_get_int("sort_tab_num"))
        return;

    st = sorttab[inst];
    /* Sanity */
    if (!st || st->current_category != ST_CAT_SPECIAL)
        return;

    /* Only redisplay if data is actually being passed on to the next
     sort tab */
    if (st->is_go || prefs_get_int_index("sp_autodisplay", inst)) {
        st_redisplay(inst);
    }
}

/* ---------------------------------------------------------------- */
/*                                                                  */
/* Callbacks for special sort tab display                           */
/*                                                                  */
/* ---------------------------------------------------------------- */

void on_sp_or_button_toggled(GtkToggleButton *togglebutton, gpointer user_data) {
    guint32 inst = (guint32) (GPOINTER_TO_UINT(user_data) & SP_MASK);

    prefs_set_int_index("sp_or", inst, gtk_toggle_button_get_active(togglebutton));

    sp_conditions_changed(inst);
}

void on_sp_cond_button_toggled(GtkToggleButton *togglebutton, gpointer user_data) {
    guint32 inst = (guint32) (GPOINTER_TO_UINT(user_data) & SP_MASK);
    T_item cond = (guint32) GPOINTER_TO_UINT(user_data) >> SP_SHIFT;

    switch (cond) {
    case T_RATING:
        prefs_set_int_index("sp_rating_cond", inst, gtk_toggle_button_get_active(togglebutton));
        break;
    case T_PLAYCOUNT:
        prefs_set_int_index("sp_playcount_cond", inst, gtk_toggle_button_get_active(togglebutton));
        break;
    case T_TIME_PLAYED:
        prefs_set_int_index("sp_played_cond", inst, gtk_toggle_button_get_active(togglebutton));
        break;
    case T_TIME_MODIFIED:
        prefs_set_int_index("sp_modified_cond", inst, gtk_toggle_button_get_active(togglebutton));
        break;
    case T_TIME_ADDED:
        prefs_set_int_index("sp_added_cond", inst, gtk_toggle_button_get_active(togglebutton));
        break;
    default:
        break;
    }
    sp_conditions_changed(inst);
}

void on_sp_rating_n_toggled(GtkToggleButton *togglebutton, gpointer user_data) {
    guint32 inst = (guint32) (GPOINTER_TO_UINT(user_data) & SP_MASK);
    guint32 n = (guint32) GPOINTER_TO_UINT(user_data) >> SP_SHIFT;

    set_sp_rating_n(inst, n, gtk_toggle_button_get_active(togglebutton));
    if (prefs_get_int_index("sp_rating_cond", inst))
        sp_conditions_changed(inst);
}

void on_sp_entry_activate(GtkEditable *editable, gpointer user_data) {
    guint32 inst = (guint32) (GPOINTER_TO_UINT(user_data) & SP_MASK);
    T_item item = (guint32) GPOINTER_TO_UINT(user_data) >> SP_SHIFT;
    gchar *buf = gtk_editable_get_chars(editable, 0, -1);

    /*    printf ("sp_entry_activate inst: %d, item: %d\n", inst, item);*/

    switch (item) {
    case T_TIME_PLAYED:
        prefs_set_string_index("sp_played_state", inst, buf);
        break;
    case T_TIME_MODIFIED:
        prefs_set_string_index("sp_modified_state", inst, buf);
        break;
    case T_TIME_ADDED:
        prefs_set_string_index("sp_added_state", inst, buf);
        break;
    default:
        break;
    }

    g_free(buf);
    sp_update_date_interval_from_string(inst, item, TRUE);
    sp_go(inst);
}

void on_sp_cal_button_clicked(GtkButton *button, gpointer user_data) {
    guint32 inst = (guint32) GPOINTER_TO_UINT(user_data) & SP_MASK;
    T_item item = (guint32) GPOINTER_TO_UINT(user_data) >> SP_SHIFT;

    cal_open_calendar(inst, item);
}

void on_sp_go_clicked(GtkButton *button, gpointer user_data) {
    guint32 inst = (guint32) GPOINTER_TO_UINT(user_data) & SP_MASK;
    sp_go(inst);
}

void on_sp_go_always_toggled(GtkToggleButton *togglebutton, gpointer user_data) {
    guint32 inst = (guint32) GPOINTER_TO_UINT(user_data) & SP_MASK;
    gboolean state = gtk_toggle_button_get_active(togglebutton);

    /* display data if autodisplay is turned on */
    if (state)
        on_sp_go_clicked(NULL, user_data);
    prefs_set_int_index("sp_autodisplay", inst, state);
}

void on_sp_playcount_low_value_changed(GtkSpinButton *spinbutton, gpointer user_data) {
    guint32 inst = (guint32) GPOINTER_TO_UINT(user_data) & SP_MASK;

    prefs_set_int_index("sp_playcount_low", inst, gtk_spin_button_get_value(spinbutton));
    if (prefs_get_int_index("sp_playcount_cond", inst))
        sp_conditions_changed(inst);
}

void on_sp_playcount_high_value_changed(GtkSpinButton *spinbutton, gpointer user_data) {
    guint32 inst = (guint32) GPOINTER_TO_UINT(user_data) & SP_MASK;

    prefs_set_int_index("sp_playcount_high", inst, gtk_spin_button_get_value(spinbutton));
    if (prefs_get_int_index("sp_playcount_cond", inst))
        sp_conditions_changed(inst);
}

static GList *st_get_selected_tracks(gint inst) {
    GList *tracks = NULL;
    if (inst == -1) {
        Playlist *pl = gtkpod_get_current_playlist();
        if (pl)
            tracks = pl->members;
    }
    else {
        tracks = st_get_selected_members(inst);
    }
    return tracks;
}

/* ---------------------------------------------------------------- */
/*                                                                  */
/* Section for sort tab display (normal and general)                */
/*                                                                  */
/* ---------------------------------------------------------------- */
/* return a pointer to the list of members selected in the sort tab
 @inst. For a normal sort tab this is
 sorttab[inst]->current_entry->members, for a special sort tab this
 is sorttab[inst]->sp_selected.
 You must not g_list_free() the returned list */
GList *st_get_selected_members(guint32 inst) {
    SortTab *st;

    /* Sanity */
    if (inst >= prefs_get_int("sort_tab_num"))
        return NULL;

    st = sorttab[inst];

    /* Sanity */
    if (!st)
        return NULL;

    if (st->current_category != ST_CAT_SPECIAL) {
        if (st->current_entry)
            return st->current_entry->members;
        else
            return NULL;
    }
    else {
        return st->sp_selected;
    }
}

/* Get the instance of the sort tab that corresponds to
 "notebook". Returns -1 if sort tab could not be found
 and prints error message */
static gint st_get_instance_from_notebook(GtkNotebook *notebook) {
    gint i;

    for (i = 0; i < SORT_TAB_MAX; ++i) {
        if (sorttab[i] && (sorttab[i]->notebook == notebook))
            return i;
    }
    /*  g_warning ("Programming error (st_get_instance_from_notebook): notebook could
     not be found.\n"); function somehow can get called after notebooks got
     destroyed */
    return -1;
}

/* Get the instance of the sort tab that corresponds to
 "treeview". Returns -1 if sort tab could not be found
 and prints error message */
gint st_get_instance_from_treeview(GtkTreeView *tv) {
    gint i, cat;

    for (i = 0; i < SORT_TAB_MAX; ++i) {
        for (cat = 0; cat < ST_CAT_NUM; ++cat) {
            if (sorttab[i] && (sorttab[i]->treeview[cat] == tv))
                return i;
        }
    }
    return -1;
}

/* returns the selected entry */
TabEntry *st_get_selected_entry(gint inst) {
    TabEntry *result = NULL;

    if ((inst >= 0) && (inst < SORT_TAB_MAX) && sorttab[inst])
    /*	return sorttab[inst]->current_entry;*/
    /* we can't just return the "->current_entry" because the context
     menus require the selection before "->current_entry" is updated */
    {
        SortTab *st = sorttab[inst];
        GtkTreeView *tv = st->treeview[st->current_category];
        GtkTreeSelection *ts = gtk_tree_view_get_selection(tv);
        GtkTreeIter iter;

        if (gtk_tree_selection_get_selected(ts, NULL, &iter)) {
            gtk_tree_model_get(st->model, &iter, ST_COLUMN_ENTRY, &result, -1);
        }
    }
    /* wait until current_entry was updated */
    if (result != sorttab[inst]->current_entry)
        result = NULL;
    return result;
}

/* Append playlist to the playlist model. */
static void st_add_entry(TabEntry *entry, guint32 inst) {
    GtkTreeIter iter;
    GtkTreeModel *model;
    SortTab *st;

    st = sorttab[inst];
    model = st->model;
    g_return_if_fail (model != NULL);
    /* Insert the compilation entry between All and the first entry
     so it remains at the top even when the list is not sorted */
    if (entry->compilation) {
        gtk_list_store_insert(GTK_LIST_STORE (model), &iter, 1);
    }
    else {
        gtk_list_store_append(GTK_LIST_STORE (model), &iter);
    }
    gtk_list_store_set(GTK_LIST_STORE (model), &iter, ST_COLUMN_ENTRY, entry, -1);
    /* Prepend entry to the list, but always add after the master. */
    st->entries = g_list_insert(st->entries, entry, 1);

    if (!entry->master && !entry->compilation) {
        if (!st->entry_hash) {
            st->entry_hash = g_hash_table_new(g_str_hash, g_str_equal);
        }
        g_hash_table_insert(st->entry_hash, entry->name, entry);
    }
}

/* Used by st_remove_entry_from_model() to remove entry from model by calling
 gtk_tree_model_foreach () */
static gboolean st_delete_entry_from_model(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) {
    TabEntry *entry;

    gtk_tree_model_get(model, iter, ST_COLUMN_ENTRY, &entry, -1);
    if (entry == (TabEntry *) data) {
        gtk_list_store_remove(GTK_LIST_STORE (model), iter);
        return TRUE;
    }
    return FALSE;
}

/* Remove entry from the display model and the sorttab */
static void st_remove_entry_from_model(TabEntry *entry, guint32 inst) {
    SortTab *st = sorttab[inst];
    GtkTreeModel *model = st->model;
    if (model && entry) {
        /* 	printf ("entry: %p, cur_entry: %p\n", entry, st->current_entry); */
        if (entry == st->current_entry) {
            GtkTreeSelection *selection = gtk_tree_view_get_selection(st->treeview[st->current_category]);
            st->current_entry = NULL;
            /* We have to unselect the previous selection */
            gtk_tree_selection_unselect_all(selection);
        }
        gtk_tree_model_foreach(model, st_delete_entry_from_model, entry);
        st->entries = g_list_remove(st->entries, entry);
        g_list_free(entry->members);
        /* remove entry from hash */
        if (st->entry_hash) {
            TabEntry *hashed_entry = (TabEntry *) g_hash_table_lookup(st->entry_hash, entry->name);
            if (hashed_entry == entry)
                g_hash_table_remove(st->entry_hash, entry->name);
        }
        g_free(entry->name);
        g_free(entry->name_sortkey);
        g_free(entry->name_fuzzy_sortkey);
        g_free(entry);
    }
}

static void st_free_entry_cb(gpointer data, gpointer user_data) {
    TabEntry *entry = (TabEntry *) data;
    if (!entry) {
        return;
    }
    g_list_free(entry->members);
}

/* Remove all entries from the display model and the sorttab */
/* @clear_sort: reset sorted columns to the non-sorted state */
void st_remove_all_entries_from_model(guint32 inst) {
    SortTab *st = sorttab[inst];
    gint column;
    GtkSortType order;

    if (st) {
        if (st->current_entry) {
            GtkTreeSelection *selection = gtk_tree_view_get_selection(st->treeview[st->current_category]);
            st->current_entry = NULL;
            /* We may have to unselect the previous selection */
            gtk_tree_selection_unselect_all(selection);
        }
        if (st->model) {
            gtk_list_store_clear(GTK_LIST_STORE (st->model));
        }
        g_list_foreach(st->entries, st_free_entry_cb, NULL);
        g_list_free(st->entries);
        st->entries = NULL;
        if (st->entry_hash)
            g_hash_table_destroy(st->entry_hash);
        st->entry_hash = NULL;

        if ((prefs_get_int("st_sort") == SORT_NONE)
                && gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE (st->model), &column, &order)) { /* recreate track treeview to unset sorted column */
            if (column >= 0) {
                st_create_notebook(inst);
            }
        }
    }
}

/* Remove "entry" from the model (used by delete_entry_ok()). The
 * entry should be empty (otherwise it's not removed).
 * If "entry" is the master entry 'All', the sort tab is redisplayed
 * (it's empty).
 * If the entry is currently selected (usually will be), the next
 * or previous entry will be selected automatically (unless it's the
 * master entry and prefs_get_int_index("st_autoselect", inst) says don't select
 * the 'All' entry). If no new entry is selected, the next sort tab will be
 * redisplayed (should be empty) */
void st_remove_entry(TabEntry *entry, guint32 inst) {
    TabEntry *next = NULL;
    GtkTreeIter iter;
    GtkTreeSelection *selection;
    SortTab *st = sorttab[inst];

    if (!entry)
        return;
    /* is the entry empty (contains no tracks)? */
    if (g_list_length(entry->members) != 0)
        return;
    /* if the entry is the master entry 'All' -> the tab is empty,
     re-init tab */
    if (entry->master) {
        st_init(-1, inst);
        return;
    }

    /* is the entry currently selected? Remember! */
    selection = gtk_tree_view_get_selection(st->treeview[st->current_category]);
#if 0  /* it doesn't make much sense to select the next entry, or? */
    if (sorttab[inst]->current_entry == entry)
    {
        gboolean valid;
        TabEntry *entry2=NULL;
        GtkTreeIter iter2;
        /* what's the next entry (displayed)? */
        if (gtk_tree_selection_get_selected (selection, NULL, &iter))
        { /* found selected entry -> now chose next one */
            if (gtk_tree_model_iter_next (st->model, &iter))
            {
                gtk_tree_model_get(st->model, &iter, ST_COLUMN_ENTRY, &next, -1);
            }
            else
            { /* no next entry, try to find previous one */
                /* There doesn't seem to be a ..._iter_previous()
                 * call... */
                next = NULL;
                valid = gtk_tree_model_get_iter_first(st->model, &iter2);
                while(valid)
                {
                    gtk_tree_model_get(st->model, &iter2, ST_COLUMN_ENTRY, &entry2, -1);
                    if (entry == entry2) break; /* found it */
                    iter = iter2;
                    next = entry2;
                    valid = gtk_tree_model_iter_next(st->model, &iter2);
                }
                if (!valid) next = NULL;
            }
            /* don't select master entry 'All' until requested to do so */
            if (next && next->master && !ST_AUTOSELECT (inst))
            next = NULL;
        }
    }
#endif
    /* remove entry from display model */
    st_remove_entry_from_model(entry, inst);
    /* if we have a next entry, select it. */
    if (next) {
        gtk_tree_selection_select_iter(selection, &iter);
    }
}

/* Get the correct name for the entry according to currently
 selected category (page). Do _not_ g_free() the return value! */
static const gchar *st_get_entryname(Track *track, guint32 inst) {
    T_item t_item = ST_to_T(sorttab[inst]->current_category);

    return track_get_item(track, t_item);
}

/* Returns the entry "track" is stored in or NULL. The master entry
 "All" is skipped */
static TabEntry *st_get_entry_by_track(Track *track, guint32 inst) {
    GList *entries;
    TabEntry *entry;
    guint i;

    if (track == NULL)
        return NULL;
    entries = sorttab[inst]->entries;
    i = 1; /* skip master entry, which is supposed to be at first position */
    while ((entry = (TabEntry *) g_list_nth_data(entries, i)) != NULL) {
        if (g_list_find(entry->members, track) != NULL)
            break; /* found! */
        ++i;
    }
    return entry;
}

/* Find TabEntry with name "name". Return NULL if no entry was found.
 If "name" is {-1, 0x00}, it returns the master entry. Otherwise
 it skips the master entry. */
static TabEntry *st_get_entry_by_name(const gchar *name, guint32 inst) {
    TabEntry *entry = NULL;
    SortTab *st = sorttab[inst];
    GList *entries = st->entries;

    if (name == NULL)
        return NULL;
    /* check if we need to return the master entry */
    if ((strlen(name) == 1) && (*name == -1)) {
        entry = (TabEntry *) g_list_nth_data(entries, 0);
    }
    else {
        if (st->entry_hash)
            entry = g_hash_table_lookup(st->entry_hash, name);
    }
    return entry;
}

/* Find TabEntry with compilation set true. Return NULL if no entry was found. */
static TabEntry *st_get_compilation_entry(guint32 inst) {
    GList *entries;
    TabEntry *entry;
    guint i;

    entries = sorttab[inst]->entries;
    i = 1; /* skip master entry, which is supposed to be at first position */
    while ((entry = (TabEntry *) g_list_nth_data(entries, i)) != NULL) {
        if (entry->compilation)
            break; /* found! */
        ++i;
    }
    return entry;
}

/* moves a track from the entry it is currently in to the one it
 should be in according to its tags (if a Tag had been changed).
 Returns TRUE, if track has been moved, FALSE otherwise */
/* 07 Feb 2003: I decided that recategorizing is a bad thing: the
 current code only moves the tracks "up" in the entry list, so it's
 incomplete. More important: it leaves the display in an
 inconsistent state: the tracks are not removed from the
 trackview (this would confuse the user). But if he changes the entry
 again, nothing happens to the tracks displayed, because they are no
 longer members. Merging the two identical entries is no option
 either, because that takes away the possibility to easily "undo"
 what you have just done. It's also not intuitive if you have
 additional tracks appear on the screen. JCS */
static gboolean st_recategorize_track(Track *track, guint32 inst) {
#if 0
    TabEntry *oldentry, *newentry;
    gchar *entryname;

    oldentry = st_get_entry_by_track (track, inst);
    /*  printf("%d: recat_oldentry: %x\n", inst, oldentry);*/
    /* should not happen: track is not in sort tab */
    if (oldentry == NULL) return FALSE;
    entryname = st_get_entryname (track, inst);
    newentry = st_get_entry_by_name (entryname, inst);
    if (newentry == NULL)
    { /* not found, create new one */
        newentry = g_malloc0 (sizeof (TabEntry));
        newentry->name = g_strdup (entryname);
        newentry->master = FALSE;
        newentry->compilation = FALSE;
        st_add_entry (newentry, inst);
    }
    if (newentry != oldentry)
    { /* track category changed */
        /* add track to entry members list */
        newentry->members = g_list_append (newentry->members, track);
        /* remove track from old entry members list */
        oldentry->members = g_list_remove (oldentry->members, track);
        /*  printf("%d: recat_return_TRUE\n", inst);*/
        return TRUE;
    }
    /*  printf("%d: recat_return_FALSE\n", inst);*/
#endif
    return FALSE;
}

/* called by st_track_changed */
static void st_track_changed_normal(Track *track, gboolean removed, guint32 inst) {
    SortTab *st;
    TabEntry *master, *entry;

    st = sorttab[inst];
    master = g_list_nth_data(st->entries, 0);
    if (master == NULL)
        return; /* should not happen */
    /* if track is not in tab, don't proceed (should not happen) */
    if (g_list_find(master->members, track) == NULL)
        return;
    if (removed) {
        /* remove "track" from master entry "All" */
        master->members = g_list_remove(master->members, track);
        /* find entry which other entry contains the track... */
        entry = st_get_entry_by_track(track, inst);
        /* ...and remove it */
        if (entry)
            entry->members = g_list_remove(entry->members, track);
        if ((st->current_entry == entry) || (st->current_entry == master))
            st_track_changed(track, TRUE, inst + 1);
    }
    else {
        if (st->current_entry && g_list_find(st->current_entry->members, track) != NULL) { /* "track" is in currently selected entry */
            if (!st->current_entry->master) { /* it's not the master list */
                if (st_recategorize_track(track, inst))
                    st_track_changed(track, TRUE, inst + 1);
                else
                    st_track_changed(track, FALSE, inst + 1);
            }
            else { /* master entry ("All") is currently selected */
                st_recategorize_track(track, inst);
                st_track_changed(track, FALSE, inst + 1);
            }
        }
        else { /* "track" is not in an entry currently selected */
            if (st_recategorize_track(track, inst)) { /* track was moved to a different entry */
                if (st_get_entry_by_track(track, inst) == st->current_entry) { /* this entry is selected! */
                    st_add_track(track, TRUE, TRUE, inst + 1);
                }
            }
        }
    }
}

/* Some tags of a track currently stored in a sort tab have been changed.
 - if not "removed"
 - if the track is in the entry currently selected:
 - remove entry and put into correct category
 - if current entry != "All":
 - if sort category changed:
 - notify next sort tab ("removed")
 - if sort category did not change:
 - notify next sort tab ("not removed")
 - if current entry == "All":
 - notify next sort tab ("not removed")
 - if the track is not in the entry currently selected (I don't know
 how that could happen, though):
 - if sort category changed: remove entry and put into correct category
 - if this "correct" category is selected, call st_add_track for next
 instance.
 - if "removed"
 - remove the track from the sort tab
 - if track was in the entry currently selected, notify next instance
 ("removed")
 "removed": track has been removed from sort tab. This is different
 from st_remove_track, because we will not notify the track model if a
 track has been removed: it might confuse the user if the track, whose
 tabs he/she just edited, disappeared from the display */
void st_track_changed(Track *track, gboolean removed, guint32 inst) {
    if (inst == prefs_get_int("sort_tab_num")) {
        return;
    }
    else if (inst < prefs_get_int("sort_tab_num")) {
        switch (sorttab[inst]->current_category) {
        case ST_CAT_ARTIST:
        case ST_CAT_ALBUM:
        case ST_CAT_GENRE:
        case ST_CAT_COMPOSER:
        case ST_CAT_TITLE:
        case ST_CAT_YEAR:
            st_track_changed_normal(track, removed, inst);
            break;
        case ST_CAT_SPECIAL:
            st_track_changed_special(track, removed, inst);
            break;
        default:
            g_return_if_reached();
        }
    }
}

/* Reorders the tracks stored in the sort tabs according to the order
 * in the selected playlist. This has to be done e.g. if we change the
 * order in the track view.
 *
 * Right now I simply delete all members of all tab entries, then add
 * the tracks again without having them added to the track view. This
 * is very fast because neither the sort tab display nor the track
 * view display is affected in any way. For my 2459 tracks that takes
 * approx. 1.3 seconds (850 MHz AMD Duron) */
void st_adopt_order_in_playlist(void) {
    gint inst;
    Playlist *current_playlist = gtkpod_get_current_playlist();

#if DEBUG_TIMING
    GTimeVal time;
    g_get_current_time (&time);
    printf ("st_adopt_order_in_playlist enter: %ld.%06ld sec\n",
            time.tv_sec % 3600, time.tv_usec);
#endif

    /* first delete all tracks in all visible sort tabs */
    for (inst = 0; inst < prefs_get_int("sort_tab_num"); ++inst) {
        SortTab *st = sorttab[inst];
        GList *link;
        for (link = st->entries; link; link = link->next) { /* in each entry delete all tracks */
            TabEntry *entry = (TabEntry *) link->data;
            g_list_free(entry->members);
            entry->members = NULL;
        }
    }

    /* now add the tracks again, without adding them to the track view */
    if (current_playlist) {
        GList *link;

        for (link = current_playlist->members; link; link = link->next) {
            st_add_track((Track *) link->data, FALSE, FALSE, 0);
        }
    }
#if DEBUG_TIMING
    g_get_current_time (&time);
    printf ("st_adopt_order_in_playlist enter: %ld.%06ld sec\n",
            time.tv_sec % 3600, time.tv_usec);
#endif
}

/* called by st_add_track() */
static void st_add_track_normal(Track *track, gboolean final, gboolean display, guint32 inst) {
    SortTab *st;
    TabEntry *entry, *master_entry, *iter_entry;
    const gchar *entryname = NULL;
    GtkTreeSelection *selection;
    GtkTreeIter iter;
    TabEntry *select_entry = NULL;
    gboolean first = FALSE;
    gboolean group_track = FALSE;

    st = sorttab[inst];
    st->final = final;

    /*       if (track)   printf ("%d: add track: %s\n", inst, track->title); */
    /*       else        printf ("%d: add track: %p\n", inst, track); */

    if (track != NULL) {
        /* add track to "All" (master) entry */
        master_entry = g_list_nth_data(st->entries, 0);
        if (master_entry == NULL) { /* doesn't exist yet -- let's create it */
            master_entry = g_malloc0(sizeof(TabEntry));
            master_entry->name = g_strdup(_("All"));
            st_build_sortkeys(master_entry);
            master_entry->master = TRUE;
            master_entry->compilation = FALSE;
            st_add_entry(master_entry, inst);
            first = TRUE; /* this is the first track */
        }
        master_entry->members = g_list_prepend(master_entry->members, track);
        /* Check if this track should go in the compilation artist group */
        group_track = (prefs_get_int("group_compilations") && (track->compilation == TRUE) && (st->current_category
                == ST_CAT_ARTIST));
        /* Check whether entry of same name already exists */
        if (group_track) {
            entry = st_get_compilation_entry(inst);
        }
        else {
            entryname = st_get_entryname(track, inst);
            entry = st_get_entry_by_name(entryname, inst);
        }
        if (entry == NULL) { /* not found, create new one */
            entry = g_malloc0(sizeof(TabEntry));
            if (group_track) {
                entry->name = g_strdup(_("Compilations"));
            }
            else {
                entry->name = g_strdup(entryname);
            }
            st_build_sortkeys(entry);
            entry->compilation = group_track;
            entry->master = FALSE;
            st_add_entry(entry, inst);
        }
        /* add track to entry members list */
        entry->members = g_list_prepend(entry->members, track);
        /* add track to next tab if "entry" is selected */
        if (st->current_entry && ((st->current_entry->master) || (entry == st->current_entry))) {
            st_add_track(track, final, display, inst + 1);
        }
        /* check if we should select some entry */
        if (!st->current_entry) {
            if (st->lastselection[st->current_category] == NULL) {
                /* no last selection -- check if we should select "All" */
                /* only select "All" when currently adding the first track */
                if (first && ST_AUTOSELECT (inst)) {
                    select_entry = master_entry;
                }
            }
            else {
                /* select current entry if it corresponds to the last
                 selection, or last_entry if that's the master entry */
                TabEntry *last_entry = st_get_entry_by_name(st->lastselection[st->current_category], inst);
                if (last_entry && ((entry == last_entry) || last_entry->master)) {
                    select_entry = last_entry;
                }
            }
        }
    }
    /* select "All" if it's the last track added, no entry currently
     selected (including "select_entry", which is to be selected" and
     prefs_get_int_index("st_autoselect", index) allows us to select "All" */
    if (final && !st->current_entry && !select_entry && !st->unselected && ST_AUTOSELECT (inst)) { /* auto-select entry "All" */
        select_entry = g_list_nth_data(st->entries, 0);
    }

    if (select_entry) { /* select current select_entry */
        /* 	  printf("%d: selecting: %p: %s\n", inst, select_entry, select_entry->name); */
        if (!gtk_tree_model_get_iter_first(st->model, &iter)) {
            g_warning ("Programming error: st_add_track: iter invalid\n");
            return;
        }
        do {
            gtk_tree_model_get(st->model, &iter, ST_COLUMN_ENTRY, &iter_entry, -1);
            if (iter_entry == select_entry) {
                selection = gtk_tree_view_get_selection(st->treeview[st->current_category]);
                /* We may need to unselect the previous selection */
                /* gtk_tree_selection_unselect_all (selection); */
                st->current_entry = select_entry;
                gtk_tree_selection_select_iter(selection, &iter);
                break;
            }
        }
        while (gtk_tree_model_iter_next(st->model, &iter));
    }
    else if (!track && final) {
        st_add_track(NULL, final, display, inst + 1);
    }
}

/* Add track to sort tab. If the track matches the currently
 selected sort criteria, it will be passed on to the next
 sort tab. The last sort tab will pass the track on to the
 track model (currently two sort tabs).
 When the first track is added, the "All" entry is created.
 If prefs_get_int_index("st_autoselect", inst) is true, the "All" entry is
 automatically selected, if there was no former selection
 @display: TRUE: add to track model (i.e. display it) */
void st_add_track(Track *track, gboolean final, gboolean display, guint32 inst) {
#if DEBUG_ADD_TRACK
    printf ("st_add_track: inst: %d, final: %d, display: %d, track: %p\n",
            inst, final, display, track);
#endif

    if (inst == prefs_get_int("sort_tab_num")) { /* just add to track model */
        if (final)
            gtkpod_tracks_statusbar_update();
    }
    else if (inst < prefs_get_int("sort_tab_num")) {
        switch (sorttab[inst]->current_category) {
        case ST_CAT_ARTIST:
        case ST_CAT_ALBUM:
        case ST_CAT_GENRE:
        case ST_CAT_COMPOSER:
        case ST_CAT_TITLE:
        case ST_CAT_YEAR:
            st_add_track_normal(track, final, display, inst);
            break;
        case ST_CAT_SPECIAL:
            st_add_track_special(track, final, display, inst);
            break;
        default:
            g_return_if_reached();
        }
    }
}

/* called by st_remove_track() */
static void st_remove_track_normal(Track *track, guint32 inst) {
    TabEntry *master, *entry;
    SortTab *st = sorttab[inst];

    master = g_list_nth_data(st->entries, 0);
    if (master == NULL)
        return; /* should not happen! */
    /* remove "track" from master entry "All" */
    master->members = g_list_remove(master->members, track);
    /* find entry which other entry contains the track... */
    entry = st_get_entry_by_track(track, inst);
    /* ...and remove it */
    if (entry)
        entry->members = g_list_remove(entry->members, track);
    st_remove_track(track, inst + 1);
}

/* Remove track from sort tab. If the track matches the currently
 selected sort criteria, it will be passed on to the next
 sort tab (i.e. removed).
 The last sort tab will remove the
 track from the track model (currently two sort tabs). */
/* 02. Feb 2003: bugfix: track is always passed on to the next sort
 * tab: it might have been recategorized, but still be displayed. JCS */
void st_remove_track(Track *track, guint32 inst) {
    if (inst < prefs_get_int("sort_tab_num")) {
        switch (sorttab[inst]->current_category) {
        case ST_CAT_ARTIST:
        case ST_CAT_ALBUM:
        case ST_CAT_GENRE:
        case ST_CAT_COMPOSER:
        case ST_CAT_TITLE:
        case ST_CAT_YEAR:
            st_remove_track_normal(track, inst);
            break;
        case ST_CAT_SPECIAL:
            st_remove_track_special(track, inst);
            break;
        default:
            g_return_if_reached();
        }
    }
}

/* Init a sort tab: all current entries are removed. The next sort tab
 is initialized as well (st_init (-1, inst+1)).  Set new_category to
 -1 if the current category is to be left unchanged */
/* Normally we do not specifically remember the "All" entry and will
 select "All" in accordance to the prefs settings. */
void st_init(ST_CAT_item new_category, guint32 inst) {
    if (inst == prefs_get_int("sort_tab_num")) {
        gtkpod_tracks_statusbar_update();
        return;
    }
    if (inst < prefs_get_int("sort_tab_num")) {
        SortTab *st = sorttab[inst];

        if (st == NULL)
            return; /* could happen during initialisation */

        sp_store_sp_entries(inst); /* store sp entries (if applicable) */
        st->unselected = FALSE; /* nothing was unselected so far */
        st->final = TRUE; /* all tracks are added */
        st->is_go = FALSE; /* did not press "Display" yet (special) */
#if 0
        if (st->current_entry != NULL)
        {
            ST_CAT_item cat = st->current_category;
            if (!st->current_entry->master)
            {
                C_FREE (st->lastselection[cat]);
                st->lastselection[cat] = g_strdup (st->current_entry->name);
            }
            /* don't remember entry 'All' */
#if 0
            else
            {
                gchar buf[] = {-1, 0}; /* this is how I mark the "All"
                 * entry as string: should be
                 * illegal UTF8 */
                C_FREE (st->lastselection[cat]);
                st->lastselection[cat] = g_strdup (buf);*/
            }
#endif
        }
#endif
        switch (sorttab[inst]->current_category) {
        case ST_CAT_ARTIST:
        case ST_CAT_ALBUM:
        case ST_CAT_GENRE:
        case ST_CAT_COMPOSER:
        case ST_CAT_TITLE:
        case ST_CAT_YEAR:
            st_remove_all_entries_from_model(inst);
            break;
        case ST_CAT_SPECIAL:
            sp_remove_all_members(inst);
            break;
        default:
            g_return_if_reached();
        }
        if (new_category != -1) {
            st->current_category = new_category;
            prefs_set_int_index("st_category", inst, new_category);
        }
        st_init(-1, inst + 1);
    }
}

static gboolean st_page_selected_cb(gpointer data) {
    GtkNotebook *notebook = GTK_NOTEBOOK (data);
    guint page;
    guint32 inst;
    guint oldpage;
    gboolean is_go;
    GList *copy = NULL;
    SortTab *st;

#if DEBUG_TIMING
    GTimeVal time;
    g_get_current_time (&time);
    printf ("st_page_selected_cb enter (inst: %d, page: %d): %ld.%06ld sec\n",
            inst, page,
            time.tv_sec % 3600, time.tv_usec);
#endif

    inst = st_get_instance_from_notebook(notebook);
    if (inst == -1)
        return FALSE; /* invalid notebook */

    page = gtk_notebook_get_current_page(notebook);
    st = sorttab[inst];
    /* remember old is_go state and current page */
    is_go = st->is_go;
    oldpage = st->current_category;
    /* re-initialize current instance */
    st_init(page, inst);
    /* write back old is_go state if page hasn't changed (redisplay) */
    if (page == oldpage)
        st->is_go = is_go;
    /* Get list of tracks to re-insert */
    copy = st_get_selected_tracks(inst - 1);
    if (copy) {
        GList *gl;
        gboolean final;
        /* add all tracks previously present to sort tab */
        st_enable_disable_view_sort(inst, FALSE);
        for (gl = copy; gl; gl = gl->next) {
            Track *track = gl->data;
            st_add_track(track, FALSE, TRUE, inst);
        }
        st_enable_disable_view_sort(inst, TRUE);
        final = TRUE; /* playlist is always complete */
        /* if playlist is not source, get final flag from
         * corresponding sorttab */
        if ((inst > 0) && (sorttab[inst - 1]))
            final = sorttab[inst - 1]->final;
        st_add_track(NULL, final, TRUE, inst);
    }
#if DEBUG_TIMING
    g_get_current_time (&time);
    printf ("st_page_selected_cb exit (inst: %d, page: %d):  %ld.%06ld sec\n",
            inst, page,
            time.tv_sec % 3600, time.tv_usec);
#endif

    return FALSE;
}

/* Called when page in sort tab is selected */
static void st_page_selected(GtkNotebook *notebook, guint page) {
    guint32 inst;

    inst = st_get_instance_from_notebook(notebook);
#if DEBUG_CB_INIT
    printf ("st_page_selected: inst: %d, page: %d\n", inst, page);
#endif
    if (inst == -1)
        return; /* invalid notebook */
    /* inst-1: changing a page in the first sort tab is like selecting a
     new playlist and so on. Therefore we subtract 1 from the
     instance. */

    g_idle_add(st_page_selected_cb, notebook);
}

/* Redisplay the sort tab "inst" */
void st_redisplay(guint32 inst) {
    if (inst < prefs_get_int("sort_tab_num")) {
        if (sorttab[inst])
            st_page_selected(sorttab[inst]->notebook, sorttab[inst]->current_category);
    }
}

/* Start sorting */
static void st_sort_inst(guint32 inst, GtkSortType order) {
    if (inst < prefs_get_int("sort_tab_num")) {
        SortTab *st = sorttab[inst];
        if (st) {
            switch (st->current_category) {
            case ST_CAT_ARTIST:
            case ST_CAT_ALBUM:
            case ST_CAT_GENRE:
            case ST_CAT_COMPOSER:
            case ST_CAT_TITLE:
            case ST_CAT_YEAR:
                if (order != SORT_NONE)
                    gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE (st->model), ST_COLUMN_ENTRY, order);
                else if (inst == 0) { /* we only redisplay for st0 because the others
                 are reinitialized automatically */
                    /* and we only redisplay if the tree is actually
                     sorted */
                    gint column;
                    GtkSortType order;
                    if (gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE (st->model), &column, &order))
                        st_redisplay(0);
                }
                break;
            case ST_CAT_SPECIAL:
                break;
            default:
                g_return_if_reached();
            }
        }
    }
}

void st_sort(gint order) {
    gint i;
    for (i = 0; i < prefs_get_int("sort_tab_num"); ++i)
        st_sort_inst(i, order);

    prefs_set_int("st_sort", order);
    gtkpod_broadcast_preference_change("st_sort", order);
}

gint st_get_sorttab_page_number(int inst) {
    if (sorttab[inst])
        return gtk_notebook_get_current_page(sorttab[inst]->notebook);
    else
        return -1;
}

void st_set_sorttab_page(gint inst, gint category) {
    if (sorttab[inst]) {
        int current = gtk_notebook_get_current_page(sorttab[inst]->notebook);
        if (current == category)
            return; // nothing to do

        gtk_notebook_set_current_page(sorttab[inst]->notebook, category);
        st_page_selected(sorttab[inst]->notebook, category);
    }
}

gboolean st_set_selection(Itdb_Track *track) {
    GtkTreeSelection *selection;
    GtkTreeView *treeview;
    GtkTreeModel *model;
    GtkTreeIter iter;
    TabEntry *entry = NULL;
    gboolean status;

    gtk_notebook_set_current_page(sorttab[0]->notebook, ST_CAT_ARTIST);
    st_page_selected(sorttab[0]->notebook, ST_CAT_ARTIST);

    /* ######## Select the artist from the first sorttab ######## */
    treeview = sorttab[0]->treeview[ST_CAT_ARTIST];
    model = gtk_tree_view_get_model(treeview);
    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));

    status = gtk_tree_model_get_iter_first(model, &iter);
    g_return_val_if_fail (status, FALSE);

    do {
        gtk_tree_model_get(model, &iter, ST_COLUMN_ENTRY, &entry, -1);
        g_return_val_if_fail (entry, FALSE);

        if (g_ascii_strcasecmp(entry->name, track->artist) == 0) {
            /* break out the loop once the correct iter has been found */
            break;
        }
    }
    while (gtk_tree_model_iter_next(model, &iter));

    gtk_tree_selection_select_iter(selection, &iter);

    /* ######## Select the album from the second sorttab ######## */
    gtk_notebook_set_current_page(sorttab[1]->notebook, ST_CAT_ALBUM);
    st_page_selected(sorttab[1]->notebook, ST_CAT_ALBUM);

    treeview = sorttab[1]->treeview[ST_CAT_ALBUM];
    model = gtk_tree_view_get_model(treeview);
    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));

    status = gtk_tree_model_get_iter_first(model, &iter);
    g_return_val_if_fail (status, FALSE);

    do {
        gtk_tree_model_get(model, &iter, ST_COLUMN_ENTRY, &entry, -1);
        g_return_val_if_fail (entry, FALSE);

        if (g_ascii_strcasecmp(entry->name, track->album) == 0) {
            /* break out the loop once the correct iter has been found */
            break;
        }
    }
    while (gtk_tree_model_iter_next(model, &iter));

    gtk_tree_selection_select_iter(selection, &iter);

    return TRUE;
}

static gboolean st_selection_changed_cb(gpointer data) {
    StSelectionEvent *event = (StSelectionEvent *) data;
    GtkTreeView *tree_view = event->tree_view;
    GtkTreeSelection *selection = gtk_tree_view_get_selection(tree_view);
    guint32 inst = event->inst;
    GtkTreeModel *model;
    GtkTreeIter iter;
    TabEntry *new_entry;
    SortTab *st;

#if DEBUG_TIMING || DEBUG_CB_INIT
    GTimeVal time;
    g_get_current_time (&time);
    printf ("st_selection_changed_cb enter (inst: %d): %ld.%06ld sec\n",
            inst, time.tv_sec % 3600, time.tv_usec);
#endif

    /* printf("st_s_c_cb %d: entered\n", inst); */
    st = sorttab[inst];
    if (st == NULL)
        return FALSE;
    if (gtk_tree_selection_get_selected(selection, &model, &iter) == FALSE) {
        /* no selection -- unselect current selection (unless
         st->current_entry == NULL -- that means we're in the middle
         of a  st_init() (removing all entries). In that case we don't
         want to forget our last selection! */
        if (st->current_entry) {
            st->current_entry = NULL;
            C_FREE (st->lastselection[st->current_category]);
            st->unselected = TRUE;
        }
        st_init(-1, inst + 1);
    }
    else { /* handle new selection */
        gtk_tree_model_get(model, &iter, ST_COLUMN_ENTRY, &new_entry, -1);
        /* printf("selected instance %d, entry %x (was: %x)\n", inst,
         *new_entry, st->current_entry); */

        /* initialize next instance */
        st_init(-1, inst + 1);
        /* remember new selection */
        st->current_entry = new_entry;
        if (!new_entry->master) {
            C_FREE (st->lastselection[st->current_category]);
            st->lastselection[st->current_category] = g_strdup(new_entry->name);
        }
        st->unselected = FALSE;

        if (new_entry->members) {
            GList *gl;
            st_enable_disable_view_sort(inst + 1, FALSE);

            for (gl = new_entry->members; gl; gl = gl->next) { /* add all member tracks to next instance */
                Track *track = gl->data;
                st_add_track(track, FALSE, TRUE, inst + 1);
            }
            st_enable_disable_view_sort(inst + 1, TRUE);
            st_add_track(NULL, TRUE, st->final, inst + 1);
            /* Advertise that a new set of tracks has been selected */
            if ((inst + 1) == prefs_get_int("sort_tab_num")) {
                gtkpod_set_displayed_tracks(new_entry->members);
            }
        }
        gtkpod_tracks_statusbar_update();
    }

#if DEBUG_TIMING
    g_get_current_time (&time);
    printf ("st_selection_changed_cb exit:  %ld.%06ld sec\n",
            time.tv_sec % 3600, time.tv_usec);
#endif

    return FALSE;
}

/* Callback function called when the selection
 of the sort tab view has changed */
/* Instead of handling the selection directly, we add a
 "callback". Currently running display updates will be stopped
 before the st_selection_changed_cb is actually called */
static void st_selection_changed(GtkTreeSelection *selection, gpointer user_data) {
#if DEBUG_CB_INIT
    printf("st_s_c enter (inst: %d)\n", (gint)user_data);
#endif
    StSelectionEvent *event = g_malloc0(sizeof(StSelectionEvent));
    event->tree_view = gtk_tree_selection_get_tree_view(selection);
    event->inst = (guint32) GPOINTER_TO_UINT(user_data);
    g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, st_selection_changed_cb, event, g_free);
#if DEBUG_CB_INIT
    printf("st_s_c exit (inst: %d)\n", (gint)user_data);
#endif
}

/* Called when editable cell is being edited. Stores new data to
 the entry list and changes all members. */
static void st_cell_edited(GtkCellRendererText *renderer, const gchar *path_string, const gchar *new_text, gpointer data) {
    GtkTreeModel *model;
    GtkTreePath *path;
    GtkTreeIter iter;
    TabEntry *entry;
    ST_item column;
    gint i, n, inst;
    GList *members;
    SortTab *st;

    inst = (guint32) GPOINTER_TO_UINT(data);
    st = sorttab[inst];
    model = st->model;
    path = gtk_tree_path_new_from_string(path_string);
    column = (ST_item) g_object_get_data(G_OBJECT (renderer), "column");
    gtk_tree_model_get_iter(model, &iter, path);
    gtk_tree_model_get(model, &iter, column, &entry, -1);

    /*printf("Inst %d: st_cell_edited: column: %d  :%lx\n", inst, column, entry);*/

    switch (column) {
    case ST_COLUMN_ENTRY:
        /* We only do something, if the name actually got changed */
        if (g_utf8_collate(entry->name, new_text) != 0) {
            iTunesDB *itdb = NULL;
            /* remove old hash entry if available */
            TabEntry *hash_entry = g_hash_table_lookup(st->entry_hash, entry->name);
            if (hash_entry == entry)
                g_hash_table_remove(st->entry_hash, entry->name);
            /* replace entry name */
            g_free(entry->name);
            if (sorttab[inst]->current_category == ST_CAT_YEAR) { /* make sure the entry->name is identical to
             atoi(new_text) */
                entry->name = g_strdup_printf("%d", atoi(new_text));
                g_object_set(G_OBJECT (renderer), "text", entry->name, NULL);
            }
            else {
                entry->name = g_strdup(new_text);
            }
            st_build_sortkeys(entry);

            /* re-insert into hash table if the same name doesn't
             already exist */
            if (g_hash_table_lookup(st->entry_hash, entry->name) == NULL)
                g_hash_table_insert(st->entry_hash, entry->name, entry);
            /* Now we look up all the tracks and change the ID3 Tag as well */
            /* We make a copy of the current members list, as it may change
             during the process */
            members = g_list_copy(entry->members);
            n = g_list_length(members);
            /* block user input if we write tags (might take a while) */
            if (prefs_get_int("id3_write"))
                block_widgets();
            for (i = 0; i < n; ++i) {
                ExtraTrackData *etr;
                Track *track = (Track *) g_list_nth_data(members, i);
                T_item t_item;

                g_return_if_fail (track);
                etr = track->userdata;
                g_return_if_fail (etr);
                g_return_if_fail (track->itdb);
                if (!itdb)
                    itdb = track->itdb;

                t_item = ST_to_T(sorttab[inst]->current_category);

                if (t_item == T_YEAR) {
                    gint nr = atoi(new_text);
                    if (nr < 0)
                        nr = 0;
                    track->year = nr;
                    g_free(etr->year_str);
                    etr->year_str = g_strdup_printf("%d", nr);
                }
                else {
                    gchar **itemp_utf8 = track_get_item_pointer(track, t_item);
                    g_return_if_fail (itemp_utf8);
                    g_free(*itemp_utf8);
                    *itemp_utf8 = g_strdup(new_text);
                }
                track->time_modified = time(NULL);
                gtkpod_track_updated(track);
                /* If prefs say to write changes to file, do so */
                if (prefs_get_int("id3_write")) {
                    /* T_item tag_id;*/
                    /* should we update all ID3 tags or just the one
                     changed? -- obsoleted in 0.71 */
                    /*		  if (prefs_get_id3_writeall ()) tag_id = T_ALL;
                     else		                 tag_id = t_item;*/
                    write_tags_to_file(track);
                    while (widgets_blocked && gtk_events_pending())
                        gtk_main_iteration();
                }
            }
            g_list_free(members);
            /* allow user input again */
            if (prefs_get_int("id3_write"))
                release_widgets();
            /* display possible duplicates that have been removed */
            gp_duplicate_remove(NULL, NULL);
            /* indicate that data has changed */
            if (itdb)
                data_changed(itdb);
        }
        break;
    default:
        break;
    }
    gtk_tree_path_free(path);
}

/* The sort tab entries are stored in a separate list (sorttab->entries)
 and only pointers to the corresponding TabEntry structure are placed
 into the model.
 This function reads the data for the given cell from the list and
 passes it to the renderer. */
static void st_cell_data_func(GtkTreeViewColumn *tree_column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) {
    TabEntry *entry;
    gint column;

    column = (gint) GPOINTER_TO_INT(g_object_get_data (G_OBJECT (renderer), "column"));
    gtk_tree_model_get(model, iter, ST_COLUMN_ENTRY, &entry, -1);

    switch (column) { /* We only have one column, so this code is overkill... */
    case ST_COLUMN_ENTRY:
        if (entry->master || entry->compilation) { /* mark the "All" entry */
            g_object_set(G_OBJECT (renderer), "text", entry->name, "editable", FALSE, "weight", PANGO_WEIGHT_BOLD, NULL);
        }
        else {
            g_object_set(G_OBJECT (renderer), "text", entry->name, "editable", TRUE, "weight", PANGO_WEIGHT_NORMAL, NULL);
        }
        break;
    }
}

/* Function used to compare rows with user's search string */
gboolean st_search_equal_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data) {
    TabEntry *entry1;
    gboolean cmp;
    gtk_tree_model_get(model, iter, ST_COLUMN_ENTRY, &entry1, -1);

    cmp = (compare_string_start_case_insensitive(entry1->name, key) != 0);
    return cmp;
}
;

/* Function used to compare two cells during sorting (sorttab view) */
gint st_data_compare_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) {
    TabEntry *entry1;
    TabEntry *entry2;
    GtkSortType order;
    gint corr, colid;
    gint inst;
    SortTab *st;

    inst = (guint32) GPOINTER_TO_UINT(user_data);

    gtk_tree_model_get(model, a, ST_COLUMN_ENTRY, &entry1, -1);
    gtk_tree_model_get(model, b, ST_COLUMN_ENTRY, &entry2, -1);
    if (gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE (model), &colid, &order) == FALSE)
        return 0;

    /* We make sure that the "all" entry always stays on top, closely followed
     by the compilation entry */
    if (order == GTK_SORT_ASCENDING)
        corr = +1;
    else
        corr = -1;
    if (entry1->master)
        return (-corr);
    if (entry2->master)
        return (corr);
    if (entry1->compilation)
        return (-corr);
    if (entry2->compilation)
        return (corr);

    /* compare the two entries */
    /* string_compare_func is set to either compare_string_fuzzy or
     compare_string in on_st_switch_page() which is called
     once before the comparing begins. */

    st = sorttab[inst];

    return st->entry_compare_func(entry1, entry2);
}

static void cell_renderer_stop_editing(GtkCellRenderer *renderer, gpointer user_data) {
    gtk_cell_renderer_stop_editing (renderer, (gboolean) GPOINTER_TO_INT(user_data));
}

/* Stop editing. If @cancel is TRUE, the edited value will be
 discarded (I have the feeling that the "discarding" part does not
 work quite the way intended). */
void st_stop_editing(gint inst, gboolean cancel) {
    if (inst < prefs_get_int("sort_tab_num")) {
        SortTab *st = sorttab[inst];
        GList *cells;

        if (st) {
            GtkTreeViewColumn *col;
            gtk_tree_view_get_cursor(st->treeview[st->current_category], NULL, &col);

            if (col) {
                cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT (col));
                g_list_foreach(cells, (GFunc) cell_renderer_stop_editing, GINT_TO_POINTER((gint) cancel));
                g_list_free(cells);
            }
        }
    }
}

/* Compare function to avoid sorting */
static gint st_nosort_comp(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) {
    return 0;
}

/* Disable sorting of the view during lengthy updates. */
/* @enable: TRUE: enable, FALSE: disable */
void st_enable_disable_view_sort(gint inst, gboolean enable) {
    static gint disable_count[SORT_TAB_MAX];

    if (inst >= prefs_get_int("sort_tab_num")) {
        gtkpod_set_sort_enablement(enable);
        return;
    }

    if (enable) {
        disable_count[inst]--;
        if (disable_count[inst] < 0)
            fprintf(stderr, "Programming error: disable_count < 0\n");
        if (disable_count[inst] == 0) {
            /* Re-enable sorting */
            if (prefs_get_int("st_sort") != SORT_NONE) {
                SortTab *st = sorttab[inst];
                if (st && (st->current_category != ST_CAT_SPECIAL) && st->model) {
                    if (BROKEN_GTK_TREE_SORT) {
                        gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE (st->model), ST_COLUMN_ENTRY, st_data_compare_func, GINT_TO_POINTER(inst), NULL);
                    }
                    else {
                        gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE (st->model), ST_COLUMN_ENTRY, prefs_get_int("st_sort"));
                    }
                }
            }
            st_enable_disable_view_sort(inst + 1, enable);
        }
    }
    else {
        if (disable_count[inst] == 0) {
            /* Disable sorting */
            if (prefs_get_int("st_sort") != SORT_NONE) {
                SortTab *st = sorttab[inst];
                if (st && (st->current_category != ST_CAT_SPECIAL) && st->model) {
                    if (BROKEN_GTK_TREE_SORT) {
                        gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE (st->model), ST_COLUMN_ENTRY, st_nosort_comp, NULL, NULL);
                    }
                    else {
                        gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE (st->model), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, prefs_get_int("st_sort"));
                    }
                }
            }
            st_enable_disable_view_sort(inst + 1, enable);
        }
        disable_count[inst]++;
    }
}

void st_select_current_position(gint inst, gint x, gint y) {
    if (inst < prefs_get_int("sort_tab_num")) {
        SortTab *st = sorttab[inst];
        if (st) {
            GtkTreePath *path;
            GtkTreeView *tv = st->treeview[st->current_category];

            gtk_tree_view_get_path_at_pos(tv, x, y, &path, NULL, NULL, NULL);
            if (path) {
                GtkTreeSelection *ts = gtk_tree_view_get_selection(tv);
                gtk_tree_selection_select_path(ts, path);
                gtk_tree_path_free(path);
            }
        }
    }
}

/* Make the appropriate number of sort tab instances visible */
/* Also: make the menu items "more/less sort tabs" active/inactive as
 * needed. */
static void st_adjust_visible(void) {
    gint i, n;
    //    GtkWidget *w;

    if (!st_paned[0])
        return;

    /* first initialize (clear) all sorttabs */
    n = prefs_get_int("sort_tab_num");
    prefs_set_int("sort_tab_num", SORT_TAB_MAX);
    st_init(-1, 0);
    prefs_set_int("sort_tab_num", n);

    /* set the visible elements */
    for (i = 0; i < n; ++i) {
        gtk_widget_show(GTK_WIDGET (sorttab[i]->notebook));
        if (i < PANED_NUM_ST)
            gtk_widget_show(GTK_WIDGET (st_paned[i]));
    }
    /* set the invisible elements */
    for (i = n; i < SORT_TAB_MAX; ++i) {
        gtk_widget_hide(GTK_WIDGET (sorttab[i]->notebook));
        if (i < PANED_NUM_ST)
            gtk_widget_hide(GTK_WIDGET (st_paned[i]));
    }
}

/* Make the appropriate number of sort tab instances visible */
/* Also: make the menu items "more/less sort tabs" active/inactive as
 * needed. */
void st_show_visible(void) {
    /* Adjust visibility */
    st_adjust_visible();

    /* redisplay */
    st_redisplay(0);
}

/* Created paned elements for sorttabs */
static void st_create_paned() {
    gint i;

    /* sanity check */
    g_return_if_fail (st_paned[0] == NULL);

    for (i = 0; i < PANED_NUM_ST; ++i) {
        GtkWidget *paned;

        paned = gtk_hpaned_new();
        gtk_widget_show(paned);

        if (!i) {
            g_return_if_fail (sorttab_parent);

            g_object_set_data(G_OBJECT (paned), "paned_id", "st_0");
            gtk_paned_pack2(sorttab_parent, paned, TRUE, TRUE);
            st_update_paned_position();
        }
        else {
            gtk_paned_pack2(st_paned[i - 1], paned, TRUE, TRUE);
        }

        st_paned[i] = GTK_PANED (paned);
    }
}

static gboolean st_button_press_event(GtkWidget *w, GdkEventButton *e, gpointer data) {
    if (w && e) {
        switch (e->button) {
        case 3:
            st_select_current_position(GPOINTER_TO_INT(data), e->x, e->y);
            st_context_menu_init(GPOINTER_TO_INT(data));
            return TRUE;
        default:
            break;
        }

    }
    return (FALSE);
}

/* Create tracks listview */
static void st_create_treeview(gint inst, ST_CAT_item st_cat) {
    SortTab *st = sorttab[inst];
    GtkTreeSelection *selection;
    GtkTreeView *treeview;
    GtkCellRenderer *renderer;
    GtkTreeViewColumn *column;

    /* create treeview */
    treeview = GTK_TREE_VIEW (gtk_tree_view_new ());
    gtk_widget_show(GTK_WIDGET (treeview));
    st->treeview[st_cat] = treeview;
    gtk_container_add(GTK_CONTAINER (st->window[st_cat]), GTK_WIDGET (treeview));
    gtk_tree_view_set_model(treeview, st->model);
    g_signal_connect_after ((gpointer) treeview, "key_release_event",
            G_CALLBACK (on_st_treeview_key_release_event),
            NULL);
    gtk_drag_source_set(GTK_WIDGET (treeview), GDK_BUTTON1_MASK, st_drag_types, TGNR (st_drag_types), GDK_ACTION_COPY
            | GDK_ACTION_MOVE);
    g_signal_connect (G_OBJECT (treeview), "button-press-event",
            G_CALLBACK (st_button_press_event), GINT_TO_POINTER(inst));
    g_signal_connect ((gpointer) treeview, "drag_data_get",
            G_CALLBACK (st_drag_data_get),
            NULL);
    g_signal_connect ((gpointer) treeview, "drag-end",
            G_CALLBACK (st_drag_end),
            NULL);
    gtk_tree_view_set_rules_hint(GTK_TREE_VIEW (treeview), TRUE);
    gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW (treeview), TRUE);
    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW (treeview), FALSE);
    gtk_tree_view_set_enable_search(GTK_TREE_VIEW (treeview), TRUE);
    gtk_tree_view_set_search_column(GTK_TREE_VIEW (treeview), 0);
    gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW (treeview), st_search_equal_func, NULL, NULL);

    selection = gtk_tree_view_get_selection(treeview);
    gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
    g_signal_connect (G_OBJECT (selection), "changed",
            G_CALLBACK (st_selection_changed),
            GINT_TO_POINTER(inst));
    /* Add column */
    renderer = gtk_cell_renderer_text_new();
    g_signal_connect (G_OBJECT (renderer), "edited",
            G_CALLBACK (st_cell_edited),
            GINT_TO_POINTER(inst));
    g_object_set_data(G_OBJECT (renderer), "column", (gint *) ST_COLUMN_ENTRY);
    column = gtk_tree_view_column_new();
    gtk_tree_view_column_pack_start(column, renderer, TRUE);
    column = gtk_tree_view_column_new_with_attributes("", renderer, NULL);
    gtk_tree_view_column_set_cell_data_func(column, renderer, st_cell_data_func, NULL, NULL);
    gtk_tree_view_column_set_sort_column_id(column, ST_COLUMN_ENTRY);
    gtk_tree_view_column_set_resizable(column, TRUE);
    gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
    gtk_tree_view_column_set_sort_order(column, GTK_SORT_ASCENDING);
    gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE (st->model), ST_COLUMN_ENTRY, st_data_compare_func, GINT_TO_POINTER(inst), NULL);
    gtk_tree_view_append_column(treeview, column);
}

/* Create the "special" page in the notebook and connect all the
 signals */
static void st_create_special(gint inst, GtkWidget *window) {
    GtkWidget *special;
    GtkWidget *viewport;
    GtkWidget *w;
    SortTab *st = sorttab[inst];
    gint i;
    GtkBuilder *special_xml;
    gchar *buf;

    special_xml = gtkpod_builder_xml_new(glade_file_path);
    special = gtkpod_builder_xml_get_widget(special_xml, "special_sorttab");
    viewport = gtkpod_builder_xml_get_widget(special_xml, "special_viewport");

    /* according to GTK FAQ: move a widget to a new parent */
    g_object_ref(viewport);
    gtk_container_remove(GTK_CONTAINER (special), viewport);
    gtk_container_add(GTK_CONTAINER (window), viewport);
    g_object_unref(viewport);

    /* Connect the signal handlers and set default value. User data
     is @inst+(additional data << SP_SHIFT) */
    /* AND/OR button */
    w = gtkpod_builder_xml_get_widget(special_xml, "sp_or_button");
    g_signal_connect ((gpointer)w,
            "toggled", G_CALLBACK (on_sp_or_button_toggled),
            GINT_TO_POINTER(inst));
    if (prefs_get_int_index("sp_or", inst))
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), TRUE);
    else {
        w = gtkpod_builder_xml_get_widget(special_xml, "sp_and_button");
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), TRUE);
    }

    /* RATING */
    w = gtkpod_builder_xml_get_widget(special_xml, "sp_rating_button");
    g_signal_connect ((gpointer)w,
            "toggled", G_CALLBACK (on_sp_cond_button_toggled),
            GUINT_TO_POINTER((T_RATING<<SP_SHIFT) + inst));
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), prefs_get_int_index("sp_rating_cond", inst));
    for (i = 0; i <= RATING_MAX; ++i) {
        gchar *buf = g_strdup_printf("sp_rating%d", i);
        w = gtkpod_builder_xml_get_widget(special_xml, buf);
        g_signal_connect ((gpointer)w,
                "toggled", G_CALLBACK (on_sp_rating_n_toggled),
                GUINT_TO_POINTER((i<<SP_SHIFT) + inst));
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), get_sp_rating_n(inst, i));
        g_free(buf);
    }

    /* PLAYCOUNT */
    w = gtkpod_builder_xml_get_widget(special_xml, "sp_playcount_button");
    g_signal_connect ((gpointer)w,
            "toggled", G_CALLBACK (on_sp_cond_button_toggled),
            GUINT_TO_POINTER((T_PLAYCOUNT<<SP_SHIFT) + inst));
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), prefs_get_int_index("sp_playcound_cond", inst));
    w = gtkpod_builder_xml_get_widget(special_xml, "sp_playcount_low");
    g_signal_connect ((gpointer)w,
            "value_changed",
            G_CALLBACK (on_sp_playcount_low_value_changed),
            GINT_TO_POINTER(inst));
    gtk_spin_button_set_value(GTK_SPIN_BUTTON (w), prefs_get_int_index("sp_playcount_low", inst));
    w = gtkpod_builder_xml_get_widget(special_xml, "sp_playcount_high");
    g_signal_connect ((gpointer)w,
            "value_changed",
            G_CALLBACK (on_sp_playcount_high_value_changed),
            GINT_TO_POINTER(inst));
    gtk_spin_button_set_value(GTK_SPIN_BUTTON (w), prefs_get_int_index("sp_playcount_high", inst));

    /* PLAYED */
    buf = prefs_get_string_index("sp_played_state", inst);

    w = gtkpod_builder_xml_get_widget(special_xml, "sp_played_button");
    st->ti_played.active = w;
    g_signal_connect ((gpointer)w,
            "toggled", G_CALLBACK (on_sp_cond_button_toggled),
            GUINT_TO_POINTER((T_TIME_PLAYED<<SP_SHIFT) + inst));
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), prefs_get_int_index("sp_played_cond", inst));
    w = gtkpod_builder_xml_get_widget(special_xml, "sp_played_entry");
    st->ti_played.entry = w;
    gtk_entry_set_text(GTK_ENTRY (w), buf);
    g_signal_connect ((gpointer)w,
            "activate", G_CALLBACK (on_sp_entry_activate),
            GUINT_TO_POINTER((T_TIME_PLAYED<<SP_SHIFT) + inst));
    g_signal_connect ((gpointer)gtkpod_builder_xml_get_widget (special_xml,
                    "sp_played_cal_button"),
            "clicked",
            G_CALLBACK (on_sp_cal_button_clicked),
            GUINT_TO_POINTER((T_TIME_PLAYED<<SP_SHIFT) + inst));
    g_free(buf);

    /* MODIFIED */
    buf = prefs_get_string_index("sp_modified_state", inst);

    w = gtkpod_builder_xml_get_widget(special_xml, "sp_modified_button");
    st->ti_modified.active = w;
    g_signal_connect ((gpointer)w,
            "toggled", G_CALLBACK (on_sp_cond_button_toggled),
            GUINT_TO_POINTER((T_TIME_MODIFIED<<SP_SHIFT) + inst));
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), prefs_get_int_index("sp_modified_cond", inst));
    w = gtkpod_builder_xml_get_widget(special_xml, "sp_modified_entry");
    st->ti_modified.entry = w;
    gtk_entry_set_text(GTK_ENTRY (w), buf);
    g_signal_connect ((gpointer)w,
            "activate", G_CALLBACK (on_sp_entry_activate),
            GUINT_TO_POINTER((T_TIME_MODIFIED<<SP_SHIFT) + inst));
    g_signal_connect ((gpointer)gtkpod_builder_xml_get_widget (special_xml,
                    "sp_modified_cal_button"),
            "clicked",
            G_CALLBACK (on_sp_cal_button_clicked),
            GUINT_TO_POINTER((T_TIME_MODIFIED<<SP_SHIFT) + inst));
    g_free(buf);

    /* ADDED */
    buf = prefs_get_string_index("sp_added_state", inst);

    w = gtkpod_builder_xml_get_widget(special_xml, "sp_added_button");
    st->ti_added.active = w;
    g_signal_connect ((gpointer)w,
            "toggled", G_CALLBACK (on_sp_cond_button_toggled),
            GUINT_TO_POINTER((T_TIME_ADDED<<SP_SHIFT) + inst));
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), prefs_get_int_index("sp_added_cond", inst));
    w = gtkpod_builder_xml_get_widget(special_xml, "sp_added_entry");
    st->ti_added.entry = w;
    gtk_entry_set_text(GTK_ENTRY (w), buf);
    g_signal_connect ((gpointer)w,
            "activate", G_CALLBACK (on_sp_entry_activate),
            GUINT_TO_POINTER((T_TIME_ADDED<<SP_SHIFT) + inst));
    g_signal_connect ((gpointer)gtkpod_builder_xml_get_widget (special_xml,
                    "sp_added_cal_button"),
            "clicked",
            G_CALLBACK (on_sp_cal_button_clicked),
            GUINT_TO_POINTER((T_TIME_ADDED<<SP_SHIFT) + inst));

    g_signal_connect ((gpointer)gtkpod_builder_xml_get_widget (special_xml, "sp_go"),
            "clicked", G_CALLBACK (on_sp_go_clicked),
            GINT_TO_POINTER(inst));
    w = gtkpod_builder_xml_get_widget(special_xml, "sp_go_always");
    g_signal_connect ((gpointer)w,
            "toggled", G_CALLBACK (on_sp_go_always_toggled),
            GINT_TO_POINTER(inst));
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), prefs_get_int_index("sp_autodisplay", inst));
    g_free(buf);

    /*
    st->sp_tooltips_data = gtk_tooltips_data_get(gtkpod_builder_xml_get_widget(special_xml, "sp_modified_entry"));
    g_return_if_fail (st->sp_tooltips_data);
    if (prefs_get_int("display_tooltips_main"))
        gtk_tooltips_enable(st->sp_tooltips_data->tooltips);
    else
        gtk_tooltips_disable(st->sp_tooltips_data->tooltips);
    */
    /* we don't need this any more */
    gtk_widget_destroy(special);
}

/* create the treeview for category @st_cat of instance @inst */
static void st_create_page(gint inst, ST_CAT_item st_cat) {
    GtkWidget *st0_notebook;
    GtkWidget *st0_window0;
    GtkWidget *st0_label0 = NULL;
    SortTab *st = sorttab[inst];

    /* destroy treeview if already present */
    if (st->treeview[st_cat]) {
        gtk_widget_destroy(GTK_WIDGET (st->treeview[st_cat]));
        st->treeview[st_cat] = NULL;
    }

    st0_notebook = GTK_WIDGET (st->notebook);

    if (st->window[st_cat]) {
        st0_window0 = GTK_WIDGET (st->window[st_cat]);
    }
    else { /* create window if not already present */
        st0_window0 = gtk_scrolled_window_new(NULL, NULL);
        gtk_widget_show(st0_window0);
        gtk_container_add(GTK_CONTAINER (st0_notebook), st0_window0);
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (st0_window0), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
        st->window[st_cat] = st0_window0;
    }

    switch (st_cat) {
    case ST_CAT_ARTIST:
        st0_label0 = gtk_label_new(_("Artist"));
        break;
    case ST_CAT_ALBUM:
        st0_label0 = gtk_label_new(_("Album"));
        break;
    case ST_CAT_GENRE:
        st0_label0 = gtk_label_new(_("Genre"));
        break;
    case ST_CAT_COMPOSER:
        st0_label0 = gtk_label_new(_("Comp."));
        break;
    case ST_CAT_TITLE:
        st0_label0 = gtk_label_new(_("Title"));
        break;
    case ST_CAT_YEAR:
        st0_label0 = gtk_label_new(_("Year"));
        break;
    case ST_CAT_SPECIAL:
        st0_label0 = gtk_label_new(_("Special"));
        break;
    default: /* should not happen... */
        g_return_if_reached ();
    }
    gtk_widget_show(st0_label0);
    gtk_notebook_set_tab_label(GTK_NOTEBOOK (st0_notebook), gtk_notebook_get_nth_page(GTK_NOTEBOOK (st0_notebook), st_cat), st0_label0);
    gtk_label_set_justify(GTK_LABEL (st0_label0), GTK_JUSTIFY_LEFT);

    if (st_cat != ST_CAT_SPECIAL) {
        st_create_treeview(inst, st_cat);
    }
    else {
        st_create_special(inst, st0_window0);
    }
}

/* create all ST_CAT_NUM treeviews and the special page in sort tab of
 * instance @inst, then set the model */
static void st_create_pages(gint inst) {
    GtkTreeModel *model;
    GtkListStore *liststore;
    SortTab *st = sorttab[inst];

    /* remove old model */
    if (st->model) {
        g_object_unref(G_OBJECT (st->model));
        st->model = NULL;
    }
    /* create model */
    liststore = gtk_list_store_new(ST_NUM_COLUMNS, G_TYPE_POINTER);
    model = GTK_TREE_MODEL (liststore);
    st->model = model;

    st_create_page(inst, ST_CAT_ARTIST);
    st_create_page(inst, ST_CAT_ALBUM);
    st_create_page(inst, ST_CAT_GENRE);
    st_create_page(inst, ST_CAT_COMPOSER);
    st_create_page(inst, ST_CAT_TITLE);
    st_create_page(inst, ST_CAT_YEAR);
    st_create_page(inst, ST_CAT_SPECIAL);
}

/* Create notebook and fill in sorttab[@inst] */
static void st_create_notebook(gint inst) {
    GtkWidget *st0_notebook;
    GtkPaned *paned;
    gint i, page;
    SortTab *st = sorttab[inst];

    if (st->notebook) {
        gtk_widget_destroy(GTK_WIDGET (st->notebook));
        st->notebook = NULL;
        for (i = 0; i < ST_CAT_NUM; ++i) {
            st->treeview[i] = NULL;
            st->window[i] = NULL;
        }
    }

    /* paned elements exist? */
    if (!st_paned[0])
        st_create_paned();

    st0_notebook = gtk_notebook_new();
    if (inst < prefs_get_int("sort_tab_num"))
        gtk_widget_show(st0_notebook);
    else
        gtk_widget_hide(st0_notebook);
    /* which pane? */
    if (inst == SORT_TAB_MAX - 1)
        i = inst - 1;
    else
        i = inst;
    paned = st_paned[i];
    /* how to pack? */
    if (inst == SORT_TAB_MAX - 1)
        gtk_paned_pack2(paned, st0_notebook, TRUE, TRUE);
    else
        gtk_paned_pack1(paned, st0_notebook, FALSE, TRUE);
    gtk_notebook_set_scrollable(GTK_NOTEBOOK (st0_notebook), TRUE);

    st->notebook = GTK_NOTEBOOK (st0_notebook);
    st_create_pages(inst);
    page = prefs_get_int_index("st_category", inst);
    st->current_category = page;
    gtk_notebook_set_current_page(st->notebook, page);
    st_set_string_compare_func(inst, page);

    if (prefs_get_int("st_sort") != SORT_NONE)
        st_sort_inst(inst, prefs_get_int("st_sort"));

    g_signal_connect ((gpointer) st0_notebook, "switch_page",
            G_CALLBACK (on_st_switch_page),
            GINT_TO_POINTER(inst));
}

/* Create sort tabs */
void st_create_tabs(GtkPaned *parent, gchar *glade_path) {
    g_return_if_fail(parent);
    g_return_if_fail(glade_path);

    glade_file_path = glade_path;

    gint inst;

    /* Assign the parent widget of the created paneds */
    if (sorttab_parent != parent)
        sorttab_parent = parent;

    /*   gchar *name; */

    /* we count downward here because the smaller sort tabs might try to
     initialize the higher one's -> create the higher ones first */
    for (inst = SORT_TAB_MAX - 1; inst >= 0; --inst) {
        sorttab[inst] = g_malloc0(sizeof(SortTab));
        st_create_notebook(inst);
    }
    /* adjust number of visible sorttabs (cannot use st_show_visible()
     because the latter calls st_redisplay(0) which refers to the
     playlist view which hasn't yet set up) */
    st_adjust_visible();

    st_show_hide_tooltips();
}

/* Clean up the memory used by sort tabs (program quit). */
void st_cleanup(void) {
    gint i, j;
    for (i = 0; i < SORT_TAB_MAX; ++i) {
        if (sorttab[i] != NULL) {
            sp_store_sp_entries(i);
            st_remove_all_entries_from_model(i);
            for (j = 0; j < ST_CAT_NUM; ++j) {
                C_FREE (sorttab[i]->lastselection[j]);
            }
            g_free(sorttab[i]);
            sorttab[i] = NULL;
        }
    }

    g_free(glade_file_path);
}

/* make the tooltips visible or hide it depending on the value set in
 * the prefs (tooltips_main) (called by display_show_hide_tooltips() */
void st_show_hide_tooltips(void) {
    gint i;

    for (i = 0; i < SORT_TAB_MAX; ++i) {
        /*
        GtkTooltips *tt;
        GtkTooltipsData *ttd;

        g_return_if_fail (sorttab[i]);
        ttd = sorttab[i]->sp_tooltips_data;
        g_return_if_fail (ttd);
        tt = ttd->tooltips;
        g_return_if_fail (tt);

        if (prefs_get_int("display_tooltips_main")) {
            gtk_tooltips_enable(tt);
        }
        else {
            gtk_tooltips_disable(tt);
        }

        */
    }
}

/*
 Set the correct locations of the filter tabs and track list
 when filter_tabs_top is changed
 */
void st_update_paned_position() {
    g_return_if_fail (sorttab_parent);

    GtkWidget *top = gtk_paned_get_child1(sorttab_parent);
    GtkWidget *bottom = gtk_paned_get_child2(sorttab_parent);
    if (!top || !bottom)
        return; // sorttab_parent currently lacks either top or bottom panes

    gboolean top_is_st_paned = g_object_get_data(G_OBJECT (top), "paned_id") != NULL;
    gboolean st_top = prefs_get_int("filter_tabs_top");

    if ((top_is_st_paned && !st_top) || (!top_is_st_paned && st_top)) {
        /* Filter tabs are not at the correct location - swap pane children */
        /* We use g_object_ref so the widgets are not auto-destroyed */
        g_object_ref(top);
        g_object_ref(bottom);

        gtk_container_remove(GTK_CONTAINER (sorttab_parent), top);
        gtk_container_remove(GTK_CONTAINER (sorttab_parent), bottom);

        gtk_paned_pack1(sorttab_parent, bottom, TRUE, TRUE);
        gtk_paned_pack2(sorttab_parent, top, TRUE, TRUE);

        g_object_unref(top);
        g_object_unref(bottom);
    }
}

/* ---------------------------------------------------------------- */
/*                                                                  */
/*                Section for calendar display                      */
/*                                                                  */
/* ---------------------------------------------------------------- */

/* Strings for 'Category-Combo' */

/* enum to access cat_strings */
enum {
    CAT_STRING_PLAYED = 0, CAT_STRING_MODIFIED = 1, CAT_STRING_ADDED = 2
};

/* typedef to specify lower or upper margin */
typedef enum {
    LOWER_MARGIN, UPPER_MARGIN
} MarginType;

/* Set the calendar @calendar, as well as spin buttons @hour and @min
 * according to @mactime. If @mactime is 0, check @no_margin
 * togglebutton, otherwise uncheck it. */
static void cal_set_time_widgets(GtkCalendar *cal, GtkSpinButton *hour, GtkSpinButton *min, GtkToggleButton *no_margin, time_t timet) {
    struct tm *tm;
    time_t tt = time(NULL);

    /* 0, -1 are treated in a special way (no lower/upper margin
     * -> set calendar to current time */
    if ((timet != 0) && (timet != -1)) {
        tt = timet;
        if (no_margin)
            gtk_toggle_button_set_active(no_margin, FALSE);
    }
    else if (no_margin)
        gtk_toggle_button_set_active(no_margin, TRUE);

    tm = localtime(&tt);

    if (cal) {
        gtk_calendar_select_month(cal, tm->tm_mon, 1900 + tm->tm_year);
        gtk_calendar_select_day(cal, tm->tm_mday);
    }

    if (hour)
        gtk_spin_button_set_value(hour, tm->tm_hour);
    if (min)
        gtk_spin_button_set_value(min, tm->tm_min);
}

static void cal_set_time(GtkWidget *cal, MarginType type, time_t timet) {
    GtkCalendar *calendar = NULL;
    GtkSpinButton *hour = NULL;
    GtkSpinButton *min = NULL;
    GtkToggleButton *no_margin = NULL;

    switch (type) {
    case LOWER_MARGIN:
        calendar = GTK_CALENDAR (gtkpod_builder_xml_get_widget (cal_xml, "lower_cal"));
        hour = GTK_SPIN_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "lower_hours"));
        min = GTK_SPIN_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "lower_minutes"));
        no_margin = GTK_TOGGLE_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "no_lower_margin"));
        break;
    case UPPER_MARGIN:
        calendar = GTK_CALENDAR (gtkpod_builder_xml_get_widget (cal_xml, "upper_cal"));
        hour = GTK_SPIN_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "upper_hours"));
        min = GTK_SPIN_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "upper_minutes"));
        no_margin = GTK_TOGGLE_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "no_upper_margin"));
        break;
    }
    cal_set_time_widgets(calendar, hour, min, no_margin, timet);
}

/* Extract data from calendar/time.
 *
 * Return value:
 *
 * pointer to 'struct tm' filled with the relevant data or NULL, if
 * the button no_margin was selected.
 *
 * If @tm is != NULL, modify that instead.
 *
 * You must g_free() the retuned value.
 */
static struct tm *cal_get_time(GtkWidget *cal, MarginType type, struct tm *tm) {
    GtkCalendar *calendar = NULL;
    GtkSpinButton *hour = NULL;
    GtkSpinButton *min = NULL;
    GtkSpinButton *sec = NULL;
    GtkToggleButton *no_margin = NULL;
    GtkToggleButton *no_time = NULL;

    switch (type) {
    case LOWER_MARGIN:
        calendar = GTK_CALENDAR (gtkpod_builder_xml_get_widget (cal_xml, "lower_cal"));
        hour = GTK_SPIN_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "lower_hours"));
        min = GTK_SPIN_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "lower_minutes"));
        no_margin = GTK_TOGGLE_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "no_lower_margin"));
        no_time = GTK_TOGGLE_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "lower_time"));
        break;
    case UPPER_MARGIN:
        calendar = GTK_CALENDAR (gtkpod_builder_xml_get_widget (cal_xml, "upper_cal"));
        hour = GTK_SPIN_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "upper_hours"));
        min = GTK_SPIN_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "upper_minutes"));
        no_margin = GTK_TOGGLE_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "no_upper_margin"));
        no_time = GTK_TOGGLE_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "upper_time"));
        break;
    }

    if (!gtk_toggle_button_get_active(no_margin)) {
        /* Initialize tm with current time and copy the result of
         * localtime() to persistent memory that can be g_free()'d */
        time_t tt = time(NULL);
        if (!tm) {
            tm = g_malloc(sizeof(struct tm));
            memcpy(tm, localtime(&tt), sizeof(struct tm));
        }

        if (calendar) {
            guint year, month, day;
            gtk_calendar_get_date(calendar, &year, &month, &day);
            tm->tm_year = year - 1900;
            tm->tm_mon = month;
            tm->tm_mday = day;
        }
        if (gtk_toggle_button_get_active(no_time)) {
            if (hour)
                tm->tm_hour = gtk_spin_button_get_value_as_int(hour);
            if (min)
                tm->tm_min = gtk_spin_button_get_value_as_int(min);
            if (sec)
                tm->tm_min = gtk_spin_button_get_value_as_int(sec);
        }
        else { /* use 0:00 for lower and 23:59 for upper margin */
            switch (type) {
            case LOWER_MARGIN:
                if (hour)
                    tm->tm_hour = 0;
                if (min)
                    tm->tm_min = 0;
                if (sec)
                    tm->tm_sec = 0;
                break;
            case UPPER_MARGIN:
                if (hour)
                    tm->tm_hour = 23;
                if (min)
                    tm->tm_min = 59;
                if (sec)
                    tm->tm_sec = 59;
                break;
            }
        }
    }
    return tm;
}

/* get the category (T_TIME_PLAYED or T_TIME_MODIFIED) selected in the
 * combo */
static T_item cal_get_category(GtkWidget *cal) {
    GtkWidget *w;
    T_item item;
    gint i = -1;

    w = gtkpod_builder_xml_get_widget(cal_xml, "cat_combo");
    i = gtk_combo_box_get_active(GTK_COMBO_BOX (w));

    switch (i) {
    case CAT_STRING_PLAYED:
        item = T_TIME_PLAYED;
        break;
    case CAT_STRING_MODIFIED:
        item = T_TIME_MODIFIED;
        break;
    case CAT_STRING_ADDED:
        item = T_TIME_ADDED;
        break;
    default:
        fprintf(stderr, "Programming error: cal_get_category () -- item not found.\n");
        /* set to something reasonable at least */
        item = T_TIME_PLAYED;
    }

    return item;
}

/* Returns a string "DD/MM/YYYY HH:MM". Data is taken from
 * @tm. Returns NULL if tm==NULL. You must g_free() the returned
 * string */
static gchar *cal_get_time_string(struct tm *tm) {
    gchar *str = NULL;

    if (tm)
        str
                = g_strdup_printf("%02d/%02d/%04d %d:%02d", tm->tm_mday, tm->tm_mon + 1, 1900 + tm->tm_year, tm->tm_hour, tm->tm_min);
    return str;
}

/* Extract data from calendar/time and write it to the corresponding
 entry in the specified sort tab */
static void cal_apply_data(GtkWidget *cal) {
    struct tm *lower, *upper;
    TimeInfo *ti;
    T_item item;
    gint inst;

    lower = cal_get_time(cal, LOWER_MARGIN, NULL);
    upper = cal_get_time(cal, UPPER_MARGIN, NULL);

    /* Get selected instance */
    inst = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON (gtkpod_builder_xml_get_widget (cal_xml, "sorttab_num_spin"))) - 1;
    /* Get selected category (played, modified or added) */
    item = cal_get_category(cal);
    /* Get pointer to corresponding TimeInfo struct */
    ti = sp_get_timeinfo_ptr(inst, item);

    if (ti) {
        GtkToggleButton *act = GTK_TOGGLE_BUTTON (ti->active);
        /* is criteria currently checked (active)? */
        gboolean active = gtk_toggle_button_get_active(act);
        gchar *str = NULL;
        gchar *str1 = cal_get_time_string(lower);
        gchar *str2 = cal_get_time_string(upper);

        if (!lower && !upper)
            if (!active) /* deactivate this criteria */
                gtk_toggle_button_set_active(act, FALSE);
        if (lower && !upper)
            str = g_strdup_printf("> %s", str1);
        if (!lower && upper)
            str = g_strdup_printf("< %s", str2);
        if (lower && upper)
            str = g_strdup_printf("%s < < %s", str1, str2);
        C_FREE (str1);
        C_FREE (str2);

        if (str) { /* set the new string if it's different */
            if (strcmp(str, gtk_entry_get_text(GTK_ENTRY (ti->entry))) != 0) {
                gtk_entry_set_text(GTK_ENTRY (ti->entry), str);
                /* notification that contents have changed */
                g_signal_emit_by_name(ti->entry, "activate");
            }
            g_free(str);
        }
        if (!active) { /* activate the criteria */
            gtk_toggle_button_set_active(act, TRUE);
        }
    }
    g_free(lower);
    g_free(upper);
}

/* Callback for 'Lower/Upper time ' buttons */
static void cal_time_toggled(GtkToggleButton *togglebutton, gpointer user_data) {
    gboolean sens = gtk_toggle_button_get_active(togglebutton);

    if ((GtkWidget *) togglebutton == gtkpod_builder_xml_get_widget(cal_xml, "lower_time")) {
        gtk_widget_set_sensitive(gtkpod_builder_xml_get_widget(cal_xml, "lower_time_box"), sens);
    }
    if ((GtkWidget *) togglebutton == gtkpod_builder_xml_get_widget(cal_xml, "upper_time")) {
        gtk_widget_set_sensitive(gtkpod_builder_xml_get_widget(cal_xml, "upper_time_box"), sens);
    }
}

/* Callback for 'No Lower/Upper Margin' buttons */
static void cal_no_margin_toggled(GtkToggleButton *togglebutton, gpointer user_data) {
    gboolean sens = !gtk_toggle_button_get_active(togglebutton);

    if ((GtkWidget *) togglebutton == gtkpod_builder_xml_get_widget(cal_xml, "no_lower_margin")) {
        gtk_widget_set_sensitive(gtkpod_builder_xml_get_widget(cal_xml, "lower_cal_box"), sens);
    }
    if ((GtkWidget *) togglebutton == gtkpod_builder_xml_get_widget(cal_xml, "no_upper_margin")) {
        gtk_widget_set_sensitive(gtkpod_builder_xml_get_widget(cal_xml, "upper_cal_box"), sens);
    }
}

/* Save the default geometry of the window */
static void cal_save_default_geometry(GtkWindow *cal) {
    gint x, y;

    gtk_window_get_size(cal, &x, &y);
    prefs_set_int("size_cal.x", x);
    prefs_set_int("size_cal.y", y);

}

/* Callback for 'delete' event */
static gboolean cal_delete_event(GtkWidget *widget, GdkEvent *event, gpointer user_data) {
    cal_save_default_geometry(GTK_WINDOW (user_data));
    return FALSE;
}

/* Callback for 'Cancel' button */
static void cal_cancel(GtkButton *button, gpointer user_data) {
    cal_save_default_geometry(GTK_WINDOW (user_data));
    gtk_widget_destroy(user_data);
}

/* Callback for 'Apply' button */
static void cal_apply(GtkButton *button, gpointer user_data) {
    cal_save_default_geometry(GTK_WINDOW (user_data));
    cal_apply_data(GTK_WIDGET (user_data));
}

/* Callback for 'OK' button */
static void cal_ok(GtkButton *button, gpointer user_data) {
    cal_apply(button, user_data);
    gtk_widget_destroy(user_data);
}

/* Open a calendar window. Preset the values for instance @inst,
 category @item (time played, time modified or time added) */
void cal_open_calendar(gint inst, T_item item) {
    SortTab *st;
    GtkWidget *w;
    GtkWidget *cal;
    int index = -1;
    gint defx, defy;
    TimeInfo *ti;

    /* Sanity */
    if (inst >= SORT_TAB_MAX)
        return;

    st = sorttab[inst];

    /* Sanity */
    if (!st)
        return;

    cal_xml = gtkpod_builder_xml_new(glade_file_path);

    gtk_builder_connect_signals (cal_xml, NULL);

    cal = gtkpod_builder_xml_get_widget(cal_xml, "calendar_window");

    /* Set to saved size */
    defx = prefs_get_int("size_cal.x");
    defy = prefs_get_int("size_cal.y");
    gtk_window_set_default_size(GTK_WINDOW (cal), defx, defy);

    /* Set sorttab number */
    w = gtkpod_builder_xml_get_widget(cal_xml, "sorttab_num_spin");
    gtk_spin_button_set_range(GTK_SPIN_BUTTON (w), 1, SORT_TAB_MAX);
    gtk_spin_button_set_value(GTK_SPIN_BUTTON (w), inst + 1);

    /* Set Category-Combo */
    w = gtkpod_builder_xml_get_widget(cal_xml, "cat_combo");

    switch (item) {
    case T_TIME_PLAYED:
        index = CAT_STRING_PLAYED;
        break;
    case T_TIME_MODIFIED:
        index = CAT_STRING_MODIFIED;
        break;
    case T_TIME_ADDED:
        index = CAT_STRING_ADDED;
        break;
    default:
        fprintf(stderr, "Programming error: cal_open_calendar() -- item not found\n");
        break;
    }

    gtk_combo_box_set_active(GTK_COMBO_BOX (w), index);

    /* Make sure we use the current contents of the entry */
    sp_store_sp_entries(inst);
    /* set calendar */
    ti = sp_update_date_interval_from_string(inst, item, TRUE);

    /* set the calendar if we have a valid TimeInfo */
    if (ti) {
        if (!ti->valid) { /* set to reasonable default */
            ti->lower = 0;
            ti->upper = 0;
        }

        /* Lower Margin */
        w = gtkpod_builder_xml_get_widget(cal_xml, "no_lower_margin");
        g_signal_connect (w,
                "toggled",
                G_CALLBACK (cal_no_margin_toggled),
                cal);
        w = gtkpod_builder_xml_get_widget(cal_xml, "lower_time");
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (w), TRUE);
        g_signal_connect (w,
                "toggled",
                G_CALLBACK (cal_time_toggled),
                cal);

        cal_set_time(cal, LOWER_MARGIN, ti->lower);

        /* Upper Margin */
        w = gtkpod_builder_xml_get_widget(cal_xml, "no_upper_margin");
        g_signal_connect (w,
                "toggled",
                G_CALLBACK (cal_no_margin_toggled),
                cal);
        w = gtkpod_builder_xml_get_widget(cal_xml, "upper_time");
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (w), TRUE);
        g_signal_connect (w,
                "toggled",
                G_CALLBACK (cal_time_toggled),
                cal);
        cal_set_time(cal, UPPER_MARGIN, ti->upper);
    }

    /* Connect delete-event */
    g_signal_connect (cal, "delete_event",
            G_CALLBACK (cal_delete_event), cal);
    /* Connect cancel-button */
    g_signal_connect (gtkpod_builder_xml_get_widget (cal_xml, "cal_cancel"), "clicked",
            G_CALLBACK (cal_cancel), cal);
    /* Connect apply-button */
    g_signal_connect (gtkpod_builder_xml_get_widget (cal_xml, "cal_apply"), "clicked",
            G_CALLBACK (cal_apply), cal);
    /* Connect ok-button */
    g_signal_connect (gtkpod_builder_xml_get_widget (cal_xml, "cal_ok"), "clicked",
            G_CALLBACK (cal_ok), cal);

    gtk_window_set_transient_for(GTK_WINDOW (cal), GTK_WINDOW (gtkpod_app));
    gtk_widget_show(cal);
}

/* Let the user select a sort tab number */
/* @text: text to be displayed */
/* return value: -1: user selected cancel
 0...prefs_get_sort_tab_number()-1: selected tab */
gint st_get_sort_tab_number(gchar *text) {
    static gint last_nr = 1;
    GtkWidget *mdialog;
    GtkDialog *dialog;
    GtkWidget *combo;
    GtkCellRenderer *cell;
    gint result;
    gint i, nr, stn;
    gchar *bufp;
    GtkListStore *store;
    GtkTreeIter iter;

    mdialog = gtk_message_dialog_new(GTK_WINDOW (gtkpod_app), GTK_DIALOG_DESTROY_WITH_PARENT,
                                     GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, "%s", text);

    dialog = GTK_DIALOG (mdialog);

    store = gtk_list_store_new(1, G_TYPE_STRING);
    stn = prefs_get_int("sort_tab_num");
    /* Create list */
    for (i = 1; i <= stn; ++i) {
        bufp = g_strdup_printf("%d", i);
        gtk_list_store_append(store, &iter);
        gtk_list_store_set(store, &iter, 0, bufp, -1);
        g_free(bufp);
    }

    combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));

    /* Create cell renderer. */
    cell = gtk_cell_renderer_text_new();

    /* Pack it to the combo box. */
    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(combo), cell, TRUE );

    /* Connect renderer to data source */
    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(combo), cell, "text", 0, NULL );

    gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);

    gtk_widget_show_all(combo);
    gtk_container_add(GTK_CONTAINER (gtk_dialog_get_content_area(GTK_DIALOG(dialog))), combo);

    result = gtk_dialog_run(GTK_DIALOG (mdialog));

    /* free the list */
    if (result == GTK_RESPONSE_CANCEL) {
        nr = -1; /* no selection */
    }
    else {
        gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter);
        gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 0, &bufp, -1);
        if (bufp) {
            nr = atoi(bufp) - 1;
            last_nr = nr + 1;
            g_free(bufp);
        } else {
            nr = -1; /* selection failed */
        }
    }

    gtk_widget_destroy(mdialog);
    g_object_unref(store);

    return nr;
}

void st_delete_entry_head(gint inst, DeleteAction deleteaction) {
    struct DeleteData *dd;
    Playlist *pl;
    GList *selected_tracks = NULL;
    GString *str;
    gchar *label = NULL, *title = NULL;
    gboolean confirm_again;
    gchar *confirm_again_key;
    TabEntry *entry;
    GtkResponseType response;
    iTunesDB *itdb;

    g_return_if_fail (inst >= 0);
    g_return_if_fail (inst <= prefs_get_int("sort_tab_num"));

    pl = gtkpod_get_current_playlist();
    if (pl == NULL) { /* no playlist??? Cannot happen, but... */
        message_sb_no_playlist_selected();
        return;
    }
    itdb = pl->itdb;
    g_return_if_fail (itdb);

    entry = st_get_selected_entry(inst);
    if (entry == NULL) { /* no entry selected */
        gtkpod_statusbar_message(_("No entry selected."));
        return;
    }

    if (entry->members == NULL) { /* no tracks in entry -> just remove entry */
        if (!entry->master)
            st_remove_entry(entry, inst);
        else
            gtkpod_statusbar_message(_("Cannot remove entry 'All'"));
        return;
    }

    selected_tracks = g_list_copy(entry->members);

    dd = g_malloc0(sizeof(struct DeleteData));
    dd->deleteaction = deleteaction;
    dd->tracks = selected_tracks;
    dd->pl = pl;
    dd->itdb = itdb;

    delete_populate_settings(dd, &label, &title, &confirm_again, &confirm_again_key, &str);

    /* open window */
    response = gtkpod_confirmation(-1, /* gint id, */
    TRUE, /* gboolean modal, */
    title, /* title */
    label, /* label */
    str->str, /* scrolled text */
    NULL, 0, NULL, /* option 1 */
    NULL, 0, NULL, /* option 2 */
    confirm_again, /* gboolean confirm_again, */
    confirm_again_key,/* ConfHandlerOpt confirm_again_key,*/
    CONF_NULL_HANDLER, /* ConfHandler ok_handler,*/
    NULL, /* don't show "Apply" button */
    CONF_NULL_HANDLER, /* cancel_handler,*/
    NULL, /* gpointer user_data1,*/
    NULL); /* gpointer user_data2,*/

    switch (response) {
    case GTK_RESPONSE_OK:
        /* Delete the tracks */
        delete_track_ok(dd);
        /* Delete the entry */
        st_remove_entry(entry, inst);
        break;
    default:
        delete_track_cancel(dd);
        break;
    }

    g_free(label);
    g_free(title);
    g_free(confirm_again_key);
    g_string_free(str, TRUE);
}

void sorttab_display_select_playlist_cb(GtkPodApp *app, gpointer pl, gpointer data) {
    Playlist *new_playlist = pl;

    /* Remove all data from tab */
    st_init(-1, 0);

    /* Add the tracks from the selected playlist to the sorttabs */
    if (new_playlist && new_playlist->members) {
        GList *gl;

        st_enable_disable_view_sort(0, FALSE);

        for (gl = new_playlist->members; gl; gl = gl->next) {
            /* add all tracks to sort tab 0 */
            Track *track = gl->data;
            st_add_track(track, FALSE, TRUE, 0);
        }

        st_enable_disable_view_sort(0, TRUE);
        st_add_track(NULL, TRUE, TRUE, 0);
    }
}

void sorttab_display_track_removed_cb(GtkPodApp *app, gpointer tk, gint32 pos, gpointer data) {
    Track *old_track = tk;
    st_remove_track(old_track, 0);
}

void sorttab_display_track_updated_cb(GtkPodApp *app, gpointer tk, gpointer data) {
    Track *track = tk;
    st_track_changed(track, FALSE, 0);
}

