changeset 3298:f985357757e0 trunk

audacious-core: convert to tuple-ng, remove titlestring API.
author William Pitcock <nenolod@atheme-project.org>
date Fri, 10 Aug 2007 05:22:35 -0500
parents 575f59c45eaf
children 4ced71e14fcb
files src/audacious/Makefile src/audacious/dbus.c src/audacious/input.c src/audacious/input.h src/audacious/playlist.c src/audacious/playlist.h src/audacious/plugin.h src/audacious/titlestring.c src/audacious/titlestring.h src/audacious/ui_albumart.c src/audacious/ui_fileinfo.c src/audacious/ui_fileinfo.h src/audacious/ui_fileinfopopup.c src/audacious/ui_fileinfopopup.h src/audacious/ui_lastfm.c src/audacious/ui_playlist.c src/audacious/ui_skinned_playlist.c
diffstat 17 files changed, 216 insertions(+), 790 deletions(-) [+]
line wrap: on
line diff
--- a/src/audacious/Makefile	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/Makefile	Fri Aug 10 05:22:35 2007 -0500
@@ -51,7 +51,6 @@
 	playlist_container.h \
 	plugin.h \
 	strings.h \
-	titlestring.h \
 	tuple.h \
 	tuple_formatter.h \
 	ui_fileinfopopup.h \
@@ -97,7 +96,6 @@
 	rcfile.c \
 	signals.c \
 	strings.c \
-	titlestring.c \
 	tuple.c \
 	tuple_formatter.c \
 	skin.c \
--- a/src/audacious/dbus.c	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/dbus.c	Fri Aug 10 05:22:35 2007 -0500
@@ -37,7 +37,7 @@
 #include "ui_playlist.h"
 #include "ui_preferences.h"
 #include "memorypool.h"
-#include "titlestring.h"
+#include "tuple.h"
 #include "ui_jumptotrack.h"
 #include "strings.h"
 
@@ -528,59 +528,26 @@
 
 gboolean audacious_rc_song_tuple(RemoteObject *obj, guint pos, gchar *field,
                                  GValue *value, GError **error) {
-    TitleInput *tuple;
+    Tuple *tuple;
     tuple = playlist_get_tuple(playlist_get_active(), pos);
     if (!tuple) {
         return FALSE;
     } else {
-        if (!strcasecmp(field, "performer")) {
-            g_value_init(value, G_TYPE_STRING);
-            g_value_set_string(value, tuple->performer);
-        } else if (!strcasecmp(field, "album_name")) {
-            g_value_init(value, G_TYPE_STRING);
-            g_value_set_string(value, tuple->album_name);
-        } else if (!strcasecmp(field, "track_name")) {
-            g_value_init(value, G_TYPE_STRING);
-            g_value_set_string(value, tuple->track_name);
-        } else if (!strcasecmp(field, "track_number")) {
-            g_value_init(value, G_TYPE_INT);
-            g_value_set_int(value, tuple->track_number);
-        } else if (!strcasecmp(field, "year")) {
-            g_value_init(value, G_TYPE_INT);
-            g_value_set_int(value, tuple->year);
-        } else if (!strcasecmp(field, "date")) {
-            g_value_init(value, G_TYPE_STRING);
-            g_value_set_string(value, tuple->date);
-        } else if (!strcasecmp(field, "genre")) {
-            g_value_init(value, G_TYPE_STRING);
-            g_value_set_string(value, tuple->genre);
-        } else if (!strcasecmp(field, "comment")) {
+        TupleValueType type = tuple_get_value_type(tuple, field);
+
+        switch(type)
+        {
+        case TUPLE_STRING:
             g_value_init(value, G_TYPE_STRING);
-            g_value_set_string(value, tuple->comment);
-        } else if (!strcasecmp(field, "file_name")) {
-            g_value_init(value, G_TYPE_STRING);
-            g_value_set_string(value, tuple->file_name);
-        } else if (!strcasecmp(field, "file_ext")) {
-            g_value_init(value, G_TYPE_STRING);
-            g_value_set_string(value, g_strdup(tuple->file_ext));
-        } else if (!strcasecmp(field, "file_path")) {
-            g_value_init(value, G_TYPE_STRING);
-            g_value_set_string(value, tuple->file_path);
-        } else if (!strcasecmp(field, "length")) {
+            g_value_set_string(value, tuple_get_string(tuple, field));
+            break;
+        case TUPLE_INT:
             g_value_init(value, G_TYPE_INT);
-            g_value_set_int(value, tuple->length);
-        } else if (!strcasecmp(field, "album_name")) {
-            g_value_init(value, G_TYPE_STRING);
-            g_value_set_string(value, tuple->album_name);
-        } else if (!strcasecmp(field, "formatter")) {
-            g_value_init(value, G_TYPE_STRING);
-            g_value_set_string(value, tuple->formatter);
-        } else if (!strcasecmp(field, "custom")) {
-            g_value_init(value, G_TYPE_STRING);
-            g_value_set_string(value, tuple->custom);
-        } else if (!strcasecmp(field, "mtime")) {
-            g_value_init(value, G_TYPE_INT);
-            g_value_set_int(value, tuple->mtime);
+            g_value_set_int(value, tuple_get_int(tuple, field));
+            break;
+        default:
+            return FALSE;
+            break;
         }
     }
     return TRUE;
--- a/src/audacious/input.c	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/input.c	Fri Aug 10 05:22:35 2007 -0500
@@ -39,7 +39,8 @@
 #include "playback.h"
 #include "pluginenum.h"
 #include "strings.h"
-#include "titlestring.h"
+#include "tuple.h"
+#include "tuple_formatter.h"
 #include "ui_main.h"
 #include "util.h"
 #include "visualization.h"
@@ -414,7 +415,7 @@
     {
         if (ip->probe_for_tuple != NULL)
         {
-            TitleInput *tuple = ip->probe_for_tuple(filename_proxy, fd);
+            Tuple *tuple = ip->probe_for_tuple(filename_proxy, fd);
 
             if (tuple != NULL)
             {
@@ -424,7 +425,7 @@
                 pr = g_new0(ProbeResult, 1);
                 pr->ip = ip;
                 pr->tuple = tuple;
-                pr->tuple->mtime = input_get_mtime(filename_proxy);
+                tuple_associate_int(pr->tuple, "mtime", input_get_mtime(filename_proxy));
 
                 return pr;
             }
@@ -492,7 +493,7 @@
 
         if (fd && ip->probe_for_tuple != NULL)
         {
-            TitleInput *tuple = ip->probe_for_tuple(filename_proxy, fd);
+            Tuple *tuple = ip->probe_for_tuple(filename_proxy, fd);
 
             if (tuple != NULL)
             {
@@ -502,7 +503,7 @@
                 pr = g_new0(ProbeResult, 1);
                 pr->ip = ip;
                 pr->tuple = tuple;
-                pr->tuple->mtime = input_get_mtime(filename_proxy);
+                tuple_associate_int(pr->tuple, "mtime", input_get_mtime(filename_proxy));
 
                 return pr;
             }
@@ -569,7 +570,7 @@
 input_get_song_info(const gchar * filename, gchar ** title, gint * length)
 {
     InputPlugin *ip = NULL;
-    BmpTitleInput *input;
+    Tuple *tuple;
     gchar *tmp = NULL, *ext;
     gchar *filename_proxy;
     ProbeResult *pr;
@@ -595,38 +596,42 @@
         g_free(tmp);
     }
     else {
-        input = bmp_title_input_new();
+        tuple = tuple_new();
 
         tmp = g_strdup(filename);
         if ((ext = strrchr(tmp, '.')))
             *ext = '\0';
 
-        input->file_name = g_path_get_basename(tmp);
-        input->file_ext = ext ? ext + 1 : NULL;
-        input->file_path = g_path_get_dirname(tmp);
+        tuple_associate_string(tuple, "file-name", g_path_get_basename(tmp));
+
+        if (ext)
+            tuple_associate_string(tuple, "file-ext", ext + 1);
 
-        if ((tmp = xmms_get_titlestring(xmms_get_gentitle_format(), input))) {
+        tuple_associate_string(tuple, "file-path", g_path_get_dirname(tmp));
+
+        tmp = tuple_formatter_process_string(tuple, cfg.gentitle_format);
+        if (tmp != NULL && *tmp != '\0') {
             (*title) = str_to_utf8(tmp);
             g_free(tmp);
         }
         else {
-            (*title) = filename_to_utf8(input->file_name);
+            (*title) = filename_to_utf8(tuple_get_string(tuple, "file-name"));
         }
 
         (*length) = -1;
 
-        bmp_title_input_free(input);
-        input = NULL;
+        mowgli_object_unref(tuple);
+        tuple = NULL;
     }
 
     g_free(filename_proxy);
 }
 
-TitleInput *
+Tuple *
 input_get_song_tuple(const gchar * filename)
 {
     InputPlugin *ip = NULL;
-    TitleInput *input;
+    Tuple *input;
     gchar *ext = NULL;
     gchar *filename_proxy;
     ProbeResult *pr;
@@ -649,16 +654,23 @@
         input = ip->get_song_tuple(filename_proxy);
     else
     {
-        input = bmp_title_input_new();
+        gchar *tmp;
+
+        input = tuple_new();
 
-        ext = strrchr(filename, '.');
+        tmp = g_strdup(filename);
+        if ((ext = strrchr(tmp, '.')))
+            *ext = '\0';
 
-        input->track_name = NULL;
-        input->length = -1;
-        input_get_song_info(filename, &input->track_name, &input->length);
-        input->file_name = g_path_get_basename(filename);
-        input->file_ext = ( ( ext != NULL ) ? g_strdup(ext + 1) : NULL );
-        input->file_path = g_path_get_dirname(filename);
+        tuple_associate_string(input, "file-name", g_path_get_basename(tmp));
+
+        if (ext)
+            tuple_associate_string(input, "file-ext", ext + 1);
+
+        tuple_associate_string(input, "file-path", g_path_get_dirname(tmp));
+        tuple_associate_int(input, "length", -1);
+
+        g_free(tmp);
     }
 
     g_free(filename_proxy);
--- a/src/audacious/input.h	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/input.h	Fri Aug 10 05:22:35 2007 -0500
@@ -41,7 +41,7 @@
 };
 
 typedef struct {
-    TitleInput *tuple;
+    Tuple *tuple;
     InputPlugin *ip;
 } ProbeResult;
 
@@ -53,7 +53,7 @@
 void free_vis_data(void);
 
 ProbeResult *input_check_file(const gchar * filename, gboolean show_warning);
-TitleInput *input_get_song_tuple(const gchar * filename);
+Tuple *input_get_song_tuple(const gchar * filename);
 
 void input_play(gchar * filename);
 void input_stop(void);
--- a/src/audacious/playlist.c	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/playlist.c	Fri Aug 10 05:22:35 2007 -0500
@@ -176,7 +176,7 @@
         return;
 
     if (entry->tuple != NULL) {
-        bmp_title_input_free(entry->tuple);
+        mowgli_object_unref(entry->tuple);
         entry->tuple = NULL;
     }
 
@@ -192,23 +192,21 @@
 static gboolean
 playlist_entry_get_info(PlaylistEntry * entry)
 {
-    TitleInput *tuple = NULL;
+    Tuple *tuple = NULL;
     ProbeResult *pr = NULL;
     time_t modtime;
+    const gchar *formatter;
 
     g_return_val_if_fail(entry != NULL, FALSE);
 
-    if (entry->tuple == NULL || entry->tuple->mtime > 0 || entry->tuple->mtime == -1)
+    if (entry->tuple == NULL || tuple_get_int(entry->tuple, "mtime") > 0 || tuple_get_int(entry->tuple, "mtime") == -1)
         modtime = playlist_get_mtime(entry->filename);
     else
         modtime = 0;  /* URI -nenolod */
 
     if (str_has_prefix_nocase(entry->filename, "http:") &&
         g_thread_self() != playlist_get_info_thread)
-    {
-        g_print("attempting to retrieve remote info not in background thread!\n");
         return FALSE;
-    }
 
     if (entry->decoder == NULL)
     {
@@ -219,10 +217,10 @@
 
     /* renew tuple if file mtime is newer than tuple mtime. */
     if(entry->tuple){
-        if(entry->tuple->mtime == modtime)
+        if(tuple_get_int(entry->tuple, "mtime") == modtime)
             return TRUE;
         else {
-            bmp_title_input_free(entry->tuple);
+            mowgli_object_unref(entry->tuple);
             entry->tuple = NULL;
         }
     }
@@ -236,11 +234,12 @@
         return FALSE;
 
     /* attach mtime */
-    tuple->mtime = modtime;
+    tuple_associate_int(tuple, "mtime", modtime);
 
     /* entry is still around */
-    entry->title = xmms_get_titlestring(tuple->formatter != NULL ? tuple->formatter : xmms_get_gentitle_format(), tuple);
-    entry->length = tuple->length;
+    formatter = tuple_get_string(tuple, "formatter");
+    entry->title = tuple_formatter_process_string(tuple, formatter ? formatter : cfg.gentitle_format);
+    entry->length = tuple_get_int(tuple, "length");
     entry->tuple = tuple;
 
     g_free(pr);
@@ -638,7 +637,7 @@
 __playlist_ins_with_info_tuple(Playlist * playlist,
 			       const gchar * filename,
 			       gint pos,
-			       TitleInput *tuple,
+			       Tuple *tuple,
 			       InputPlugin * dec)
 {
     PlaylistEntry *entry;
@@ -646,7 +645,7 @@
     g_return_if_fail(playlist != NULL);
     g_return_if_fail(filename != NULL);
 
-    entry = playlist_entry_new(filename, tuple ? tuple->track_name : NULL, tuple ? tuple->length : -1, dec);
+    entry = playlist_entry_new(filename, tuple ? tuple_get_string(tuple, "title") : NULL, tuple ? tuple_get_int(tuple, "length") : -1, dec);
     if(!playlist->tail)
         playlist->tail = g_list_last(playlist->entries);
 
@@ -674,12 +673,13 @@
 
     PLAYLIST_UNLOCK( playlist->mutex );
     if (tuple != NULL) {
-        entry->title = xmms_get_titlestring(tuple->formatter != NULL ? tuple->formatter : xmms_get_gentitle_format(), tuple);
-        entry->length = tuple->length;
+        const gchar *formatter = tuple_get_string(tuple, "formatter");
+        entry->title = tuple_formatter_process_string(tuple, formatter ? formatter : cfg.gentitle_format);
+        entry->length = tuple_get_int(tuple, "length");
         entry->tuple = tuple;
     }
 
-    if(tuple != NULL && tuple->mtime == -1) { // kick the scanner thread only if mtime = -1 (uninitialized).
+    if(tuple != NULL && tuple_get_int(tuple, "mtime") == -1) { // kick the scanner thread only if mtime = -1 (uninitialized).
         g_mutex_lock(mutex_scan);
         playlist_get_info_scan_active = TRUE;
         g_mutex_unlock(mutex_scan);
@@ -695,7 +695,7 @@
     VFSFile *file;
     ProbeResult *pr = NULL;
     InputPlugin *dec = NULL;
-    TitleInput *tuple = NULL;
+    Tuple *tuple = NULL;
 
     g_return_val_if_fail(playlist != NULL, FALSE);
     g_return_val_if_fail(filename != NULL, FALSE);
@@ -1049,12 +1049,10 @@
         playlist->position->title = g_strdup(title);
         playlist->position->length = length;
 
-        // overwrite tuple->track_name, mainly for streaming. it may incur side effects. --yaz
-        if(playlist->position->tuple && length == -1){
-            if(playlist->position->tuple->track_name){
-                g_free(playlist->position->tuple->track_name);
-            }
-            playlist->position->tuple->track_name = g_strdup(title);
+        // overwrite tuple::title, mainly for streaming. it may incur side effects. --yaz
+        if(playlist->position->tuple && tuple_get_int(playlist->position->tuple, "length") == -1){
+            tuple_disassociate(playlist->position->tuple, "title");
+            tuple_associate_string(playlist->position->tuple, "title", title);
         }
     }
 
@@ -1685,7 +1683,7 @@
 			     const gchar * filename_p,
 			     const gchar * playlist_name, 
 			     gint pos,
-			     TitleInput *tuple)
+			     Tuple *tuple)
 {
     gchar *filename;
     gchar *tmp, *path;
@@ -1829,6 +1827,7 @@
     gchar *title = NULL;
     PlaylistEntry *entry;
     GList *node;
+    time_t mtime;
 
     if (!playlist)
         return NULL;
@@ -1842,9 +1841,14 @@
 
     entry = node->data;
 
+    if (entry->tuple)
+        mtime = tuple_get_int(entry->tuple, "mtime");
+    else
+        mtime = 0;
+
     /* FIXME: simplify this logic */
     if ((entry->title == NULL && entry->length == -1) ||
-        (entry->tuple && entry->tuple->mtime != 0 && (entry->tuple->mtime == -1 || entry->tuple->mtime != playlist_get_mtime(entry->filename))))
+        (entry->tuple && mtime != 0 && (mtime == -1 || mtime != playlist_get_mtime(entry->filename))))
     {
         if (playlist_entry_get_info(entry))
             title = entry->title;
@@ -1866,12 +1870,13 @@
     return str_to_utf8(title);
 }
 
-TitleInput *
+Tuple *
 playlist_get_tuple(Playlist *playlist, guint pos)
 {
     PlaylistEntry *entry;
-    TitleInput *tuple = NULL;
+    Tuple *tuple = NULL;
     GList *node;
+    time_t mtime;
 
     if (!playlist)
         return NULL;
@@ -1884,9 +1889,14 @@
 
     tuple = entry->tuple;
 
+    if (tuple)
+        mtime = tuple_get_int(tuple, "mtime");
+    else
+        mtime = 0;
+
     // if no tuple or tuple with old mtime, get new one.
     if (tuple == NULL || 
-        (entry->tuple && entry->tuple->mtime != 0 && (entry->tuple->mtime == -1 || entry->tuple->mtime != playlist_get_mtime(entry->filename))))
+        (entry->tuple && mtime != 0 && (mtime == -1 || mtime != playlist_get_mtime(entry->filename))))
     {
         playlist_entry_get_info(entry);
         tuple = entry->tuple;
@@ -1901,6 +1911,7 @@
     gint song_time = -1;
     PlaylistEntry *entry;
     GList *node;
+    time_t mtime;
 
     if (!playlist)
         return -1;
@@ -1910,8 +1921,14 @@
     }
 
     entry = node->data;
+
+    if (entry->tuple)
+        mtime = tuple_get_int(entry->tuple, "mtime");
+    else
+        mtime = 0;
+
     if (entry->tuple == NULL ||
-        (entry->tuple->mtime != 0 && (entry->tuple->mtime == -1 || entry->tuple->mtime != playlist_get_mtime(entry->filename)))) {
+        (mtime != 0 && (mtime == -1 || mtime != playlist_get_mtime(entry->filename)))) {
 
         if (playlist_entry_get_info(entry))
             song_time = entry->length;
@@ -1927,6 +1944,9 @@
 playlist_compare_track(PlaylistEntry * a,
 		       PlaylistEntry * b)
 {
+    gint tracknumber_a;
+    gint tracknumber_b;
+
     g_return_val_if_fail(a != NULL, 0);
     g_return_val_if_fail(b != NULL, 0);
 
@@ -1935,11 +1955,16 @@
     if(!b->tuple)
         playlist_entry_get_info(b);
 
-    g_return_val_if_fail(a->tuple != NULL, 0);
-    g_return_val_if_fail(b->tuple != NULL, 0);
-
-    return (a->tuple->track_number && b->tuple->track_number ?
-	    a->tuple->track_number - b->tuple->track_number : 0);
+    if (a->tuple == NULL)
+        return 0;
+    if (b->tuple == NULL)
+        return 0;
+
+    tracknumber_a = tuple_get_int(a->tuple, "track-number");
+    tracknumber_b = tuple_get_int(b->tuple, "track-number");
+
+    return (tracknumber_a && tracknumber_b ?
+	    tracknumber_a - tracknumber_b : 0);
 }
 
 static gint
@@ -1986,10 +2011,10 @@
     if(!b->tuple)
         playlist_entry_get_info(b);
 
-    if (a->tuple != NULL && a->tuple->track_name != NULL)
-        a_title = a->tuple->track_name;
-    if (b->tuple != NULL && b->tuple->track_name != NULL)
-        b_title = b->tuple->track_name;
+    if (a->tuple != NULL)
+        a_title = tuple_get_string(a->tuple, "title");
+    if (b->tuple != NULL)
+        b_title = tuple_get_string(b->tuple, "title");
 
     if (a_title != NULL && b_title != NULL)
         return strcasecmp(a_title, b_title);
@@ -2030,14 +2055,14 @@
     if (b->tuple != NULL)
         playlist_entry_get_info(b);
 
-    if (a->tuple != NULL && a->tuple->performer != NULL) {
-        a_artist = a->tuple->performer;
+    if (a->tuple != NULL) {
+        a_artist = tuple_get_string(a->tuple, "artist");
         if (str_has_prefix_nocase(a_artist, "the "))
             a_artist += 4;
     }
 
-    if (b->tuple != NULL && b->tuple->performer != NULL) {
-        b_artist = b->tuple->performer;
+    if (b->tuple != NULL) {
+        b_artist = tuple_get_string(b->tuple, "artist");
         if (str_has_prefix_nocase(b_artist, "the "))
             b_artist += 4;
     }
@@ -2377,8 +2402,9 @@
     gchar *path = NULL;
     GList *node;
     PlaylistEntry *entry = NULL;
-    TitleInput *tuple = NULL;
+    Tuple *tuple = NULL;
     ProbeResult *pr = NULL;
+    time_t mtime;
 
     PLAYLIST_LOCK(playlist->mutex);
 
@@ -2391,9 +2417,14 @@
 
     PLAYLIST_UNLOCK(playlist->mutex);
 
+    if (entry->tuple)
+        mtime = tuple_get_int(entry->tuple, "mtime");
+    else
+        mtime = 0;
+
     /* No tuple? Try to set this entry up properly. --nenolod */
-    if (entry->tuple == NULL || entry->tuple->mtime == -1 ||
-        entry->tuple->mtime == 0 || entry->tuple->mtime != playlist_get_mtime(entry->filename))
+    if (entry->tuple == NULL || mtime == -1 ||
+        mtime == 0 || mtime != playlist_get_mtime(entry->filename))
     {
         playlist_entry_get_info(entry);
         tuple = entry->tuple;
@@ -2431,7 +2462,7 @@
 playlist_fileinfo_current(Playlist *playlist)
 {
     gchar *path = NULL;
-    TitleInput *tuple = NULL;
+    Tuple *tuple = NULL;
 
     PLAYLIST_LOCK(playlist->mutex);
 
@@ -2497,7 +2528,7 @@
                 entry = node->data;
 
                 if(playlist->attribute & PLAYLIST_STATIC ||
-                   (entry->tuple && entry->tuple->length > -1 && entry->tuple->mtime != -1)) {
+                   (entry->tuple && tuple_get_int(entry->tuple, "length") > -1 && tuple_get_int(entry->tuple, "mtime") != -1)) {
                     update_playlistwin = TRUE;
                     continue;
                 }
@@ -2539,7 +2570,7 @@
                  entry = node->data;
 
                  if(playlist->attribute & PLAYLIST_STATIC ||
-                    (entry->tuple && entry->tuple->length > -1 && entry->tuple->mtime != -1)) {
+                   (entry->tuple && tuple_get_int(entry->tuple, "length") > -1 && tuple_get_int(entry->tuple, "mtime") != -1)) {
                     update_playlistwin = TRUE;
                     continue;
                  }
@@ -2861,7 +2892,7 @@
 }
 
 gint
-playlist_select_search( Playlist *playlist , TitleInput *tuple , gint action )
+playlist_select_search( Playlist *playlist , Tuple *tuple , gint action )
 {
     GList *entry_list = NULL, *found_list = NULL, *sel_list = NULL;
     gboolean is_first_search = TRUE;
@@ -2875,10 +2906,10 @@
 
     PLAYLIST_LOCK(playlist->mutex);
 
-    if ( tuple->track_name != NULL )
+    if ( tuple_get_string(tuple, "title") != NULL )
     {
         /* match by track_name */
-        const gchar *regex_pattern = tuple->track_name;
+        const gchar *regex_pattern = tuple_get_string(tuple, "title");
         regex_t regex;
     #if defined(USE_REGEX_PCRE)
         if ( regcomp( &regex , regex_pattern , REG_NOSUB | REG_ICASE | REG_UTF8 ) == 0 )
@@ -2891,9 +2922,10 @@
             else entry_list = found_list; /* use found_list */
             for ( ; entry_list ; entry_list = g_list_next(entry_list) )
             {
+                const gchar *track_name = tuple_get_string( tuple, "title" );
                 PlaylistEntry *entry = entry_list->data;
-                if ( ( entry->tuple != NULL ) && ( entry->tuple->track_name != NULL ) &&
-                   ( regexec( &regex , entry->tuple->track_name , 0 , NULL , 0 ) == 0 ) )
+                if ( ( entry->tuple != NULL ) && ( track_name != NULL ) &&
+                   ( regexec( &regex , track_name , 0 , NULL , 0 ) == 0 ) )
                 {
                     tfound_list = g_list_append( tfound_list , entry );
                 }
@@ -2905,10 +2937,10 @@
         is_first_search = FALSE;
     }
 
-    if ( tuple->album_name != NULL )
+    if ( tuple_get_string(tuple, "album") != NULL )
     {
         /* match by album_name */
-        const gchar *regex_pattern = tuple->album_name;
+        const gchar *regex_pattern = tuple_get_string(tuple, "album");
         regex_t regex;
     #if defined(USE_REGEX_PCRE)
         if ( regcomp( &regex , regex_pattern , REG_NOSUB | REG_ICASE | REG_UTF8 ) == 0 )
@@ -2921,9 +2953,10 @@
             else entry_list = found_list; /* use found_list */
             for ( ; entry_list ; entry_list = g_list_next(entry_list) )
             {
+                const gchar *album_name = tuple_get_string( tuple, "album" );
                 PlaylistEntry *entry = entry_list->data;
-                if ( ( entry->tuple != NULL ) && ( entry->tuple->album_name != NULL ) &&
-                   ( regexec( &regex , entry->tuple->album_name , 0 , NULL , 0 ) == 0 ) )
+                if ( ( entry->tuple != NULL ) && ( album_name != NULL ) &&
+                   ( regexec( &regex , album_name , 0 , NULL , 0 ) == 0 ) )
                 {
                     tfound_list = g_list_append( tfound_list , entry );
                 }
@@ -2935,10 +2968,10 @@
         is_first_search = FALSE;
     }
 
-    if ( tuple->performer != NULL )
+    if ( tuple_get_string(tuple, "artist") != NULL )
     {
         /* match by performer */
-        const gchar *regex_pattern = tuple->performer;
+        const gchar *regex_pattern = tuple_get_string(tuple, "artist");
         regex_t regex;
     #if defined(USE_REGEX_PCRE)
         if ( regcomp( &regex , regex_pattern , REG_NOSUB | REG_ICASE | REG_UTF8 ) == 0 )
@@ -2951,9 +2984,10 @@
             else entry_list = found_list; /* use found_list */
             for ( ; entry_list ; entry_list = g_list_next(entry_list) )
             {
+                const gchar *performer = tuple_get_string( tuple, "performer" );
                 PlaylistEntry *entry = entry_list->data;
-                if ( ( entry->tuple != NULL ) && ( entry->tuple->performer != NULL ) &&
-                   ( regexec( &regex , entry->tuple->performer , 0 , NULL , 0 ) == 0 ) )
+                if ( ( entry->tuple != NULL ) && ( performer != NULL ) &&
+                   ( regexec( &regex , performer , 0 , NULL , 0 ) == 0 ) )
                 {
                     tfound_list = g_list_append( tfound_list , entry );
                 }
@@ -2965,10 +2999,10 @@
         is_first_search = FALSE;
     }
 
-    if ( tuple->file_name != NULL )
+    if ( tuple_get_string(tuple, "file-name") != NULL )
     {
         /* match by file_name */
-        const gchar *regex_pattern = tuple->file_name;
+        const gchar *regex_pattern = tuple_get_string(tuple, "file-name");
         regex_t regex;
     #if defined(USE_REGEX_PCRE)
         if ( regcomp( &regex , regex_pattern , REG_NOSUB | REG_ICASE | REG_UTF8 ) == 0 )
@@ -2981,9 +3015,10 @@
             else entry_list = found_list; /* use found_list */
             for ( ; entry_list ; entry_list = g_list_next(entry_list) )
             {
+                const gchar *file_name = tuple_get_string( tuple, "file-name" );
                 PlaylistEntry *entry = entry_list->data;
-                if ( ( entry->tuple != NULL ) && ( entry->tuple->file_name != NULL ) &&
-                   ( regexec( &regex , entry->tuple->file_name , 0 , NULL , 0 ) == 0 ) )
+                if ( ( entry->tuple != NULL ) && ( file_name != NULL ) &&
+                   ( regexec( &regex , file_name , 0 , NULL , 0 ) == 0 ) )
                 {
                     tfound_list = g_list_append( tfound_list , entry );
                 }
@@ -3109,7 +3144,7 @@
 
 	/* invalidate mtime to reread */
 	if (entry->tuple != NULL)
-	    entry->tuple->mtime = -1; /* -1 denotes "non-initialized". now 0 is for stream etc. yaz */
+            tuple_associate_int(entry->tuple, "mtime", -1); /* -1 denotes "non-initialized". now 0 is for stream etc. yaz */
 
         if (!playlist_entry_get_info(entry)) {
             if (g_list_index(playlist->entries, entry) == -1)
--- a/src/audacious/playlist.h	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/playlist.h	Fri Aug 10 05:22:35 2007 -0500
@@ -36,7 +36,8 @@
 
 #include <mowgli.h>
 #include <glib.h>
-#include "audacious/titlestring.h"
+#include "audacious/tuple.h"
+#include "audacious/tuple_formatter.h"
 #include "input.h"
 
 G_BEGIN_DECLS
@@ -71,7 +72,7 @@
     gint length;
     gboolean selected;
     InputPlugin *decoder;
-    TitleInput *tuple;		/* cached entry tuple, if available */
+    Tuple *tuple;		/* cached entry tuple, if available */
 } PlaylistEntry;
 
 #define PLAYLIST(x)  ((Playlist *)(x))
@@ -187,7 +188,7 @@
 
 gchar *playlist_get_filename(Playlist *playlist, guint pos);
 gchar *playlist_get_songtitle(Playlist *playlist, guint pos);
-TitleInput *playlist_get_tuple(Playlist *playlist, guint pos);
+Tuple *playlist_get_tuple(Playlist *playlist, guint pos);
 gint playlist_get_songtime(Playlist *playlist, guint pos);
 
 GList *playlist_get_selected(Playlist *playlist);
@@ -198,7 +199,7 @@
                              gboolean * total_more,
                              gboolean * selection_more);
 
-gint playlist_select_search(Playlist *playlist, TitleInput *tuple, gint action);
+gint playlist_select_search(Playlist *playlist, Tuple *tuple, gint action);
 void playlist_select_all(Playlist *playlist, gboolean set);
 void playlist_select_range(Playlist *playlist, gint min, gint max, gboolean sel);
 void playlist_select_invert_all(Playlist *playlist);
@@ -232,7 +233,7 @@
 
 extern void playlist_load_ins_file_tuple(Playlist *playlist, const gchar * filename_p,
 					 const gchar * playlist_name, gint pos,
-					 TitleInput *tuple);
+					 Tuple *tuple);
 
 Playlist *playlist_get_active(void);
 
--- a/src/audacious/plugin.h	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/plugin.h	Fri Aug 10 05:22:35 2007 -0500
@@ -36,8 +36,10 @@
 #define BMP_PLUGIN_H
 
 #include <glib.h>
+#include <gtk/gtk.h>
 #include "audacious/vfs.h"
-#include "audacious/titlestring.h"
+#include "audacious/tuple.h"
+#include "audacious/tuple_formatter.h"
 #include "audacious/eventqueue.h"
 
 #define PLUGIN(x)         ((Plugin *)(x))
@@ -252,8 +254,8 @@
     OutputPlugin *output; /* deprecated */
 
     /* Added in Audacious 1.1.0 */
-    TitleInput *(*get_song_tuple) (gchar * filename);
-    void (*set_song_tuple) (TitleInput * tuple);
+    Tuple *(*get_song_tuple) (gchar * filename);
+    void (*set_song_tuple) (Tuple * tuple);
     void (*set_status_buffering) (gboolean status);
 
     /* Added in Audacious 1.3.0 */
@@ -262,7 +264,7 @@
 
     /* Added in Audacious 1.4.0 */
     void (*mseek) (InputPlayback * playback, gulong millisecond);
-    TitleInput *(*probe_for_tuple)(gchar *uri, VFSFile *fd);
+    Tuple *(*probe_for_tuple)(gchar *uri, VFSFile *fd);
 };
 
 struct _GeneralPlugin {
--- a/src/audacious/titlestring.c	Fri Aug 10 04:04:41 2007 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,457 +0,0 @@
-/*
- * Copyright (C) 2001,  Espen Skoglund <esk@ira.uka.de>
- * Copyright (C) 2001,  Haavard Kvaalen <havardk@xmms.org>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- * 02110-1301, USA.
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#  include "config.h"
-#endif
-
-#define GETTEXT_PACKAGE PACKAGE_NAME
-
-#include <glib.h>
-#include <glib/gi18n-lib.h>
-#include <gtk/gtk.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "titlestring.h"
-
-#define CHECK(input, field) \
-	(((gchar *) &input->field - (gchar *) input) < input->__size)
-
-#define VS(input, field) (CHECK(input, field) ? input->field : NULL)
-#define VI(input, field) (CHECK(input, field) ? input->field : 0)
-
-/**
- * bmp_title_input_new:
- *
- * #BmpTitleInput tuple factory.
- *
- * Return value: A #BmpTitleInput object.
- **/
-BmpTitleInput *
-bmp_title_input_new()
-{
-    BmpTitleInput *input;
-    input = g_new0(BmpTitleInput, 1);
-    input->__size = XMMS_TITLEINPUT_SIZE;
-    input->__version = XMMS_TITLEINPUT_VERSION;
-    input->mtime = -1;
-    return input;
-}
-
-/**
- * bmp_title_input_free:
- * @input: A #BmpTitleInput tuple to destroy.
- *
- * Destroys a #BmpTitleInput tuple.
- **/
-void
-bmp_title_input_free(BmpTitleInput * input)
-{
-    if (input == NULL)
-        return;
-
-    if (input->performer != NULL)
-        g_free(input->performer);
-
-    if (input->album_name != NULL)
-        g_free(input->album_name);
-
-    if (input->track_name != NULL)
-        g_free(input->track_name);
-
-    if (input->date != NULL)
-        g_free(input->date);
-
-    if (input->genre != NULL)
-        g_free(input->genre);
-
-    if (input->comment != NULL)
-        g_free(input->comment);
-
-    if (input->file_name != NULL)
-        g_free(input->file_name);
-
-    if (input->file_path != NULL)
-        g_free(input->file_path);
-
-    if (input->custom != NULL)
-        g_free(input->custom);
-
-    g_free(input);
-}
-
-/**
- * xmms_get_titlestring:
- * @fmt: A format string.
- * @input: A tuple to use for data.
- *
- * Generates a formatted string from a tuple.
- *
- * Return value: A formatted tuple string.
- **/
-gchar *
-xmms_get_titlestring(const gchar * fmt, TitleInput * input)
-{
-    GString *outstr;
-    const gchar *string;
-    gchar c, convert[16];
-    gint numdigits, numpr, val, i;
-    gint f_left, f_space, f_zero, someflag, width, precision;
-    gboolean did_output = FALSE;
-    gchar digits[] = "0123456789";
-
-#define PUTCH(ch) g_string_append_c(outstr, ch)
-
-#define LEFTPAD(num)                            \
-    G_STMT_START {                              \
-        gint cnt = (num);                       \
-        if ( ! f_left && cnt > 0 )              \
-            while ( cnt-- > 0 )                 \
-                PUTCH(f_zero ? '0' : ' ');      \
-    } G_STMT_END;
-
-#define RIGHTPAD(num)                           \
-    G_STMT_START {                              \
-        gint cnt = (num);                       \
-        if ( f_left && cnt > 0 )                \
-            while ( cnt-- > 0 )                 \
-                PUTCH( ' ' );                   \
-    } G_STMT_END;
-
-    if (fmt == NULL || input == NULL)
-        return NULL;
-    outstr = g_string_new("");
-
-    for (;;) {
-        /* Copy characters until we encounter '%'. */
-        while ((c = *fmt++) != '%') {
-            if (c == '\0')
-                goto Done;
-            g_string_append_c(outstr, c);
-        }
-
-        f_left = f_space = f_zero = 0;
-        someflag = 1;
-
-
-        /* Parse flags. */
-        while (someflag) {
-            switch (*fmt) {
-            case '-':
-                f_left = 1;
-                fmt++;
-                break;
-            case ' ':
-                f_space = 1;
-                fmt++;
-                break;
-            case '0':
-                f_zero = 1;
-                fmt++;
-                break;
-            default:
-                someflag = 0;
-                break;
-            }
-        }
-
-
-        /* Parse field width. */
-        if ((c = *fmt) >= '0' && c <= '9') {
-            width = 0;
-            while ((c = *fmt++) >= '0' && c <= '9') {
-                width *= 10;
-                width += c - '0';
-            }
-            fmt--;
-        }
-        else
-            width = -1;
-
-
-        /* Parse precision. */
-        if (*fmt == '.') {
-            if ((c = *++fmt) >= '0' && c <= '9') {
-                precision = 0;
-                while ((c = *fmt++) >= '0' && c <= '9') {
-                    precision *= 10;
-                    precision += c - '0';
-                }
-                fmt--;
-            }
-            else
-                precision = -1;
-        }
-        else
-            precision = -1;
-
-
-        /* Parse format conversion. */
-        switch (c = *fmt++) {
-        case '}':              /* close optional, just ignore */
-            continue;
-
-        case '{':{             /* optional entry: %{n:...%} */
-                char n = *fmt++;
-                if (!((n == 'a' && VS(input, album_name)) ||
-                      (n == 'c' && VS(input, comment)) ||
-                      (n == 'C' && VS(input, custom)) ||
-                      (n == 'd' && VS(input, date)) ||
-                      (n == 'e' && VS(input, file_ext)) ||
-                      (n == 'f' && VS(input, file_name)) ||
-                      (n == 'F' && VS(input, file_path)) ||
-                      (n == 'g' && VS(input, genre)) ||
-                      (n == 'n' && VI(input, track_number)) ||
-                      (n == 'p' && VS(input, performer)) ||
-                      (n == 't' && VS(input, track_name)) ||
-                      (n == 'y' && VI(input, year)))) {
-                    int nl = 0;
-                    char c;
-                    while ((c = *fmt++))    /* until end of string      */
-                        if (c == '}')   /* if end of opt            */
-                            if (!nl)
-                                break;  /* if outmost indent level  */
-                            else
-                                --nl;   /* else reduce indent       */
-                        else if (c == '{')
-                            ++nl;   /* increase indent          */
-                }
-                else
-                    ++fmt;
-                break;
-            }
-
-        case 'a':
-            string = VS(input, album_name);
-            goto Print_string;
-        case 'c':
-            string = VS(input, comment);
-            goto Print_string;
-        case 'C':
-            string = VS(input, custom);
-            goto Print_string;
-        case 'd':
-            string = VS(input, date);
-            goto Print_string;
-        case 'e':
-            string = VS(input, file_ext);
-            goto Print_string;
-        case 'f':
-            string = VS(input, file_name);
-            goto Print_string;
-        case 'F':
-            string = VS(input, file_path);
-            goto Print_string;
-        case 'g':
-            string = VS(input, genre);
-            goto Print_string;
-        case 'n':
-            val = VI(input, track_number);
-            goto Print_number;
-        case 'p':
-            string = VS(input, performer);
-            goto Print_string;
-        case 't':
-            string = VS(input, track_name);
-	    goto Print_string;
-        case 'y':
-            val = VI(input, year);
-	    goto Print_number;
-
-          Print_string:
-            if (string == NULL)
-                break;
-            did_output = TRUE;
-
-            numpr = 0;
-            if (width > 0) {
-                /* Calculate printed size. */
-                numpr = strlen(string);
-                if (precision >= 0 && precision < numpr)
-                    numpr = precision;
-
-                LEFTPAD(width - numpr);
-            }
-
-            /* Insert string. */
-            if (precision >= 0) {
-                glong offset_max = precision, offset;
-                gchar *uptr = NULL;
-                const gchar *tmpstring = string;
-                while (precision > 0) {
-                    offset = offset_max - precision;
-                    uptr = g_utf8_offset_to_pointer(tmpstring, offset);
-                    if (*uptr == '\0')
-                        break;
-                    g_string_append_unichar(outstr, g_utf8_get_char(uptr));
-                    precision--;
-                }
-            }
-            else {
-                while ((c = *string++) != '\0')
-                    PUTCH(c);
-            }
-
-            RIGHTPAD(width - numpr);
-            break;
-
-          Print_number:
-            if (val == 0)
-                break;
-            if (c != 'N')
-                did_output = TRUE;
-
-            /* Create reversed number string. */
-            numdigits = 0;
-            do {
-                convert[numdigits++] = digits[val % 10];
-                val /= 10;
-            }
-            while (val > 0);
-
-            numpr = numdigits > precision ? numdigits : precision;
-
-            /* Insert left padding. */
-            if (!f_left && width > numpr) {
-                if (f_zero)
-                    numpr = width;
-                else
-                    for (i = width - numpr; i-- > 0;)
-                        PUTCH(' ');
-            }
-
-            /* Insert zero padding. */
-            for (i = numpr - numdigits; i-- > 0;)
-                PUTCH('0');
-
-            /* Insert number. */
-            while (numdigits > 0)
-                PUTCH(convert[--numdigits]);
-
-            RIGHTPAD(width - numpr);
-            break;
-
-        case '%':
-            PUTCH('%');
-            break;
-
-        default:
-            PUTCH('%');
-            PUTCH(c);
-            break;
-        }
-    }
-
-  Done:
-    if (did_output)
-        return g_string_free(outstr, FALSE);
-    else
-        return NULL;
-}
-
-struct _TagDescription {
-    gchar tag;
-    gchar *description;
-};
-
-typedef struct _TagDescription TagDescription;
-
-static TagDescription tag_descriptions[] = {
-    {'p', N_("Performer/Artist")},
-    {'a', N_("Album")},
-    {'g', N_("Genre")},
-    {'f', N_("File name")},
-    {'F', N_("File path")},
-    {'e', N_("File extension")},
-    {'t', N_("Track name")},
-    {'n', N_("Track number")},
-    {'d', N_("Date")},
-    {'y', N_("Year")},
-    {'c', N_("Comment")}
-};
-
-gint tag_descriptions_length =
-    sizeof(tag_descriptions) / sizeof(TagDescription);
-
-/**
- * xmms_titlestring_descriptions:
- * @tags: A list of formatters to provide.
- * @columns: A number of columns to arrange them in.
- *
- * Generates a box explaining how to use the formatters.
- *
- * Return value: A GtkWidget containing the table.
- **/
-GtkWidget *
-xmms_titlestring_descriptions(gchar * tags, gint columns)
-{
-    GtkWidget *table, *label;
-    gchar tag_str[5];
-    gint num = strlen(tags);
-    gint r = 0, c, i;
-
-    g_return_val_if_fail(tags != NULL, NULL);
-    g_return_val_if_fail(columns <= num, NULL);
-
-    table = gtk_table_new((num + columns - 1) / columns, columns * 2, FALSE);
-    gtk_table_set_row_spacings(GTK_TABLE(table), 2);
-    gtk_table_set_col_spacings(GTK_TABLE(table), 5);
-
-    for (c = 0; c < columns; c++) {
-        for (r = 0; r < (num + columns - 1 - c) / columns; r++) {
-            g_snprintf(tag_str, sizeof(tag_str), "%%%c:", *tags);
-            label = gtk_label_new(tag_str);
-            gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
-            gtk_table_attach(GTK_TABLE(table), label, 2 * c, 2 * c + 1, r,
-                             r + 1, GTK_FILL, GTK_FILL, 0, 0);
-            gtk_widget_show(label);
-
-            for (i = 0; i < tag_descriptions_length; i++) {
-                if (*tags == tag_descriptions[i].tag) {
-                    label = gtk_label_new(_(tag_descriptions[i].description));
-                    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
-                    gtk_table_attach(GTK_TABLE(table), label, 2 * c + 1,
-                                     2 * c + 2, r, r + 1,
-                                     GTK_EXPAND | GTK_FILL,
-                                     GTK_EXPAND | GTK_FILL, 0, 0);
-                    gtk_widget_show(label);
-                    break;
-                }
-            }
-
-            if (i == tag_descriptions_length)
-                g_warning("Invalid tag: %c", *tags);
-
-            tags++;
-        }
-
-    }
-
-    label = gtk_label_new(_("%{n:...%}: Display \"...\" only if element "
-                            "%n is present"));
-    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
-    gtk_table_attach(GTK_TABLE(table), label, 0, r + 1,
-                     r + 1, r + 2, GTK_FILL, GTK_FILL, 0, 0);
-    gtk_widget_show(label);
-
-    return table;
-}
--- a/src/audacious/titlestring.h	Fri Aug 10 04:04:41 2007 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2001,  Espen Skoglund <esk@ira.uka.de>
- *                
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- * 02110-1301, USA.
- *                
- */
-#ifndef XMMS_TITLESTRING_H
-#define XMMS_TITLESTRING_H
-
-#include <glib.h>
-#include <gtk/gtk.h>
-#include <unistd.h>
-#include <time.h>
-
-/**
- * TitleInput:
- * @__size: Private field which describes the version of the TitleInput.
- * @__version: Private field which describes the version of the TitleInput.
- * @performer: The performer of the media that the tuple is describing.
- * @album_name: The name of the album that contains the media.
- * @track_name: The title of the media.
- * @track_number: The track number of the media.
- * @year: The year the media was published.
- * @date: The date the media was published.
- * @genre: The genre of the media.
- * @comment: Any comments attached to the media.
- * @file_name: The filename which refers to the media.
- * @file_ext: The file's extension.
- * @file_path: The path that the media is in.
- * @length: The length of the media.
- * @formatter: The format string that should be used.
- * @custom: A custom field for miscellaneous information.
- * @mtime: The last modified time of the file.
- *
- * Tuple which is passed to xmms_get_titlestring().  An input tuple
- * is allocated and initialized with bmp_title_input_new().  Before
- * passing the struct to xmms_get_titlestring() it should be filled
- * with appropriate field values.
- **/
-typedef struct {
-    gint __size;                /* Set by bmp_title_input_new() */
-    gint __version;             /* Ditto */
-
-    gchar *performer;           /* %p */
-    gchar *album_name;          /* %a */
-    gchar *track_name;          /* %t */
-    gint track_number;          /* %n */
-    gint year;                  /* %y */
-    gchar *date;                /* %d */
-    gchar *genre;               /* %g */
-    gchar *comment;             /* %c */
-    gchar *file_name;           /* %f */
-    const gchar *file_ext;      /* %e *//* is not always strdup'ed, see xmms_input_get_song_info and plugins! */
-    gchar *file_path;           /* %F */
-    gint length;                /* not displayable */
-    gchar *formatter;           /* not displayable */
-    gchar *custom;              /* not displayable, for internal use */
-    time_t mtime;
-} TitleInput;
-
-/**
- * BmpTitleInput:
- *
- * An alternate name for the #TitleInput object.
- **/
-typedef TitleInput BmpTitleInput;
-
-
-/*
- * Using a __size field helps the library functions detect plugins
- * that use a possibly extended version of the struct.  The __version
- * field helps the library detect possible future incompatibilities in
- * the struct layout.
- */
-
-/**
- * XMMS_TITLEINPUT_SIZE:
- *
- * The size of the TitleInput object compiled into the library.
- **/
-#define XMMS_TITLEINPUT_SIZE	sizeof(TitleInput)
-
-/**
- * XMMS_TITLEINPUT_VERSION:
- *
- * The version of the TitleInput object compiled into the library.
- **/
-#define XMMS_TITLEINPUT_VERSION	(1)
-
-/**
- * XMMS_NEW_TITLEINPUT:
- * @input: A TitleInput to initialize.
- *
- * Initializes a TitleInput object. Included for XMMS compatibility.
- **/
-#define XMMS_NEW_TITLEINPUT(input) input = bmp_title_input_new();
-
-
-G_BEGIN_DECLS
-
-TitleInput *bmp_title_input_new(void);
-void bmp_title_input_free(BmpTitleInput * input);
-
-gchar *xmms_get_titlestring(const gchar * fmt, TitleInput * input);
-GtkWidget *xmms_titlestring_descriptions(gchar * tags, gint columns);
-
-G_END_DECLS
-
-#endif                          /* !XMMS_TITLESTRING_H */
--- a/src/audacious/ui_albumart.c	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/ui_albumart.c	Fri Aug 10 05:22:35 2007 -0500
@@ -28,7 +28,6 @@
 #include <gtk/gtk.h>
 #include <string.h>
 
-#include "titlestring.h"
 #include "ui_fileinfopopup.h"
 #include "main.h"
 #include "ui_main.h"
--- a/src/audacious/ui_fileinfo.c	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/ui_fileinfo.c	Fri Aug 10 05:22:35 2007 -0500
@@ -51,7 +51,7 @@
 #include "main.h"
 #include "util.h"
 #include "dnd.h"
-#include "titlestring.h"
+#include "tuple.h"
 
 #include "playlist.h"
 
@@ -171,7 +171,7 @@
 }
 
 void
-fileinfo_show_for_tuple(TitleInput *tuple)
+fileinfo_show_for_tuple(Tuple *tuple)
 {
 	gchar *tmp = NULL;
 
@@ -180,31 +180,26 @@
 
 	gtk_widget_realize(fileinfo_win);
 
-	if (tuple->track_name)
-		fileinfo_entry_set_text("entry_title", tuple->track_name);
-	if (tuple->performer)
-		fileinfo_entry_set_text("entry_artist", tuple->performer);
-	if (tuple->album_name)
-		fileinfo_entry_set_text("entry_album", tuple->album_name);
-	if (tuple->comment)
-		fileinfo_entry_set_text("entry_comment", tuple->comment);
-	if (tuple->genre)
-		fileinfo_entry_set_text("entry_genre", tuple->genre);
+	fileinfo_entry_set_text("entry_title", tuple_get_string(tuple, "title"));
+	fileinfo_entry_set_text("entry_artist", tuple_get_string(tuple, "artist"));
+	fileinfo_entry_set_text("entry_album", tuple_get_string(tuple, "album"));
+	fileinfo_entry_set_text("entry_comment", tuple_get_string(tuple, "comment"));
+	fileinfo_entry_set_text("entry_genre", tuple_get_string(tuple, "genre"));
 
-	tmp = g_strdup_printf("%s/%s", tuple->file_path, tuple->file_name);
+	tmp = g_strdup_printf("%s/%s", tuple_get_string(tuple, "file-path"), tuple_get_string(tuple, "file-name"));
 	if(tmp){
 		fileinfo_entry_set_text_free("entry_location", str_to_utf8(tmp));
 		g_free(tmp);
 		tmp = NULL;
 	}
 
-	if (tuple->year != 0)
-		fileinfo_entry_set_text_free("entry_year", g_strdup_printf("%d", tuple->year));
+	if (tuple_get_int(tuple, "year"))
+		fileinfo_entry_set_text_free("entry_year", g_strdup_printf("%d", tuple_get_int(tuple, "year")));
 
-	if (tuple->track_number != 0)
-		fileinfo_entry_set_text_free("entry_track", g_strdup_printf("%d", tuple->track_number));
+	if (tuple_get_int(tuple, "track-number"))
+		fileinfo_entry_set_text_free("entry_track", g_strdup_printf("%d", tuple_get_int(tuple, "track-number")));
 
-	tmp = fileinfo_recursive_get_image(tuple->file_path, tuple->file_name, 0);
+	tmp = fileinfo_recursive_get_image(tuple_get_string(tuple, "file-path"), tuple_get_string(tuple, "file-name"), 0);
 	
 	if(tmp)
 	{
@@ -218,13 +213,12 @@
 void
 fileinfo_show_for_path(gchar *path)
 {
-	TitleInput *tuple = input_get_song_tuple(path);
+	Tuple *tuple = input_get_song_tuple(path);
 
 	if (tuple == NULL)
 		return input_file_info_box(path);
 
 	fileinfo_show_for_tuple(tuple);
 
-	bmp_title_input_free(tuple);
-	tuple = NULL;
+	mowgli_object_unref(tuple);
 }
--- a/src/audacious/ui_fileinfo.h	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/ui_fileinfo.h	Fri Aug 10 05:22:35 2007 -0500
@@ -19,11 +19,11 @@
 #ifndef _UI_FILEINFO_H_
 #define _UI_FILEINFO_H_
 
-#include "titlestring.h"
+#include "tuple.h"
 #include <glib.h>
 
 void create_fileinfo_window(void);
-void fileinfo_show_for_tuple(TitleInput *tuple);
+void fileinfo_show_for_tuple(Tuple *tuple);
 gchar* fileinfo_recursive_get_image(const gchar* path, const gchar* file_name, gint depth);
 void fileinfo_show_for_path(gchar *path);
 
--- a/src/audacious/ui_fileinfopopup.c	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/ui_fileinfopopup.c	Fri Aug 10 05:22:35 2007 -0500
@@ -32,7 +32,6 @@
 #include "playlist.h"
 #include "playback.h"
 #include "strings.h"
-#include "titlestring.h"
 #include "ui_fileinfopopup.h"
 #include "ui_fileinfo.h"
 
@@ -330,13 +329,14 @@
 
 void
 audacious_fileinfopopup_show_from_tuple(GtkWidget *filepopup_win,
-                                        TitleInput *tuple)
+                                        Tuple *tuple)
 {
     gchar *tmp = NULL;
     gint x, y, x_off = 3, y_off = 3, h, w;
     gchar *length_string, *year_string, *track_string;
     gchar *last_artwork;
     const static gchar default_artwork[] = DATA_DIR "/images/audio.png";
+    gint length;
 
     last_artwork =
         g_object_get_data(G_OBJECT(filepopup_win), "last_artwork");
@@ -349,64 +349,66 @@
         tmp = NULL;
         g_object_set_data(G_OBJECT(filepopup_win), "file", NULL);
     }
-    if (tuple->file_path && tuple->file_name)
+    if (tuple_get_string(tuple, "file-path") && tuple_get_string(tuple, "file-name"))
         g_object_set_data(G_OBJECT(filepopup_win), "file",
-                          g_build_filename(tuple->file_path, tuple->file_name,
+                          g_build_filename(tuple_get_string(tuple, "file-path"), 
+                                           tuple_get_string(tuple, "file-name"),
                                            NULL));
 
     gtk_widget_realize(filepopup_win);
 
-    if (tuple->track_name != NULL)
+    if (tuple_get_string(tuple, "title"))
     {
         gchar *markup =
             g_markup_printf_escaped("<span style=\"italic\">%s</span>", _("Title"));
         gtk_label_set_markup(GTK_LABEL(g_object_get_data(G_OBJECT(filepopup_win), "header_title")), markup);
         g_free(markup);
-        filepopup_entry_set_text(filepopup_win, "label_title", tuple->track_name);
+        filepopup_entry_set_text(filepopup_win, "label_title", tuple_get_string(tuple, "title"));
     }
     else
     {
         /* display filename if track_name is not available */
         gchar *markup =
             g_markup_printf_escaped("<span style=\"italic\">%s</span>", _("Filename"));
-        gchar *utf_filename = filename_to_utf8(tuple->file_name);
+        gchar *utf_filename = filename_to_utf8(tuple_get_string(tuple, "file-name"));
         gtk_label_set_markup(GTK_LABEL(g_object_get_data(G_OBJECT(filepopup_win), "header_title")), markup);
         g_free(markup);
         filepopup_entry_set_text(filepopup_win, "label_title", utf_filename);
         g_free(utf_filename);
     }
 
-    audacious_fileinfopupup_update_data(filepopup_win, tuple->performer,
+    audacious_fileinfopupup_update_data(filepopup_win, tuple_get_string(tuple, "artist"),
                                         "label_artist", "header_artist");
-    audacious_fileinfopupup_update_data(filepopup_win, tuple->album_name,
+    audacious_fileinfopupup_update_data(filepopup_win, tuple_get_string(tuple, "album"),
                                         "label_album", "header_album");
-    audacious_fileinfopupup_update_data(filepopup_win, tuple->genre,
+    audacious_fileinfopupup_update_data(filepopup_win, tuple_get_string(tuple, "genre"),
                                         "label_genre", "header_genre");
 
-    length_string = (tuple->length > 0) ?
-        g_strdup_printf("%d:%02d", tuple->length / 60000, (tuple->length / 1000) % 60) : NULL;
+    length = tuple_get_int(tuple, "length");
+    length_string = (length > 0) ?
+        g_strdup_printf("%d:%02d", length / 60000, (length / 1000) % 60) : NULL;
     audacious_fileinfopupup_update_data(filepopup_win, length_string,
                                         "label_tracklen", "header_tracklen");
     g_free(length_string);
 
-    if ( tuple->length > 0 )
-      g_object_set_data( G_OBJECT(filepopup_win), "length" , GINT_TO_POINTER(tuple->length) );
+    if ( length > 0 )
+      g_object_set_data( G_OBJECT(filepopup_win), "length" , GINT_TO_POINTER(length) );
     else
       g_object_set_data( G_OBJECT(filepopup_win), "length" , GINT_TO_POINTER(-1) );
 
-    year_string = (tuple->year == 0) ? NULL : g_strdup_printf("%d", tuple->year);
+    year_string = (tuple_get_int(tuple, "year") == 0) ? NULL : g_strdup_printf("%d", tuple_get_int(tuple, "year"));
     audacious_fileinfopupup_update_data(filepopup_win, year_string,
                                         "label_year", "header_year");
     g_free(year_string);
 
-    track_string = (tuple->track_number == 0) ? NULL : g_strdup_printf("%d", tuple->track_number);
+    track_string = (tuple_get_int(tuple, "track-number") == 0) ? NULL : g_strdup_printf("%d", tuple_get_int(tuple, "track-number"));
     audacious_fileinfopupup_update_data(filepopup_win, track_string,
                                         "label_tracknum", "header_tracknum");
     g_free(track_string);
     
-    if (tuple->file_path && tuple->file_name)
+    if (tuple_get_string(tuple, "file-name") && tuple_get_string(tuple, "file-path"))
     {
-        tmp = fileinfo_recursive_get_image(tuple->file_path, tuple->file_name, 0);
+        tmp = fileinfo_recursive_get_image(tuple_get_string(tuple, "file-path"), tuple_get_string(tuple, "file-name"), 0);
         if (tmp) { // picture found
             if (!last_artwork || strcmp(last_artwork, tmp)) { // new picture
                 filepopup_entry_set_image(filepopup_win, "image_artwork", tmp);
@@ -450,10 +452,10 @@
 void
 audacious_fileinfopopup_show_from_title(GtkWidget *filepopup_win, gchar *title)
 {
-    TitleInput * tuple = bmp_title_input_new();
-    tuple->track_name = g_strdup(title);
+    Tuple * tuple = tuple_new();
+    tuple_associate_string(tuple, "title", title);
     audacious_fileinfopopup_show_from_tuple(filepopup_win, tuple);
-    bmp_title_input_free(tuple);
+    mowgli_object_unref(tuple);
     return;
 }
 
--- a/src/audacious/ui_fileinfopopup.h	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/ui_fileinfopopup.h	Fri Aug 10 05:22:35 2007 -0500
@@ -21,7 +21,7 @@
 #ifndef _UI_FILEINFOPOPUP_H_
 #define _UI_FILEINFOPOPUP_H_
 
-#include "audacious/titlestring.h"
+#include "audacious/tuple.h"
 #include <gtk/gtk.h>
 
 /* create/destroy */
@@ -29,7 +29,7 @@
 void audacious_fileinfopopup_destroy(GtkWidget* fileinfopopup_win);
 
 /* show/hide */
-void audacious_fileinfopopup_show_from_tuple(GtkWidget *fileinfopopup_win, TitleInput *tuple);
+void audacious_fileinfopopup_show_from_tuple(GtkWidget *fileinfopopup_win, Tuple *tuple);
 void audacious_fileinfopopup_show_from_title(GtkWidget *fileinfopopup_win, gchar *title);
 void audacious_fileinfopopup_hide(GtkWidget *filepopup_win, gpointer unused);
 
--- a/src/audacious/ui_lastfm.c	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/ui_lastfm.c	Fri Aug 10 05:22:35 2007 -0500
@@ -18,7 +18,6 @@
 
 last_fm *login_data=NULL;
 Playlist *current_playlist=NULL;
-TitleInput *info = NULL;
 GtkWidget *lastfm_url_entry, *lastfm_label,*artist_label,*title_label,*album_label, *gui_window=NULL;
 
 gpointer open_url(gpointer url)
--- a/src/audacious/ui_playlist.c	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/ui_playlist.c	Fri Aug 10 05:22:35 2007 -0500
@@ -570,27 +570,23 @@
       {
          gint matched_entries_num = 0;
          /* create a TitleInput tuple with user search data */
-         TitleInput *tuple = g_malloc(sizeof(TitleInput));
+         Tuple *tuple = tuple_new();
          gchar *searchdata = NULL;
          searchdata = (gchar*)gtk_entry_get_text( GTK_ENTRY(searchdlg_entry_title) );
-         tuple->track_name = ( strcmp(searchdata,"") ) ? g_strdup(searchdata) : NULL;
+         tuple_associate_string(tuple, "title", searchdata);
          searchdata = (gchar*)gtk_entry_get_text( GTK_ENTRY(searchdlg_entry_album) );
-         tuple->album_name = ( strcmp(searchdata,"") ) ? g_strdup(searchdata) : NULL;
+         tuple_associate_string(tuple, "album", searchdata);
          searchdata = (gchar*)gtk_entry_get_text( GTK_ENTRY(searchdlg_entry_performer) );
-         tuple->performer = ( strcmp(searchdata,"") ) ? g_strdup(searchdata) : NULL;
+         tuple_associate_string(tuple, "artist", searchdata);
          searchdata = (gchar*)gtk_entry_get_text( GTK_ENTRY(searchdlg_entry_file_name) );
-         tuple->file_name = ( strcmp(searchdata,"") ) ? g_strdup(searchdata) : NULL;
+         tuple_associate_string(tuple, "file-name", searchdata);
          /* check if previous selection should be cleared before searching */
          if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(searchdlg_checkbt_clearprevsel)) == TRUE )
              playlistwin_select_none();
          /* now send this tuple to the real search function */
          matched_entries_num = playlist_select_search( playlist , tuple , 0 );
          /* we do not need the tuple and its data anymore */
-         if ( tuple->track_name != NULL ) g_free( tuple->track_name );
-         if ( tuple->album_name != NULL )  g_free( tuple->album_name );
-         if ( tuple->performer != NULL ) g_free( tuple->performer );
-         if ( tuple->file_name != NULL ) g_free( tuple->file_name );
-         g_free( tuple );
+         mowgli_object_unref(tuple);
          playlistwin_update_list(playlist_get_active());
          /* check if a new playlist should be created after searching */
          if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(searchdlg_checkbt_newplaylist)) == TRUE )
--- a/src/audacious/ui_skinned_playlist.c	Fri Aug 10 04:04:41 2007 -0500
+++ b/src/audacious/ui_skinned_playlist.c	Fri Aug 10 05:22:35 2007 -0500
@@ -1013,12 +1013,12 @@
     gint pos = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "popup_position"));
 
     if (GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "timer_active")) == 1 && pos != -1) {
-        TitleInput *tuple;
+        Tuple *tuple;
         Playlist *pl_active = playlist_get_active();
         GtkWidget *popup = g_object_get_data(G_OBJECT(widget), "popup");
 
         tuple = playlist_get_tuple(pl_active, pos);
-        if ((tuple == NULL) || (tuple->length < 1)) {
+        if ((tuple == NULL) || (tuple_get_int(tuple, "length") < 1)) {
            gchar *title = playlist_get_songtitle(pl_active, pos);
            audacious_fileinfopopup_show_from_title(popup, title);
            g_free(title);