r2752 - in trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2: . images src

abraxa at sita.openmoko.org abraxa at sita.openmoko.org
Tue Aug 21 09:41:22 CEST 2007


Author: abraxa
Date: 2007-08-21 09:41:07 +0200 (Tue, 21 Aug 2007)
New Revision: 2752

Added:
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/ico-tracktype-general.png
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/editor_page.c
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/editor_page.h
Modified:
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/TODO
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/Makefile.am
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/Makefile.am
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/files_page.c
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/files_page.h
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/guitools.c
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main.c
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main.h
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main_page.c
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main_page.h
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/persistent.c
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/persistent.h
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playback.c
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playback.h
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist.c
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist.h
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist_page.c
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist_page.h
Log:
Added metadata processing and caching
Added playlist chooser
Added volume slider  
Enhanced playlist handling
Prepared GUI and codebase for playlist editing
Various bugfixes



Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/TODO
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/TODO	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/TODO	2007-08-21 07:41:07 UTC (rev 2752)
@@ -3,18 +3,19 @@
 	Add repeat mode indicator (different button pixmaps)
 	Make FFWD/REW buttons trigger repeatedly
 	Make Prev/Next buttons sensitive depending on playlist state
+	m3u import
 	
 Backend:
 	Use GConf
 	Use GST_MESSAGE_DURATION and use saved value
-	Use GST_MESSAGE_TAG
 	Use GST_MESSAGE_BUFFERING
+	Check for unicode compliance
 	
 Issues:
 	How/where to store playlists? -> Single common path, omitting path chooser
-	How to adjust volume/panning?
 	Use EQ presets only or allow individual band adjustment?
 	How to adjust equalizer?
+	Reduce call frequency of omp_playback_get_track_length()
 
 Build process:
 	Bitbake recipe and its integration into OE

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/Makefile.am
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/Makefile.am	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/Makefile.am	2007-08-21 07:41:07 UTC (rev 2752)
@@ -9,6 +9,7 @@
 	ico-repeat.png \
 	ico-shuffle.png \
 	ico-list.png \
+	ico-tracktype-general.png \
 	ind-music-eq-01.png \
 	ind-music-eq-02.png \
 	ind-music-eq-03.png \
@@ -38,6 +39,6 @@
 	ind-music-volume-07.png \
 	ind-music-volume-08.png \
 	ind-music-volume-09.png \
-	ind-music-volume-10.png  
+	ind-music-volume-10.png
 
 EXTRA_DIST = $(images_DATA)

Added: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/ico-tracktype-general.png
===================================================================
(Binary files differ)


Property changes on: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/ico-tracktype-general.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/Makefile.am
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/Makefile.am	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/Makefile.am	2007-08-21 07:41:07 UTC (rev 2752)
@@ -30,4 +30,5 @@
 	persistent.c persistent.h \
 	main_page.c main_page.h \
 	playlist_page.c playlist_page.h \
+	editor_page.c editor_page.h \
 	files_page.c files_page.h

Added: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/editor_page.c
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/editor_page.c	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/editor_page.c	2007-08-21 07:41:07 UTC (rev 2752)
@@ -0,0 +1,276 @@
+/*
+ *  OpenMoko Media Player
+ *   http://openmoko.org/
+ *
+ *  Copyright (C) 2007 by the OpenMoko team
+ *
+ *  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.
+ */
+
+/**
+ * @file editor_page.c
+ * Playlist editor, part 1: main editor
+ */
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <libmokoui2/moko-finger-scroll.h>
+
+#include "editor_page.h"
+#include "playlist.h"
+#include "guitools.h"
+
+/// Enumeration for the track list columns
+enum
+{
+	TYPE_COLUMN,
+	NUMBER_COLUMN,
+	TITLE_COLUMN,
+	DURATION_COLUMN,
+	COLUMN_COUNT
+};
+
+/// List store for the playlist selector
+GtkListStore *omp_editor_page_list_store = NULL;
+
+/// The label showing the editor's title caption
+GtkWidget *omp_editor_title_label = NULL;
+
+// Forward declarations for internal use
+void omp_editor_page_list_populate();
+
+
+
+/**
+ * Gets called when a new playlist was loaded - we then update the editor window accordingly
+ */
+void
+omp_editor_page_playlist_loaded(gpointer instance, gchar *title, gpointer user_data)
+{
+	gchar *text;
+
+	text = g_strdup_printf(_(OMP_WIDGET_CAPTION_EDITOR), title);
+	gtk_label_set_text(GTK_LABEL(omp_editor_title_label), text);
+	g_free(text);
+
+	omp_editor_page_list_populate();
+}
+
+/**
+ * Updates a track's title and duration upon arrival of metadata
+ */
+void
+omp_editor_page_update_track_info(gpointer instance, guint track_id, gpointer user_data)
+{
+	guint duration;
+	gchar *track_title, *track_duration, *path;
+	GtkTreeIter tree_iter;
+
+	omp_playlist_get_track_info(track_id, &track_title, &duration);
+
+	if (duration > 0)
+	{
+		track_duration = g_strdup_printf(OMP_WIDGET_CAPTION_EDITOR_TRACK_TIME,
+			duration / 60000, (duration/1000) % 60);
+	} else {
+		track_duration = NULL;
+	}
+
+	path = g_strdup_printf("%d", track_id);
+	gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(omp_editor_page_list_store),
+		&tree_iter, path);
+
+	gtk_list_store_set(omp_editor_page_list_store, &tree_iter,
+		TITLE_COLUMN, track_title,
+		DURATION_COLUMN, track_duration, -1);
+
+	g_free(path);
+	g_free(track_duration);
+}
+
+/**
+ * Callback for the "add track" button
+ */
+void
+omp_editor_page_add_clicked(gpointer instance, gpointer user_data)
+{
+	
+}
+
+/**
+ * Monitors general click events on the list view and acts appropriately
+ */
+gboolean
+omp_editor_page_list_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
+{
+	
+}
+
+/**
+ * Fills the data model with track titles from the playlist
+ */
+void
+omp_editor_page_list_populate()
+{
+	GtkTreeIter tree_iter;
+	guint track_num, duration;
+	gchar *track_title, *track_duration;
+	struct playlist_iter *pl_iter;
+
+	gtk_list_store_clear(omp_editor_page_list_store);
+
+	pl_iter = omp_playlist_init_iterator();
+
+	// Iterate over the playlist and gather track infos to fill the list with
+	while (!omp_playlist_iter_finished(pl_iter))
+	{
+		omp_playlist_get_track_from_iter(pl_iter, &track_num, &track_title, &duration);
+
+		if (duration > 0)
+		{
+			track_duration = g_strdup_printf(OMP_WIDGET_CAPTION_EDITOR_TRACK_TIME,
+				duration / 60000, (duration/1000) % 60);
+		} else {
+			track_duration = g_strdup("");
+		}
+
+		gtk_list_store_append(omp_editor_page_list_store, &tree_iter);
+		gtk_list_store_set(omp_editor_page_list_store, &tree_iter,
+			NUMBER_COLUMN, track_num+1,
+			TITLE_COLUMN, track_title,
+			DURATION_COLUMN, track_duration, -1);
+
+		g_free(track_duration);
+		g_free(track_title);
+
+		omp_playlist_advance_iter(pl_iter);
+	}
+}
+
+/**
+ * Creates the track view
+ * @param container Destination container of the view
+ */
+void
+omp_editor_page_list_create(GtkContainer *container)
+{
+	GtkWidget *tree_view;
+	GtkTreeSelection *select;
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *column;
+	GdkPixbuf *track_icon;
+
+	track_icon = pixbuf_new_from_file("ico-tracktype-general.png");
+
+	// Create data model
+	omp_editor_page_list_store = gtk_list_store_new(COLUMN_COUNT,
+		G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING);
+
+	// Create data view
+	tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(omp_editor_page_list_store));
+	g_object_unref(G_OBJECT(omp_editor_page_list_store));
+
+	g_signal_connect(G_OBJECT(tree_view), "button-press-event",
+		G_CALLBACK(omp_editor_page_list_clicked), NULL);
+
+	// Configure selection handler
+	select = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
+	gtk_tree_selection_set_mode(GTK_TREE_SELECTION(select), GTK_SELECTION_SINGLE);
+
+	// Set up columns
+	renderer = gtk_cell_renderer_pixbuf_new();
+	g_object_set(G_OBJECT(renderer), "pixbuf", track_icon, NULL);
+	column = gtk_tree_view_column_new_with_attributes("", renderer, NULL);
+	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
+	gtk_tree_view_column_set_fixed_width(column, BUTTON_PIXMAP_SIZE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
+
+	renderer = gtk_cell_renderer_text_new();
+	column = gtk_tree_view_column_new_with_attributes(_("#"), renderer,
+		"text", NUMBER_COLUMN, NULL);
+	gtk_tree_view_column_set_fixed_width(column, BUTTON_PIXMAP_SIZE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
+
+	renderer = gtk_cell_renderer_text_new();
+	column = gtk_tree_view_column_new_with_attributes(_("Track Title"), renderer,
+		"text", TITLE_COLUMN, NULL);
+	gtk_tree_view_column_set_expand(column, TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
+
+	renderer = gtk_cell_renderer_text_new();
+	column = gtk_tree_view_column_new_with_attributes(_("Duration"), renderer,
+		"text", DURATION_COLUMN, NULL);
+	gtk_tree_view_column_set_fixed_width(column, 2*BUTTON_PIXMAP_SIZE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
+
+	// Add playlist view to container
+	gtk_container_add(container, GTK_WIDGET(tree_view));
+}
+
+/**
+ * Creates the playlist editor UI page and all its elements
+ */
+GtkWidget *
+omp_editor_page_create()
+{
+	GtkWidget *main_vbox, *alignment, *label, *scroll_box, *hbox, *button, *image;
+
+	// Create main container
+	main_vbox = gtk_vbox_new(FALSE, 0);
+
+	// Caption
+	alignment = create_label(&label, "Sans 14", "black", 0, 0, 0, 0, 0);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 5, 5, 5, 5);
+	gtk_box_pack_start(GTK_BOX(main_vbox), GTK_WIDGET(alignment), FALSE, FALSE, 0);
+	omp_editor_title_label = label;
+
+	// Track list viewport
+	scroll_box = moko_finger_scroll_new();
+	gtk_box_pack_start(GTK_BOX(main_vbox), GTK_WIDGET(scroll_box), TRUE, TRUE, 0);
+
+	// Create track view
+	omp_editor_page_list_create(GTK_CONTAINER(scroll_box));
+
+	// Add "add tracks" button
+	alignment = gtk_alignment_new(0, 0, 1, 0);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 5, 5, 5, 5);
+	gtk_box_pack_start(GTK_BOX(main_vbox), GTK_WIDGET(alignment), FALSE, TRUE, 0);
+
+	button = gtk_button_new();
+	gtk_container_add(GTK_CONTAINER(alignment), GTK_WIDGET(button));
+	hbox = gtk_hbox_new(FALSE, 0);
+	gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(hbox));
+
+	image = gtk_image_new_from_icon_name("gtk-file", BUTTON_PIXMAP_SIZE);
+	gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(image), TRUE, TRUE, 0);
+
+	alignment = create_label(&label, "Sans 14", "black", 0, 0, 0, 0, 0);
+	gtk_label_set_text(GTK_LABEL(label), _("Add Tracks"));
+	gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(alignment), TRUE, TRUE, 0);
+
+	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(omp_editor_page_add_clicked), NULL);
+
+	// Make all widgets visible
+	gtk_widget_show_all(main_vbox);
+
+	// Set up signal handlers
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_LOADED,
+		G_CALLBACK(omp_editor_page_playlist_loaded), NULL);
+
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_INFO_CHANGED,
+		G_CALLBACK(omp_editor_page_update_track_info), NULL);
+
+	return main_vbox;
+}

Added: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/editor_page.h
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/editor_page.h	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/editor_page.h	2007-08-21 07:41:07 UTC (rev 2752)
@@ -0,0 +1,39 @@
+/*
+ *  OpenMoko Media Player
+ *   http://openmoko.org/
+ *
+ *  Copyright (C) 2007 by the OpenMoko team
+ *
+ *  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.
+ */
+
+/**
+ * @file editor_page.h
+ * Playlist editor, part 1: main editor
+ */
+
+#ifndef EDITOR_PAGE_H
+#define EDITOR_PAGE_H
+
+#include <gtk/gtk.h>
+
+#define OMP_WIDGET_CAPTION_EDITOR "Tracks in playlist '%s':"
+#define OMP_WIDGET_CAPTION_EDITOR_TRACK_TIME "%d:%.2d"
+
+GtkWidget *omp_editor_page_create();
+
+#endif
+
+

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/files_page.c
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/files_page.c	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/files_page.c	2007-08-21 07:41:07 UTC (rev 2752)
@@ -21,7 +21,7 @@
 
 /**
  * @file files_page.c
- * Playlist editor
+ * Playlist editor, part 2: file adding window
  */
 
 #include "files_page.h"

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/files_page.h
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/files_page.h	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/files_page.h	2007-08-21 07:41:07 UTC (rev 2752)
@@ -21,14 +21,13 @@
 
 /**
  * @file files_page.h
- * Playlist editor
+ * Playlist editor, part 2: file adding window
  */
 
 #ifndef FILES_PAGE_H
 #define FILES_PAGE_H
 
 
-
 #endif
 
 

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/guitools.c
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/guitools.c	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/guitools.c	2007-08-21 07:41:07 UTC (rev 2752)
@@ -27,20 +27,19 @@
 #include "guitools.h"
 
 /// Absolute path to the UI pixmaps
-extern gchar *ui_image_path = NULL;
+gchar *ui_image_path = NULL;
 
 
 
-
 /**
  * Loads an image from a file into a pixel buffer
  */
 GdkPixbuf*
-pixbuf_new_from_file(const gchar* file_name)
+pixbuf_new_from_file(const gchar *file_name)
 {
-	gchar* image_file_name;
-	GdkPixbuf* pixbuf = NULL;
-	GError* error = NULL;
+	gchar *image_file_name;
+	GdkPixbuf *pixbuf = NULL;
+	GError *error = NULL;
 
 	image_file_name = g_strdup_printf("%s/%s", ui_image_path, file_name);
 
@@ -49,11 +48,11 @@
 		pixbuf = gdk_pixbuf_new_from_file(image_file_name, &error);
 		if(!pixbuf)
 		{
-			g_print("File found but failed to load: %s\n", image_file_name);
+			g_printerr("File found but failed to load: %s\n", image_file_name);
 			g_error_free(error);
 		}
 	} else {
-		g_debug("Can't find %s\n", image_file_name);
+		g_printerr("Can't find %s\n", image_file_name);
 	}
 
 	g_free(image_file_name);
@@ -84,10 +83,14 @@
 	font_desc = pango_font_description_from_string(font_info);
 	gtk_widget_modify_font(*label, font_desc);
 	pango_font_description_free(font_desc);
-	gtk_label_set_width_chars(GTK_LABEL(*label), max_char_count);
 	gtk_misc_set_alignment(GTK_MISC(*label), 0, 0.5);
-	gtk_label_set_ellipsize(GTK_LABEL(*label), PANGO_ELLIPSIZE_END);
 
+	if (max_char_count)
+	{
+		gtk_label_set_width_chars(GTK_LABEL(*label), max_char_count);
+		gtk_label_set_ellipsize(GTK_LABEL(*label), PANGO_ELLIPSIZE_END);
+	}
+
 	gdk_color_parse(color_desc, &color);
 	gtk_widget_modify_fg(GTK_WIDGET(*label), GTK_STATE_NORMAL, &color);
 

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main.c
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main.c	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main.c	2007-08-21 07:41:07 UTC (rev 2752)
@@ -48,6 +48,8 @@
 
 #include "main.h"
 #include "main_page.h"
+#include "playlist_page.h"
+#include "editor_page.h"
 #include "guitools.h"
 #include "playlist.h"
 #include "playback.h"
@@ -59,39 +61,19 @@
 // Enables GLib memory profiling when defined
 //define DEBUG_MEM_PROFILE
 
+// Forces the window to the native size of the Neo1973's screen area if enabled
+//define EMULATE_SIZE
+
 // The padding applied to the page handle's contents
 #define NOTEBOOK_PAGE_PADDING 6
 
-GtkWidget *omp_window = NULL;
-GtkWidget *omp_notebook = NULL;
-struct _omp_notebook_tabs *omp_notebook_tabs = NULL;
+GtkWidget *omp_window = NULL;													///< Application's main window
+GtkWidget *omp_notebook = NULL;												///< GtkNotebook containing the pages making up the UI
+struct _omp_notebook_tab_ids *omp_notebook_tab_ids = NULL;	///< Holds numerical IDs of the notebook tabs, used for gtk_notebook_set_current_page()
+struct _omp_notebook_tabs *omp_notebook_tabs = NULL;	///< Holds the GtkWidget handles of the notebook tabs so they can be hidden/shown
 
 
 
-/*
-void
-init_dbus()
-{
-    //added by lijiang
-    DBusConnection *bus;
-    DBusError error;
-
-    dbus_error_init(&error);
-    bus = dbus_bus_get(DBUS_BUS_SESSION, &error);
-    if(!bus)
-    {
-        g_print("Failed to connect to the D-Bus daemon: %s", error.message);
-	dbus_error_free(&error);
-	return;
-    }
-    dbus_connection_setup_with_g_main(bus, NULL);
-    dbus_bus_add_match(bus, "type='signal',interface='com.burtonini.dbus.Signal'", &error);
-    dbus_connection_add_filter(bus, signal_filter, mainwindow, NULL);
-    //added end
-
-}
-*/
-
 /**
  * Terminate the entire program normally
  */
@@ -238,32 +220,52 @@
 	omp_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
 	gtk_window_set_title(GTK_WINDOW(omp_window), _("Media Player"));
 	g_signal_connect(G_OBJECT(omp_window), "destroy", G_CALLBACK(omp_close), NULL);
+
+	#ifdef EMULATE_SIZE
+		gtk_widget_set_size_request(GTK_WIDGET(omp_window), 480, 620);
+	#endif
 }
 
 /**
- * Create the individual pages that make up the UI
+ * Creates the individual pages that make up the UI
  * @note Must be called after the backends have been initialized so the signals exist that the UIs hook to
  */
 void
 omp_window_create_pages()
 {
+	GtkWidget *page;
+	guint page_id = 0;
+
 	// Create and set up the notebook that contains the individual UI pages
 	omp_notebook = gtk_notebook_new();
 	g_object_set(G_OBJECT(omp_notebook), "can-focus", FALSE, "homogeneous", TRUE, NULL);
 	gtk_notebook_set_tab_pos(GTK_NOTEBOOK(omp_notebook), GTK_POS_BOTTOM);
 	gtk_container_add(GTK_CONTAINER(omp_window), GTK_WIDGET(omp_notebook));
+	gtk_widget_show(omp_notebook);
 
+	omp_notebook_tab_ids = g_new0(struct _omp_notebook_tab_ids, 1);
 	omp_notebook_tabs = g_new0(struct _omp_notebook_tabs, 1);
 
 	// Add main page
-	omp_notebook_tabs->main = omp_main_page_create(GTK_WINDOW(omp_window));
-	omp_notebook_add_page_with_icon(omp_notebook, omp_notebook_tabs->main,
+	page = omp_main_page_create();
+	omp_notebook_add_page_with_icon(omp_notebook, page,
 		MOKO_STOCK_SPEAKER, NOTEBOOK_PAGE_PADDING);
+	omp_notebook_tab_ids->main = page_id++;
+	omp_notebook_tabs->main = page;
 
 	// Add playlist page
-	omp_notebook_tabs->playlists = omp_playlist_page_create(GTK_WINDOW(omp_window));
-	omp_notebook_add_page_with_icon(omp_notebook, omp_notebook_tabs->playlists,
+	page = omp_playlist_page_create();
+	omp_notebook_add_page_with_icon(omp_notebook, page,
 		MOKO_STOCK_VIEW, NOTEBOOK_PAGE_PADDING);
+	omp_notebook_tab_ids->playlists = page_id++;
+	omp_notebook_tabs->playlists = page;
+
+	// Add playlist editor page
+/*	page = omp_editor_page_create();
+	omp_notebook_add_page_with_icon(omp_notebook, page,
+		"gtk-index", NOTEBOOK_PAGE_PADDING);
+	omp_notebook_tab_ids->editor = page_id++;
+	omp_notebook_tabs->editor = page; */
 }
 
 /**
@@ -272,7 +274,7 @@
 void
 omp_window_free()
 {
-	g_free(omp_notebook_tabs);
+	g_free(omp_notebook_tab_ids);
 }
 
 /**
@@ -281,7 +283,7 @@
 void
 omp_window_show()
 {
-	gtk_widget_show_all(omp_window);
+	gtk_widget_show(omp_window);
 }
 
 /**
@@ -361,6 +363,10 @@
 	omp_playback_init();
 	omp_playlist_init();
 	omp_window_create_pages();
+
+	// Let the UI catch up
+	while (gtk_events_pending()) gtk_main_iteration();
+
 	omp_session_restore_state();
 	omp_window_show();
 

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main.h
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main.h	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main.h	2007-08-21 07:41:07 UTC (rev 2752)
@@ -42,12 +42,19 @@
 #define SESSION_FILE_NAME "/.openmoko-mediaplayer"
 
 
+struct _omp_notebook_tab_ids
+{
+	guint main, playlists, editor, files;
+};
+
 struct _omp_notebook_tabs
 {
-	GtkWidget *main, *playlists, *files;
+	GtkWidget *main, *playlists, *editor, *files;
 };
 
+extern struct _omp_notebook_tab_ids *omp_notebook_tab_ids;
 extern struct _omp_notebook_tabs *omp_notebook_tabs;
+extern GtkWidget *omp_notebook;
 extern GtkWidget *omp_window;
 
 void omp_application_terminate();

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main_page.c
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main_page.c	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main_page.c	2007-08-21 07:41:07 UTC (rev 2752)
@@ -55,6 +55,7 @@
 	GtkWidget *shuffle_button;
 	GtkWidget *repeat_button;
 	GtkWidget *playlist_button;
+	GtkWidget *volume_hscale;
 } main_widgets;
 
 GtkWidget *omp_main_window = NULL;
@@ -62,8 +63,16 @@
 gboolean omp_main_time_slider_can_update = TRUE;				///< Determines whether the time slider can be updated or not
 gboolean omp_main_time_slider_was_dragged = FALSE;			///< Is toggled after the user finished dragging the time slider's button
 
+// Forward declarations for internal use
+void omp_main_update_track_change(gpointer instance, gpointer user_data);
+void omp_main_update_status_change(gpointer instance, gpointer user_data);
+void omp_main_update_track_position(gpointer instance, gpointer user_data);
+void omp_main_update_volume(gpointer instance, gpointer user_data);
+void omp_main_update_tag_artist(gpointer instance, const gchar *artist, gpointer user_data);
+void omp_main_update_tag_title(gpointer instance, const gchar *title, gpointer user_data);
 
 
+
 /**
  * Updates the UI volume display
  * @param vol Volume to show, in percent
@@ -237,19 +246,10 @@
 }
 
 /**
- * Event handler for the Playlist button
- */
-void
-omp_playlist_button_callback(GtkWidget *widget, gpointer data)
-{
-	// ...
-}
-
-/**
  * Event handler for the Fast Forward button
  */
 void
-omp_main_button_fast_forward_callback()
+omp_main_button_fast_forward_callback(GtkWidget *widget, gpointer data)
 {
 	// Set new position and resume playback that was paused when dragging started
 	omp_playback_set_track_position(omp_playback_get_track_position()+BUTTON_SEEK_DISTANCE);
@@ -262,7 +262,7 @@
  * Event handler for the Rewind button
  */
 void
-omp_main_button_rewind_callback()
+omp_main_button_rewind_callback(GtkWidget *widget, gpointer data)
 {
 	// Set new position and resume playback that was paused when dragging started
 	omp_playback_set_track_position(omp_playback_get_track_position()-BUTTON_SEEK_DISTANCE);
@@ -275,7 +275,7 @@
  * Event handler for the Play/Pause button
  */
 void
-omp_main_button_play_pause_callback()
+omp_main_button_play_pause_callback(GtkWidget *widget, gpointer data)
 {
 	if (omp_playback_get_state() != OMP_PLAYBACK_STATE_PLAYING)
 	{
@@ -288,11 +288,43 @@
 }
 
 /**
+ * Gets called when the volume slider's value got changed
+ */
+void
+omp_main_volume_slider_changed(GtkRange *range, gpointer data)
+{
+	omp_playback_set_volume(gtk_range_get_value(GTK_RANGE(range)));
+}
+
+/**
+ * Resets the UI to a "no track loaded" state
+ */
+void
+omp_main_reset_ui(gpointer instance, gpointer user_data)
+{
+	gchar *caption;
+
+	gtk_label_set_text(GTK_LABEL(main_widgets.title_label), "No track info available");
+	gtk_label_set_text(GTK_LABEL(main_widgets.artist_label), "");
+
+	caption = g_strdup_printf(OMP_WIDGET_CAPTION_TRACK_NUM, 0, 0);
+	gtk_label_set_text(GTK_LABEL(main_widgets.track_number_label), caption);
+	g_free(caption);
+
+	caption = g_strdup_printf(OMP_WIDGET_CAPTION_TRACK_TIME, 0, 0, 0, 0);
+	gtk_label_set_text(GTK_LABEL(main_widgets.time_label), caption);
+	g_free(caption);
+
+	gtk_range_set_range(GTK_RANGE(main_widgets.time_hscale), 0, 1);
+	gtk_range_set_value(GTK_RANGE(main_widgets.time_hscale), 0.0);
+}
+
+/**
  * Creates a button framed by a GtkAlignment
  * @param image_name Path and file name of the image to use as pixmap
  * @return A GtkAlignment containing the button
  */
-GtkWidget*
+GtkWidget *
 omp_button_create(gchar *image_name, gint pad_left, GCallback callback, GtkWidget **button)
 {
 	GtkWidget *image;
@@ -322,7 +354,7 @@
  * @param image_name The name of the image resource to use, not a file name
  * @return The button
  */
-GtkWidget*
+GtkWidget *
 omp_stock_button_create(gchar *image_name, GtkWidget **image, GCallback callback)
 {
 	GtkWidget *button;
@@ -348,8 +380,7 @@
 {
 	GtkWidget *alignment;
 	GtkWidget *mainvbox;
-	GtkWidget *background_vbox;
-	GtkWidget *upper_hbox, *middle_hbox, *lower_hbox, *controls_hbox;
+	GtkWidget *upper_hbox, *middle_hbox, *lower_hbox;
 	GtkWidget *middle_right_vbox;
 	GtkWidget *image, *button;
 
@@ -362,14 +393,12 @@
 
 	// Title label
 	alignment = create_label(&main_widgets.title_label, "Bitstream Vera Sans 24", "black", 0, 0, 1, 0, 18);
-	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 18, 0, 50, 30);
-	gtk_label_set_text(GTK_LABEL(main_widgets.title_label), "No track available");
+	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 18, 0, 35, 30);
 	gtk_box_pack_start(GTK_BOX(mainvbox), GTK_WIDGET(alignment), TRUE, TRUE, 0);
 
 	// Artist label
 	alignment = create_label(&main_widgets.artist_label, "Bitstream Vera Sans 14", "black", 0, 0, 1, 0, 30);
-	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 5, 0, 50, 30);
-	gtk_label_set_text(GTK_LABEL(main_widgets.artist_label), "");
+	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 5, 0, 35, 30);
 	gtk_box_pack_start(GTK_BOX(mainvbox), GTK_WIDGET(alignment), TRUE, TRUE, 0);
 
 	// --- --- --- --- --- Upper hbox --- --- --- --- ---
@@ -383,28 +412,22 @@
 
 	// Track number icon
 	alignment = gtk_alignment_new(0, 0, 0, 0);
-	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 2, 0, 0, 15);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 2, 0, 14, 15);
 	gtk_box_pack_start(GTK_BOX(upper_hbox), GTK_WIDGET(alignment), TRUE, TRUE, 0);
 	container_add_image(GTK_CONTAINER(alignment), "ico-track.png");
 
 	// Track number
-	alignment = create_label(&main_widgets.track_number_label, "Bitstream Vera Sans 14", "black", 0, 0, 0, 0, 12);
-	caption = g_strdup_printf(WIDGET_CAPTION_TRACK_NUM, 0, 0);
-	gtk_label_set_text(GTK_LABEL(main_widgets.track_number_label), caption);
-	g_free(caption);
+	alignment = create_label(&main_widgets.track_number_label, "Bitstream Vera Sans 14", "black", 0, 0, 0, 0, 0);
 	gtk_box_pack_start(GTK_BOX(upper_hbox), GTK_WIDGET(alignment), TRUE, TRUE, 0);
 
 	// Time icon
 	alignment = gtk_alignment_new(0, 0, 0, 0);
-	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 2, 0, 48, 15);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 2, 0, 86, 15);
 	gtk_box_pack_start(GTK_BOX(upper_hbox), GTK_WIDGET(alignment), TRUE, TRUE, 0);
 	container_add_image(GTK_CONTAINER(alignment), "ico-time.png");
 
 	// Time
-	alignment = create_label(&main_widgets.time_label, "Bitstream Vera Sans 14", "black", 0, 0, 0, 0, 12);
-	caption = g_strdup_printf(WIDGET_CAPTION_TRACK_TIME, 0, 0, 0, 0);
-	gtk_label_set_text(GTK_LABEL(main_widgets.time_label), caption);
-	g_free(caption);
+	alignment = create_label(&main_widgets.time_label, "Bitstream Vera Sans 14", "black", 0, 0, 0, 0, 0);
 	gtk_box_pack_start(GTK_BOX(upper_hbox), GTK_WIDGET(alignment), TRUE, TRUE, 0);
 
 	// --- --- --- --- --- Slider --- --- --- --- ---
@@ -419,7 +442,6 @@
 	GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(main_widgets.time_hscale), GTK_CAN_FOCUS);
 	gtk_widget_set_size_request(GTK_WIDGET(main_widgets.time_hscale), 338, 35);
 	gtk_range_set_update_policy(GTK_RANGE(main_widgets.time_hscale), GTK_UPDATE_DISCONTINUOUS);
-	gtk_range_set_value(GTK_RANGE(main_widgets.time_hscale), 0.0);
 	g_signal_connect(G_OBJECT(main_widgets.time_hscale), "value_changed",					G_CALLBACK(omp_main_time_slider_changed), NULL);
 	g_signal_connect(G_OBJECT(main_widgets.time_hscale), "button-press-event",		G_CALLBACK(omp_main_time_slider_drag_start), NULL);
 	g_signal_connect(G_OBJECT(main_widgets.time_hscale), "button-release-event",	G_CALLBACK(omp_main_time_slider_drag_stop), NULL);
@@ -464,10 +486,10 @@
 	container_add_image_with_ref(GTK_CONTAINER(alignment), "ind-music-volume-00.png", &main_widgets.volume_image);
 
 	// Volume label
-	alignment = create_label(&main_widgets.volume_label, "Sans 14", "darkorange", 0, 0, 1, 0, 4);
+	alignment = create_label(&main_widgets.volume_label, "Sans 14", "darkorange", 0, 0, 1, 0, 0);
 	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 6, 0, 10, 0);
 	gtk_box_pack_start(GTK_BOX(volume_box), GTK_WIDGET(alignment), TRUE, TRUE, 0);
-	caption = g_strdup_printf(WIDGET_CAPTION_VOLUME, 0);
+	caption = g_strdup_printf(OMP_WIDGET_CAPTION_VOLUME, 0);
 	gtk_label_set_text(GTK_LABEL(main_widgets.volume_label), caption);
 	g_free(caption);
 
@@ -483,7 +505,7 @@
 	alignment = gtk_alignment_new(0, 0, 0, 0);
 	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 25, 0, 0, 0);
 	gtk_box_pack_start(GTK_BOX(mainvbox), alignment, TRUE, TRUE, 0);
-	lower_hbox = gtk_hbox_new(FALSE, 0);
+	lower_hbox = gtk_hbutton_box_new();
 	gtk_container_add(GTK_CONTAINER(alignment), lower_hbox);
 
 	// Shuffle toggle button
@@ -493,53 +515,80 @@
 	// Repeat toggle button
 	alignment = omp_button_create("ico-repeat.png", 10, G_CALLBACK(omp_repeat_button_callback), &main_widgets.repeat_button);
 	gtk_box_pack_start(GTK_BOX(lower_hbox), alignment, TRUE, TRUE, 0);
+}
 
-	// Playlist button
-	alignment = omp_button_create("ico-list.png", 10, G_CALLBACK(omp_playlist_button_callback), &main_widgets.playlist_button);
-	gtk_box_pack_start(GTK_BOX(lower_hbox), alignment, TRUE, TRUE, 0);
+/**
+ * Creates the widgets that didn't originally belong to the main UI
+ */
+void
+omp_main_secondary_widgets_create(GtkContainer *destination)
+{
+	GtkWidget *alignment;
+	GtkWidget *mainvbox;
+	GtkWidget *vbox, *hbox, *vol_vbox, *bal_hbox;
+	GtkWidget *image, *button, *vol_scale;
 
+	// Add mainvbox to destination container
+	mainvbox = gtk_vbox_new(FALSE, 0);
+	gtk_container_add(GTK_CONTAINER(destination), GTK_WIDGET(mainvbox));
+	gtk_widget_set_size_request(GTK_WIDGET(mainvbox), 480, -1);
+
 	// --- --- --- --- --- Player controls --- --- --- --- --- ---
 
 	alignment = gtk_alignment_new(0, 0, 1, 0);
-	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 40, 0, 0, 0);
 	gtk_box_pack_start(GTK_BOX(mainvbox), alignment, TRUE, TRUE, 0);
 
-	controls_hbox = gtk_hbox_new(FALSE, 0);
-	gtk_container_add(GTK_CONTAINER(alignment), controls_hbox);
+	hbox = gtk_hbutton_box_new();
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_SPREAD);
+	gtk_container_add(GTK_CONTAINER(alignment), hbox);
 
-	gtk_box_set_homogeneous(GTK_BOX(controls_hbox), TRUE);
-
 	// Previous Track button
-	button = omp_stock_button_create("gtk-media-previous-ltr", &image, G_CALLBACK(omp_playlist_set_prev_track));
-	gtk_box_pack_start(GTK_BOX(controls_hbox), button, TRUE, TRUE, 0);
-	gtk_box_set_child_packing(GTK_BOX(controls_hbox), GTK_WIDGET(button), FALSE, FALSE, 0, GTK_PACK_START);
+	button = omp_stock_button_create("gtk-media-previous-ltr", &image,
+		G_CALLBACK(omp_playlist_set_prev_track));
+	gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
 
 	// Rewind button
-	button = omp_stock_button_create("gtk-media-rewind-ltr", &image, G_CALLBACK(omp_main_button_rewind_callback));
-	gtk_box_pack_start(GTK_BOX(controls_hbox), button, TRUE, TRUE, 0);
-	gtk_box_set_child_packing(GTK_BOX(controls_hbox), GTK_WIDGET(button), FALSE, FALSE, 0, GTK_PACK_START);
+	button = omp_stock_button_create("gtk-media-rewind-ltr", &image,
+		G_CALLBACK(omp_main_button_rewind_callback));
+	gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
 
 	// Play/Pause button
-	button = omp_stock_button_create("gtk-media-play-ltr", &main_widgets.play_pause_button_image, G_CALLBACK(omp_main_button_play_pause_callback));
-	gtk_box_pack_start(GTK_BOX(controls_hbox), button, TRUE, TRUE, 0);
-	gtk_box_set_child_packing(GTK_BOX(controls_hbox), GTK_WIDGET(button), FALSE, FALSE, 0, GTK_PACK_START);
+	button = omp_stock_button_create("gtk-media-play-ltr", &main_widgets.play_pause_button_image,
+		G_CALLBACK(omp_main_button_play_pause_callback));
+	gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
 
 	// Fast Forward button
-	button = omp_stock_button_create("gtk-media-forward-ltr", &image, G_CALLBACK(omp_main_button_fast_forward_callback));
-	gtk_box_pack_start(GTK_BOX(controls_hbox), button, TRUE, TRUE, 0);
-	gtk_box_set_child_packing(GTK_BOX(controls_hbox), GTK_WIDGET(button), FALSE, FALSE, 0, GTK_PACK_START);
+	button = omp_stock_button_create("gtk-media-forward-ltr", &image,
+		G_CALLBACK(omp_main_button_fast_forward_callback));
+	gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
 
 	// Next Track button
-	button = omp_stock_button_create("gtk-media-next-ltr", &image, G_CALLBACK(omp_playlist_set_next_track));
-	gtk_box_pack_start(GTK_BOX(controls_hbox), button, TRUE, TRUE, 0);
-	gtk_box_set_child_packing(GTK_BOX(controls_hbox), GTK_WIDGET(button), FALSE, FALSE, 0, GTK_PACK_START);
+	button = omp_stock_button_create("gtk-media-next-ltr", &image,
+		G_CALLBACK(omp_playlist_set_next_track));
+	gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
+
+	// --- --- --- --- --- Volume control --- --- --- --- --- ---
+	
+	alignment = gtk_alignment_new(0, 0, 0, 0);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 15, 0, 71, 0);
+	gtk_box_pack_start(GTK_BOX(mainvbox), alignment, TRUE, TRUE, 0);
+
+	// Volume hscale
+	vol_scale = gtk_hscale_new_with_range(0.0, 100.0, 1.0);
+	gtk_scale_set_draw_value(GTK_SCALE(vol_scale), FALSE);
+	GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(vol_scale), GTK_CAN_FOCUS);
+	gtk_widget_set_size_request(GTK_WIDGET(vol_scale), 338, 35);
+	gtk_range_set_update_policy(GTK_RANGE(vol_scale), GTK_UPDATE_DISCONTINUOUS);
+	g_signal_connect(G_OBJECT(vol_scale), "value_changed", G_CALLBACK(omp_main_volume_slider_changed), NULL);
+	gtk_container_add(GTK_CONTAINER(alignment), GTK_WIDGET(vol_scale));
+	main_widgets.volume_hscale = vol_scale;
 }
 
 /**
- * Create the main UI page and all its elements
+ * Creates the main UI page and all its elements
  */
 GtkWidget *
-omp_main_page_create(GtkWindow *window)
+omp_main_page_create()
 {
 	GtkWidget *alignment, *bg_muxer;
 
@@ -548,18 +597,47 @@
 	// Background image
 	alignment = gtk_alignment_new(0, 0, 0, 0);
 	container_add_image(GTK_CONTAINER(alignment), "background.png");
-	gtk_fixed_put(GTK_FIXED(bg_muxer), GTK_WIDGET(alignment), 15, 30);
+	gtk_fixed_put(GTK_FIXED(bg_muxer), GTK_WIDGET(alignment), 15, 10);
 	
 	// Create all widgets
 	alignment = gtk_alignment_new(0, 0, 0, 0);
 	omp_main_widgets_create(GTK_CONTAINER(alignment));
-	gtk_fixed_put(GTK_FIXED(bg_muxer), GTK_WIDGET(alignment), 20, 47);
+	gtk_fixed_put(GTK_FIXED(bg_muxer), GTK_WIDGET(alignment), 20, 26);
 
+	alignment = gtk_alignment_new(0, 0, 0, 0);
+	omp_main_secondary_widgets_create(GTK_CONTAINER(alignment));
+	gtk_fixed_put(GTK_FIXED(bg_muxer), GTK_WIDGET(alignment), 0, 420);
+
+	omp_main_reset_ui(NULL, NULL);
+
 	// Set up signal handlers
-	g_signal_connect(G_OBJECT(window), OMP_EVENT_PLAYLIST_TRACK_CHANGED,		G_CALLBACK(omp_main_update_track_change), NULL);
-	g_signal_connect(G_OBJECT(window), OMP_EVENT_PLAYBACK_STATUS_CHANGED,		G_CALLBACK(omp_main_update_status_change), NULL);
-	g_signal_connect(G_OBJECT(window), OMP_EVENT_PLAYBACK_POSITION_CHANGED,	G_CALLBACK(omp_main_update_track_position), NULL);
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_CHANGED,
+		G_CALLBACK(omp_main_update_track_change), NULL);
 
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_COUNT_CHANGED,
+		G_CALLBACK(omp_main_update_track_change), NULL);
+
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_RESET,
+		G_CALLBACK(omp_main_reset_ui), NULL);
+
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_STATUS_CHANGED,
+		G_CALLBACK(omp_main_update_status_change), NULL);
+
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_POSITION_CHANGED,
+		G_CALLBACK(omp_main_update_track_position), NULL);
+
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_VOLUME_CHANGED,
+		G_CALLBACK(omp_main_update_volume), NULL);
+
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_META_ARTIST_CHANGED,
+		G_CALLBACK(omp_main_update_tag_artist), NULL);
+
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_META_TITLE_CHANGED,
+		G_CALLBACK(omp_main_update_tag_title), NULL);
+
+	// Make all widgets visible
+	gtk_widget_show_all(bg_muxer);
+
 	return bg_muxer;
 }
 
@@ -568,7 +646,7 @@
  * @note This function only checks elements that don't change too often - for the rest we have specialized functions below
  */
 void
-omp_main_update_track_change()
+omp_main_update_track_change(gpointer instance, gpointer user_data)
 {
 	static gint old_track_count = 0;
 	static gint old_track_id = 0;
@@ -576,6 +654,7 @@
 
 	gulong track_length, track_position;
 	gchar *text;
+	gint track_id;
 
 	// Track id/track count changed?
 	if ( (omp_playlist_track_count != old_track_count) || (omp_playlist_current_track_id != old_track_id) )
@@ -583,11 +662,9 @@
 		old_track_count = omp_playlist_track_count;
 		old_track_id = omp_playlist_current_track_id;
 
-		// Update session
-		omp_session_set_track_id(omp_playlist_current_track_id);
-
 		// Update label
-		text = g_strdup_printf(WIDGET_CAPTION_TRACK_NUM, omp_playlist_current_track_id+1, omp_playlist_track_count);
+		track_id = (omp_playlist_track_count) ? omp_playlist_current_track_id+1 : 0;
+		text = g_strdup_printf(OMP_WIDGET_CAPTION_TRACK_NUM, track_id, omp_playlist_track_count);
 		gtk_label_set_text(GTK_LABEL(main_widgets.track_number_label), text);
 		g_free(text);
 	}
@@ -604,9 +681,9 @@
 		gtk_range_set_increments(GTK_RANGE(main_widgets.time_hscale), track_length/10, track_length/10);
 
 		// Update label and slider
-		text = g_strdup_printf(WIDGET_CAPTION_TRACK_TIME,
-			track_position / 60, track_position % 60,
-			track_length / 60, track_length % 60);
+		text = g_strdup_printf(OMP_WIDGET_CAPTION_TRACK_TIME,
+			track_position / 60000, (track_position/1000) % 60,
+			track_length / 60000, (track_length/1000) % 60);
 		gtk_label_set_text(GTK_LABEL(main_widgets.time_label), text);
 		g_free(text);
 
@@ -623,14 +700,18 @@
  * Updates the UI if playback engine switched between paused and playing modes
  */
 void
-omp_main_update_status_change()
+omp_main_update_status_change(gpointer instance, gpointer user_data)
 {
 	// Update Play/Pause button pixmap
 	if (omp_playback_get_state() == OMP_PLAYBACK_STATE_PAUSED)
 	{
-		gtk_image_set_from_icon_name(GTK_IMAGE(main_widgets.play_pause_button_image), "gtk-media-play-ltr", BUTTON_PIXMAP_SIZE);
+		gtk_image_set_from_icon_name(GTK_IMAGE(main_widgets.play_pause_button_image),
+			"gtk-media-play-ltr", BUTTON_PIXMAP_SIZE);
+
 	} else {
-		gtk_image_set_from_icon_name(GTK_IMAGE(main_widgets.play_pause_button_image), "gtk-media-pause", BUTTON_PIXMAP_SIZE);
+
+		gtk_image_set_from_icon_name(GTK_IMAGE(main_widgets.play_pause_button_image),
+			"gtk-media-pause", BUTTON_PIXMAP_SIZE);
 	}
 }
 
@@ -638,7 +719,7 @@
  * Updates the UI if the playback position changed
  */
 void
-omp_main_update_track_position()
+omp_main_update_track_position(gpointer instance, gpointer user_data)
 {
 	static gulong old_track_position = 0;
 
@@ -652,9 +733,9 @@
 		track_length = omp_playback_get_track_length();
 
 		// Update UI
-		text = g_strdup_printf(WIDGET_CAPTION_TRACK_TIME,
-			track_position / 60, track_position % 60,
-			track_length / 60, track_length % 60);
+		text = g_strdup_printf(OMP_WIDGET_CAPTION_TRACK_TIME,
+			track_position / 60000, (track_position/1000) % 60,
+			track_length / 60000, (track_length/1000) % 60);
 		gtk_label_set_text(GTK_LABEL(main_widgets.time_label), text);
 		g_free(text);
 
@@ -668,3 +749,39 @@
 
 }
 
+/**
+ * Updates the UI if the playback volume changed
+ */
+void
+omp_main_update_volume(gpointer instance, gpointer user_data)
+{
+	gchar *text;
+	guint volume;
+
+	volume = omp_playback_get_volume();
+
+	text = g_strdup_printf(OMP_WIDGET_CAPTION_VOLUME, volume);
+	gtk_label_set_text(GTK_LABEL(main_widgets.volume_label), text);
+	g_free(text);
+
+	gtk_range_set_value(GTK_RANGE(main_widgets.volume_hscale), volume);
+}
+
+/**
+ * Updates the UI if the track's artist changed
+ */
+void
+omp_main_update_tag_artist(gpointer instance, const gchar *artist, gpointer user_data)
+{
+	gtk_label_set_text(GTK_LABEL(main_widgets.artist_label), artist);
+}
+
+/**
+ * Updates the UI if the track's title changed
+ */
+void
+omp_main_update_tag_title(gpointer instance, const gchar *title, gpointer user_data)
+{
+	gtk_label_set_text(GTK_LABEL(main_widgets.title_label), title);
+}
+

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main_page.h
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main_page.h	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main_page.h	2007-08-21 07:41:07 UTC (rev 2752)
@@ -29,18 +29,13 @@
 
 #include <gtk/gtk.h>
 
-#define WIDGET_CAPTION_TRACK_TIME "%d:%.2d / %d:%.2d"
-#define WIDGET_CAPTION_TRACK_NUM "%.3d / %.3d"
-#define WIDGET_CAPTION_VOLUME "%d%%"
+#define OMP_WIDGET_CAPTION_TRACK_TIME "%d:%.2d / %d:%.2d"
+#define OMP_WIDGET_CAPTION_TRACK_NUM "%.3d / %.3d"
+#define OMP_WIDGET_CAPTION_VOLUME "%d%%"
 
-// Determines how many seconds the engine will seek if the FFWD/REW buttons are clicked
-#define BUTTON_SEEK_DISTANCE 10
+// Determines how many milliseconds the engine will seek if the FFWD/REW buttons are clicked
+#define BUTTON_SEEK_DISTANCE 10000
 
+GtkWidget *omp_main_page_create();
 
-GtkWidget *omp_main_page_create(GtkWindow *window);
-
-void omp_main_update_track_change();
-void omp_main_update_status_change();
-void omp_main_update_track_position();
-
 #endif

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/persistent.c
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/persistent.c	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/persistent.c	2007-08-21 07:41:07 UTC (rev 2752)
@@ -90,6 +90,18 @@
 }
 
 /**
+ * Fills the session data with sane default values
+ */
+void
+omp_session_reset()
+{
+	memset(omp_session, 0, sizeof(struct _omp_session));
+
+	omp_session->volume = 100;
+	omp_session->fade_speed = 5000;
+}
+
+/**
  * Restores program state from last session
  */
 void
@@ -104,12 +116,16 @@
 
 	omp_session = g_new0(struct _omp_session, 1);
 
-	// Load config and last used playlist if set
+	// Load config
 	omp_session_load();
 
+	omp_playback_set_volume(omp_session->volume);
+
 	if (omp_session->playlist_file[0])
 	{
-		omp_playlist_load(omp_session->playlist_file);
+		// Don't reset playlist state on load or else we'll alter the session
+		// data in unwanted ways since the new session state would be saved
+		omp_playlist_load(omp_session->playlist_file, FALSE);
 	}
 
 	// Check whether playlist_position is valid
@@ -126,6 +142,7 @@
 	{
 		if (omp_session->was_playing)
 		{
+			omp_playback_fade_volume();
 			omp_playback_play();
 		}
 
@@ -204,12 +221,13 @@
 io_error:
 	#ifdef DEBUG
 		g_printerr("Failed trying to load session data from %s: %s\n", file_name, strerror(errno));
+		g_printerr("Resetting session data\n");
 	#endif
 
 	g_free(file_name);
 
-	// Clear session data on error - just to be safe
-	memset(omp_session, 0, sizeof(struct _omp_session));
+	// Reset session data on error - just to be safe
+	omp_session_reset();
 }
 
 /**
@@ -244,3 +262,12 @@
 	omp_session_save();
 }
 
+/**
+ * Set volume to be set next session
+ */
+void
+omp_session_set_volume(guint volume)
+{
+	omp_session->volume = volume;
+	omp_session_save();
+}

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/persistent.h
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/persistent.h	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/persistent.h	2007-08-21 07:41:07 UTC (rev 2752)
@@ -45,11 +45,13 @@
 };
 
 /// Session-persistent data
-/// @note Default values should be 0/FALSE as session data will be zeroed on error
+/// @note Default values are set in omp_session_reset()
 struct _omp_session
 {
+	guint volume;											///< Playback volume in percent (0..100)
+	guint fade_speed;									///< Volume fading speed in milliseconds
 	guint playlist_position;					///< Position within the playlist
-	glong track_position;							///< Position to resume playback from within the last played track
+	gulong track_position;						///< Position to resume playback from within the last played track
 	gboolean was_playing;							///< Set to TRUE of track was being played as the player was closed
 	gchar filesel_path[256];					///< Last path used in the file selection dialog
 	gchar playlist_file[256];					///< Path and file name of current (=last used) playlist

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playback.c
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playback.c	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playback.c	2007-08-21 07:41:07 UTC (rev 2752)
@@ -34,8 +34,15 @@
 gboolean omp_playback_ui_timeout_halted;			///< Flag that tells the UI-updating timeout to exit if set
 gulong omp_playback_pending_position = 0;			///< Since we can't set a new position if element is not paused or playing we store the position here and set it when it reached either state
 
+// Some private forward declarations
+static gboolean omp_gst_message_eos(GstBus *bus, GstMessage *message, gpointer data);
+static gboolean omp_gst_message_state_changed(GstBus *bus, GstMessage *message, gpointer data);
+static gboolean omp_gst_message_error(GstBus *bus, GstMessage *message, gpointer data);
+static gboolean omp_gst_message_warning(GstBus *bus, GstMessage *message, gpointer data);
+static gboolean omp_gst_message_tag(GstBus *bus, GstMessage *message, gpointer data);
 
 
+
 /**
  * Initializes gstreamer by setting up pipe, message hooks and bins
  */
@@ -51,10 +58,34 @@
 	}
 
 	// Create the signals we'll emit
-	g_signal_new(OMP_EVENT_PLAYBACK_EOS,								G_TYPE_OBJECT, 0, 0, 0, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL);
-	g_signal_new(OMP_EVENT_PLAYBACK_STATUS_CHANGED,			G_TYPE_OBJECT, 0, 0, 0, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL);
-	g_signal_new(OMP_EVENT_PLAYBACK_POSITION_CHANGED,		G_TYPE_OBJECT, 0, 0, 0, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL);
+	g_signal_new(OMP_EVENT_PLAYBACK_RESET, G_TYPE_OBJECT,
+		G_SIGNAL_RUN_FIRST, 0, 0, NULL, g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
 
+	g_signal_new(OMP_EVENT_PLAYBACK_EOS, G_TYPE_OBJECT,
+		G_SIGNAL_RUN_FIRST, 0, 0, NULL, g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	g_signal_new(OMP_EVENT_PLAYBACK_STATUS_CHANGED, G_TYPE_OBJECT,
+		G_SIGNAL_RUN_FIRST, 0, 0, NULL, g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	g_signal_new(OMP_EVENT_PLAYBACK_POSITION_CHANGED, G_TYPE_OBJECT,
+		G_SIGNAL_RUN_FIRST, 0, 0, NULL, g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	g_signal_new(OMP_EVENT_PLAYBACK_VOLUME_CHANGED, G_TYPE_OBJECT,
+		G_SIGNAL_RUN_FIRST, 0, 0, NULL, g_cclosure_marshal_VOID__VOID,
+		G_TYPE_NONE, 0);
+
+	g_signal_new(OMP_EVENT_PLAYBACK_META_ARTIST_CHANGED, G_TYPE_OBJECT,
+		G_SIGNAL_RUN_FIRST, 0, 0, NULL, g_cclosure_marshal_VOID__STRING,
+		G_TYPE_NONE, 1, G_TYPE_STRING);
+
+	g_signal_new(OMP_EVENT_PLAYBACK_META_TITLE_CHANGED, G_TYPE_OBJECT,
+		G_SIGNAL_RUN_FIRST, 0, 0, NULL, g_cclosure_marshal_VOID__STRING,
+		G_TYPE_NONE, 1, G_TYPE_STRING);
+
 	// Set up gstreamer pipe and bins
 	omp_gst_playbin = gst_element_factory_make("playbin", "play");
 
@@ -66,6 +97,7 @@
 	g_signal_connect(bus, "message::error", 				G_CALLBACK(omp_gst_message_error), NULL);
 	g_signal_connect(bus, "message::warning", 			G_CALLBACK(omp_gst_message_warning), NULL);
 	g_signal_connect(bus, "message::state-changed",	G_CALLBACK(omp_gst_message_state_changed), NULL);
+	g_signal_connect(bus, "message::tag",						G_CALLBACK(omp_gst_message_tag), NULL);
 
 	gst_object_unref(bus);
 }
@@ -103,6 +135,17 @@
 }
 
 /**
+ * Stops playback and unloads the current track
+ */
+void
+omp_playback_reset()
+{
+	gst_element_set_state(omp_gst_playbin, GST_STATE_READY);
+
+	g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_RESET);
+}
+
+/**
  * Attempts to load a track from an URI
  * @return TRUE if successful, FALSE if failed
  */
@@ -216,7 +259,7 @@
 }
 
 /**
- * Returns the number of seconds that the track has been playing so far
+ * Returns the number of milliseconds that the track has been playing so far
  */
 gulong
 omp_playback_get_track_position()
@@ -230,16 +273,18 @@
 	}
 
 	// Return 0 if function returns FALSE, position otherwise
-	return (gst_element_query_position(omp_gst_playbin, &format, &position)) ? (position/GST_SECOND) : 0;
+	return (gst_element_query_position(omp_gst_playbin, &format, &position)) ? (position/1000000) : 0;
 }
 
 /**
  * Sets the playback position of the currently loaded track
+ * @param position Track position in milliseconds
  */
 void
-omp_playback_set_track_position(glong position)
+omp_playback_set_track_position(gulong position)
 {
 	GstState pipe_state;
+	gint64 pos;
 
 	if (!omp_gst_playbin)
 	{
@@ -258,21 +303,28 @@
 		omp_playback_pending_position = position;
 
 		#ifdef DEBUG
-			g_printf("Pended track position change to %d:%.2ds\n", position / 60, position % 60);
+			g_printf("Pended track position change to %d:%.2ds\n", position / 60000, (position/1000) % 60);
 		#endif
 		return;
 	}
 	omp_playback_pending_position = 0;
 
 	#ifdef DEBUG
-		g_printf("Setting track position to %d:%.2ds\n", position / 60, position % 60);
+		g_printf("Setting track position to %d:%.2ds\n", position / 60000, (position/1000) % 60);
 	#endif
 
+	// Overflow workaround
+	pos = position;
+	pos = pos*1000000;
+
 	gst_element_seek(GST_ELEMENT(omp_gst_playbin), 1.0,
 		GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH,
-		GST_SEEK_TYPE_SET, position*GST_SECOND,
+		GST_SEEK_TYPE_SET, pos,
 		GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
 
+	// Save session data
+	omp_playback_save_state();
+
 	g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_POSITION_CHANGED);
 }
 
@@ -290,11 +342,54 @@
 		return 0;
 	}
 
-	// Return 0 if function returns FALSE, track length otherwise
-	return (gst_element_query_duration(omp_gst_playbin, &format, &length)) ? (length/GST_SECOND) : 0;
+	gst_element_query_duration(omp_gst_playbin, &format, &length);
+	return (length > 0) ? (length/1000000) : 0;
 }
 
 /**
+ * Sets the playback volume
+ * @param volume Volume in percent (0..100)
+ */
+void
+omp_playback_set_volume(guint volume)
+{
+	// Sanity check and failure recovery
+	if (volume > 100)
+	{
+		g_warning("Attempted to set invalid volume!");
+		volume = 100;
+	}
+
+	// Set playbin volume which ranges from 0.0 to 1.0
+	g_object_set(G_OBJECT(omp_gst_playbin), "volume", volume/100.0, NULL);
+
+	omp_session_set_volume(volume);
+
+	g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_VOLUME_CHANGED);
+}
+
+/**
+ * Returns the playback volume
+ * @return Volume in percent (0..100)
+ */
+guint
+omp_playback_get_volume()
+{
+	gdouble volume;
+	g_object_get(G_OBJECT(omp_gst_playbin), "volume", &volume, NULL);
+
+	return volume*100;
+}
+
+/**
+ * Sets up the fade-in timer
+ */
+void
+omp_playback_fade_volume()
+{
+}
+
+/**
  * Handles gstreamer's end-of-stream notification
  */
 static gboolean
@@ -336,6 +431,8 @@
 		previous_state = new_state;
 		g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_STATUS_CHANGED);
 	}
+
+	return TRUE;
 }
 
 /**
@@ -371,3 +468,36 @@
 
 	return TRUE;
 }
+
+/**
+ * Handles gstreamer's tag data notification
+ * @note We can not assume that all meta data will be sent in one go so we use one signal per entry
+ */
+static gboolean
+omp_gst_message_tag(GstBus *bus, GstMessage *message, gpointer data)
+{
+	GstTagList *tag_list;
+	gchar *s;
+
+	#ifdef DEBUG
+		g_printf("gstreamer discovered tag info\n");
+	#endif
+
+	gst_message_parse_tag(message, &tag_list);
+
+	// Read artist
+	if (gst_tag_list_get_string(tag_list, GST_TAG_ARTIST, &s))
+	{
+		g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_META_ARTIST_CHANGED, s);
+		g_free(s);
+	}
+
+	// Read title
+	if (gst_tag_list_get_string(tag_list, GST_TAG_TITLE, &s))
+	{
+		g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_META_TITLE_CHANGED, s);
+		g_free(s);
+	}
+
+	return TRUE;
+}

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playback.h
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playback.h	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playback.h	2007-08-21 07:41:07 UTC (rev 2752)
@@ -29,9 +29,13 @@
 
 #include <gst/gst.h>
 
+#define OMP_EVENT_PLAYBACK_RESET "playback_reset"
 #define OMP_EVENT_PLAYBACK_EOS "playback_end_of_stream"
 #define OMP_EVENT_PLAYBACK_STATUS_CHANGED "playback_status_change"
 #define OMP_EVENT_PLAYBACK_POSITION_CHANGED "playback_position_change"
+#define OMP_EVENT_PLAYBACK_VOLUME_CHANGED "playback_volume_change"
+#define OMP_EVENT_PLAYBACK_META_ARTIST_CHANGED "playback_tag_artist_change"
+#define OMP_EVENT_PLAYBACK_META_TITLE_CHANGED "playback_tag_title_change"
 
 // Player states masking the gstreamer states so we can be more abstract
 #define OMP_PLAYBACK_STATE_PAUSED GST_STATE_PAUSED
@@ -43,6 +47,7 @@
 void omp_playback_init();
 void omp_playback_free();
 void omp_playback_save_state();
+void omp_playback_reset();
 
 gboolean omp_playback_load_track_from_uri(gchar *uri);
 
@@ -50,12 +55,11 @@
 void omp_playback_pause();
 gint omp_playback_get_state();
 gulong omp_playback_get_track_position();
-void omp_playback_set_track_position(glong position);
+void omp_playback_set_track_position(gulong position);
 gulong omp_playback_get_track_length();
 
-static gboolean omp_gst_message_eos(GstBus *bus, GstMessage *message, gpointer data);
-static gboolean omp_gst_message_state_changed(GstBus *bus, GstMessage *message, gpointer data);
-static gboolean omp_gst_message_error(GstBus *bus, GstMessage *message, gpointer data);
-static gboolean omp_gst_message_warning(GstBus *bus, GstMessage *message, gpointer data);
+void omp_playback_set_volume(guint volume);
+guint omp_playback_get_volume();
+void omp_playback_fade_volume();
 
 #endif

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist.c
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist.c	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist.c	2007-08-21 07:41:07 UTC (rev 2752)
@@ -27,19 +27,31 @@
 #include <glib.h>
 #include <glib/gprintf.h>
 #include <glib-object.h>
+#include <glib/gi18n.h>
 #include <spiff/spiff_c.h>
 
+#include <string.h>
+
 #include "playlist.h"
 #include "main.h"
 #include "playback.h"
 #include "persistent.h"
 
-struct spiff_list *omp_playlist = NULL;										///< Loaded playlist
-guint omp_playlist_track_count = 0;												///< Number of tracks stored within the current playlist
+/// Loaded playlist
+struct spiff_list *omp_playlist = NULL;
 
-struct spiff_track *omp_playlist_current_track = NULL;		///< Current track's data
-guint omp_playlist_current_track_id = -1;									///< Numerical id of the current track within the playlist
+/// File name of currently loaded playlist
+gchar *omp_playlist_file = NULL;
 
+/// Number of tracks stored within the current playlist
+guint omp_playlist_track_count = 0;
+
+/// Current track's data
+struct spiff_track *omp_playlist_current_track = NULL;
+
+/// Numerical id of the current track within the playlist
+guint omp_playlist_current_track_id = -1;
+
 /// This linked list holds all tracks that were played in this session, most recently played entry first
 GSList *omp_track_history = NULL;
 
@@ -50,8 +62,14 @@
 	guint track_id;
 };
 
+// Forward declarations for internal use
+void omp_playlist_process_eos_event(gpointer instance, gpointer user_data);
+void omp_playlist_process_tag_artist_change(gpointer instance, gchar *artist, gpointer user_data);
+void omp_playlist_process_tag_title_change(gpointer instance, gchar *title, gpointer user_data);
+void omp_playlist_check_track_duration();
 
 
+
 /**
  * Initialize all things playlist
  */
@@ -59,10 +77,31 @@
 omp_playlist_init()
 {
 	// Hook up event handlers to the playback routines
-	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_EOS, G_CALLBACK(omp_playlist_process_eos_event), NULL);
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_EOS,
+		G_CALLBACK(omp_playlist_process_eos_event), NULL);
 
-	// Create the signals we emit: no params, no return value
-	g_signal_new(OMP_EVENT_PLAYLIST_TRACK_CHANGED, G_TYPE_OBJECT, 0, 0, 0, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL);
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_META_ARTIST_CHANGED,
+		G_CALLBACK(omp_playlist_process_tag_artist_change), NULL);
+
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYBACK_META_TITLE_CHANGED,
+		G_CALLBACK(omp_playlist_process_tag_title_change), NULL);
+
+	// Create the signals we emit
+	g_signal_new(OMP_EVENT_PLAYLIST_LOADED,
+		G_TYPE_OBJECT, G_SIGNAL_RUN_FIRST, 0, 0, NULL,
+		g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
+
+	g_signal_new(OMP_EVENT_PLAYLIST_TRACK_CHANGED,
+		G_TYPE_OBJECT, G_SIGNAL_RUN_FIRST, 0, 0, NULL,
+		g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+
+	g_signal_new(OMP_EVENT_PLAYLIST_TRACK_INFO_CHANGED,
+		G_TYPE_OBJECT, G_SIGNAL_RUN_FIRST, 0, 0, NULL,
+		g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+
+	g_signal_new(OMP_EVENT_PLAYLIST_TRACK_COUNT_CHANGED,
+		G_TYPE_OBJECT, G_SIGNAL_RUN_FIRST, 0, 0, NULL,
+		g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
 }
 
 /**
@@ -82,16 +121,24 @@
 	if (omp_playlist)
 	{
 		spiff_free(omp_playlist);
+		g_free(omp_playlist_file);
 	}
 }
 
 /**
  * Load playlist
- * @todo Count playlist entries on load, trigger "playlist loaded" event to update UI
+ * @param playlist_file Absolute file name of playlist to load
+ * @param do_state_reset Determines whether to reset playlist state on successful load
+ * @return TRUE on success, FALSE on failure
  */
-void
-omp_playlist_load(gchar *playlist_file)
+gboolean
+omp_playlist_load(gchar *playlist_file, gboolean do_state_reset)
 {
+	struct spiff_track *track;
+	guint track_num;
+	GtkWidget *dialog;
+	gchar *title;
+
 	// Free the track history's memory by deleting the first element until the list is empty
 	while (omp_track_history)
 	{
@@ -103,9 +150,10 @@
 	if (omp_playlist)
 	{
 		spiff_free(omp_playlist);
+		g_free(omp_playlist_file);
 	}
 
-	// Update session unless target and source are the same
+	// Update session unless no change happened
 	if (omp_session->playlist_file != playlist_file)
 	{
 		omp_session_set_playlist(playlist_file);
@@ -114,13 +162,100 @@
 	// Load playlist
 	omp_playlist = spiff_parse(playlist_file);
 
-	if (!omp_playlist)
+	if (omp_playlist)
 	{
-		g_printerr("Could not load playlist: %s\n", playlist_file);
+		omp_playlist_file = g_strdup(playlist_file);
+
+		if (do_state_reset)
+		{
+			// Reset playlist state and prepare playback
+			omp_playback_reset();
+			omp_playlist_current_track_id	= 0;
+			omp_playlist_current_track		= omp_playlist->tracks;
+
+			if (omp_playlist_current_track)
+			{
+				omp_playlist_set_current_track(0);
+				omp_playlist_load_current_track();
+			}
+		}
+
+		omp_playlist_update_track_count();
+
+		title = get_playlist_title(omp_playlist_file);
+		g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_LOADED, title);
+		g_free(title);
+
+	} else {
+
+		omp_playlist_current_track_id	= -1;
+		omp_playlist_current_track		= NULL;
+
+		#ifdef DEBUG
+			g_printerr("Could not load playlist: %s\n", playlist_file);
+		#endif
+
+		// Notify user
+		dialog = gtk_message_dialog_new(0,
+			GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+			_("\nCould not load playlist '%s'"), playlist_file);
+
+		gtk_dialog_run(GTK_DIALOG(dialog));
+		gtk_widget_destroy(dialog);
 	}
+
+	return omp_playlist ? TRUE : FALSE;
 }
 
 /**
+ * Creates a new playlist and loads it so it can be used/edited
+ */
+void
+omp_playlist_create(gchar *playlist_file)
+{
+	if (omp_playlist)
+	{
+		spiff_free(omp_playlist);
+		g_free(omp_playlist_file);
+	}
+
+	// Create new playlist, save and load it
+	omp_playlist = spiff_new();
+	omp_playlist_file = g_strdup(playlist_file);
+	omp_playlist_save();
+	omp_playlist_load(playlist_file, TRUE);
+}
+
+/**
+ * Saves the currently loaded playlist to disk
+ */
+void
+omp_playlist_save()
+{
+	if (omp_playlist && omp_playlist_file)
+	{
+		spiff_write(omp_playlist, omp_playlist_file);
+	}
+}
+
+/**
+ * Deletes a playlist file, making sure things stay sane if it's currently loaded
+ * @todo Make unicode safe (-> g_filename_to_utf8())
+ */
+void
+omp_playlist_delete(gchar *playlist_file)
+{
+	if (strcmp(omp_playlist_file, playlist_file) == 0)
+	{
+		spiff_free(omp_playlist);
+		g_free(omp_playlist_file);
+		omp_playback_reset();
+	}
+
+	g_unlink(playlist_file);
+}
+
+/**
  * Tries to set the position within the playlist and indicates success/failure
  * @param playlist_pos New position, counting starts at 0
  */
@@ -131,17 +266,13 @@
 	struct spiff_track *track;
 	gint track_num = 0;
 
-	#ifdef DEBUG
-		g_printf("Setting current track to #%d\n", playlist_pos);
-	#endif
-
 	if (!omp_playlist)
 	{
 		return FALSE;
 	}
 
 	// Walk through the playlist and see if the new position is valid
-	for (track=omp_playlist->tracks; track!=NULL; track=track->next, track_num++)
+	for (track=omp_playlist->tracks; (track!=NULL) && (!position_valid); track=track->next, track_num++)
 	{
 		if (track_num == playlist_pos)
 		{
@@ -153,7 +284,8 @@
 
 	if (position_valid)
 	{
-		omp_playlist_track_count = track_num;
+		// Update session
+		omp_session_set_track_id(omp_playlist_current_track_id);
 
 		// Emit signal to update UI and the like
 		g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_CHANGED);
@@ -246,6 +378,9 @@
 
 	if (is_new_track)
 	{
+		// Update session
+		omp_session_set_track_id(omp_playlist_current_track_id);
+
 		// Emit signal to update UI and the like
 		g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_CHANGED);
 
@@ -308,6 +443,9 @@
 		// Add track to track history
 		omp_track_history = g_slist_prepend(omp_track_history, (gpointer)history_entry);
 
+		// Update session
+		omp_session_set_track_id(omp_playlist_current_track_id);
+
 		// Emit signal to update UI and the like
 		g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_CHANGED);
 
@@ -335,7 +473,8 @@
 /**
  * Signal handler that gets called whenever the current stream ends
  */
-void omp_playlist_process_eos_event()
+void
+omp_playlist_process_eos_event(gpointer instance, gpointer user_data)
 {
 	if (omp_playlist_set_next_track())
 	{
@@ -345,12 +484,129 @@
 }
 
 /**
+ * Updates the track's artist information in the playlist on incoming tag data
+ * @note This is our way of caching metadata information so we can display it in the playlist editor
+ * @param instance Ignored
+ * @param title Artist of currently played track
+ * @param user_data Ignored
+ */
+void
+omp_playlist_process_tag_artist_change(gpointer instance, gchar *artist, gpointer user_data)
+{
+	gchar **tokens;
+
+	// Now that we have received metadata information we might also have the track duration ready
+	omp_playlist_check_track_duration();
+
+	if (!omp_playlist_current_track) return;
+
+	// Set preliminary title if nothing was set at all
+	if (!omp_playlist_current_track->title)
+	{
+		omp_playlist_current_track->title = g_strdup_printf(_("%s - [unknown title]"), artist);
+		return;
+	}
+
+	// Split title into artist/title to see if we need to replace the artist part
+	/// @todo Make unicode safe
+	tokens = g_strsplit(omp_playlist_current_track->title, " - ", 2);
+	if (!tokens) return;
+
+	if (strcmp(tokens[0], _("[unknown artist]")) == 0)
+	{
+		// Yep, track artist was our placeholder, so we replace it
+		g_free(omp_playlist_current_track->title);
+		omp_playlist_current_track->title =
+			g_strdup_printf("%s - %s", artist, tokens[1]);
+	}
+
+	g_strfreev(tokens);
+
+	// Save changes to disk
+	omp_playlist_save();
+
+	// Notify UI of the change
+	g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_INFO_CHANGED,
+		omp_playlist_current_track_id);
+}
+
+/**
+ * Updates the track's artist information in the playlist on incoming tag data
+ * @note This is our way of caching metadata information so we can display it in the playlist editor
+ * @param instance Ignored
+ * @param title Song title of currently played track
+ * @param user_data Ignored
+ */
+void
+omp_playlist_process_tag_title_change(gpointer instance, gchar *title, gpointer user_data)
+{
+	gchar **tokens;
+
+	// Now that we have received metadata information we might also have the track duration ready
+	omp_playlist_check_track_duration();
+
+	if (!omp_playlist_current_track) return;
+
+	// Set preliminary title if nothing was set at all
+	if (!omp_playlist_current_track->title)
+	{
+		omp_playlist_current_track->title = g_strdup_printf("[unknown artist] - %s", title);
+		return;
+	}
+
+	// Split title into artist/title to see if we need to replace the title part
+	/// @todo Make unicode safe
+	tokens = g_strsplit(omp_playlist_current_track->title, " - ", 2);
+	if (!tokens) return;
+
+	if (strcmp(tokens[1], _("[unknown title]")) == 0)
+	{
+		// Yep, track title was our placeholder, so we replace it
+		g_free(omp_playlist_current_track->title);
+		omp_playlist_current_track->title =
+			g_strdup_printf("%s - %s", tokens[0], title);
+	}
+
+	g_strfreev(tokens);
+
+	// Save changes to disk
+	omp_playlist_save();
+
+	// Notify UI of the change
+	g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_INFO_CHANGED,
+		omp_playlist_current_track_id);
+}
+
+/**
+ * Checks to see if we can get track duration information from the playback interface
+ */
+void
+omp_playlist_check_track_duration()
+{
+	gulong duration;
+
+	if (!omp_playlist_current_track) return;
+
+	// Check if we can update duration information (spiff saves it in milliseconds as well)
+	duration = omp_playback_get_track_length();
+	if ( (duration > 0) && (duration != omp_playlist_current_track->duration) )
+	{
+		omp_playlist_current_track->duration = duration;
+		omp_playlist_save();
+
+		// Notify UI of the change
+		g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_INFO_CHANGED,
+			omp_playlist_current_track_id);
+	}
+}
+
+/**
  * Uses the URI(s) and metadata information of a track to locate the resource to play, then returns a playable URI
  * @return URI of a playable resource
  * @note If the URI of the found resource is not in the track's locations list already it is added and the playlist saved
  * @todo Actually make this function do what it's supposed to do :)
  */
-gchar*
+gchar *
 omp_playlist_resolve_track(struct spiff_track *track)
 {
 	if (!track)
@@ -359,10 +615,18 @@
 		return NULL;
 	}
 
+	if (!track->locations)
+	{
+		g_printerr("Resolve request for a track without any locations, returning null as URI.\n");
+		return NULL;
+	}
+
 	if (track->locations->value)
 	{
-		return(g_strdup(track->locations->value));
+		return g_strdup(track->locations->value);
+
 	} else {
+
 		g_printerr("Resolve request for a track without a valid location, ignoring. Will be implemented later.\n");
 		return NULL;
 	}
@@ -392,9 +656,174 @@
 	{
 		track_loaded = omp_playback_load_track_from_uri(track_uri);
 		g_free(track_uri);
+
+		omp_playlist_check_track_duration();
+
 		return track_loaded;
 	}
 
 	return FALSE;
 }
 
+/**
+ * Retrieves a track's meta data if possible
+ * @param track_id Track ID to get meta data of, starting at 0
+ * @param title Destination for the title string, can be NULL; must be freed after use
+ * @param duration Destination for the track duration (in milliseconds), can be NULL
+ * @todo List walking
+ */
+void
+omp_playlist_get_track_info(guint track_id, gchar **title, guint *duration)
+{
+	if (track_id == omp_playlist_current_track_id)
+	{
+		if (title) *title = g_strdup(omp_playlist_current_track->title);
+
+		// Again, spiff's internal duration is given in milliseconds
+		if (duration) *duration = omp_playlist_current_track->duration;
+
+	} else {
+
+		// It's not the current track we want to get the infos of so we need to walk the list
+		#ifdef DEBUG
+			g_print("List walking in omp_playlist_get_track_info() not yet implemented.\n");
+		#endif
+	}
+}
+
+/**
+ * Counts the number of tracks in the playlist and updates the UI
+ */
+void
+omp_playlist_update_track_count()
+{
+	struct spiff_track *track;
+	gint tracks = 0;
+
+	if (!omp_playlist) return;
+
+	for (track=omp_playlist->tracks; track!=NULL; track=track->next, tracks++);
+
+	omp_playlist_track_count = tracks;
+
+	g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_COUNT_CHANGED);
+}
+
+/**
+ * Creates an iterator for iterating over the playlist
+ * @return Returns a new iterator which is deallocated by omp_playlist_advance_iter() - or yourself
+ */
+playlist_iter *
+omp_playlist_init_iterator()
+{
+	playlist_iter *iter;
+
+	if (!omp_playlist) return NULL;
+	if (!omp_playlist->tracks) return NULL;
+
+	iter = g_new(playlist_iter, 1);
+
+	iter->track = omp_playlist->tracks;
+	iter->track_num = 0;
+
+	return iter;
+}
+
+/**
+ * Fetches information about the track the iter points at
+ * @param iter The iterator
+ * @param track_num Destination for the track number (can be NULL)
+ * @param track_title Destination for the track title (can be NULL), must be freed after use
+ */
+void
+omp_playlist_get_track_from_iter(playlist_iter *iter, guint *track_num, gchar **track_title,
+	guint *duration)
+{
+	// Sanity checks - one silent, one not
+	if (!iter) return;
+	g_return_if_fail(iter->track);
+
+	// Assign values
+	if (track_num)
+	{
+		*track_num = iter->track_num;
+	}
+
+	if (track_title)
+	{
+		if (iter->track)
+		{
+			*track_title = g_strdup(iter->track->title);
+		} else {
+			*track_title = NULL;
+		}
+	}
+
+	if (duration)
+	{
+		// Spiff saves the duration in milliseconds, too
+		*duration = (iter->track->duration > 0) ? iter->track->duration : 0;
+	}
+}
+
+/**
+ * Advances a playlist iterator by one track
+ */
+void
+omp_playlist_advance_iter(playlist_iter *iter)
+{
+	if (iter)
+	{
+		if (iter->track)
+		{
+			iter->track = iter->track->next;
+			iter->track_num++;
+		}
+	}
+}
+
+/**
+ * Determines whether an iterator has reached the end of the playlist
+ */
+gboolean
+omp_playlist_iter_finished(struct playlist_iter *iter)
+{
+	if (!iter) return TRUE;
+
+	return (iter->track) ? FALSE : TRUE;
+}
+
+/**
+ * Utility function that extracts a playlist's name from its file name
+ * @param playlist_file File name to extract title from, can contain a path
+ * @return String holding the title, must be freed after use
+ * @todo Make unicode safe
+ * @note Yes, this is quick'n'dirty. It will be replaced.
+ */
+gchar *
+get_playlist_title(gchar *playlist_file)
+{
+	gchar title[256];
+	guint i, j, last_delim, extension_pos;
+
+	// Find last directory delimiter
+	last_delim = 0;
+	for (i=0; playlist_file[i]; i++)
+	{
+		if (playlist_file[i] == '/') last_delim = i+1;
+	}
+
+	// Find file extension
+	for(extension_pos = strlen(playlist_file);
+		(extension_pos) && (playlist_file[extension_pos] != '.');
+		extension_pos--);
+
+	// Extract title
+	for (j=0, i=last_delim; i<extension_pos; i++)
+	{
+		title[j++] = playlist_file[i];
+	}
+	title[j] = 0;
+	
+	return g_strdup((gchar *)&title);
+}

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist.h
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist.h	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist.h	2007-08-21 07:41:07 UTC (rev 2752)
@@ -27,9 +27,15 @@
 #ifndef PLAYLIST_H
 #define PLAYLIST_H
 
+#include <glib.h>
 #include <spiff/spiff_c.h>
 
+#define OMP_PLAYLIST_FILE_EXTENSION "xspf"
+
+#define OMP_EVENT_PLAYLIST_LOADED "playlist_loaded"
 #define OMP_EVENT_PLAYLIST_TRACK_CHANGED "playlist_track_changed"
+#define OMP_EVENT_PLAYLIST_TRACK_INFO_CHANGED "playlist_track_info_changed"
+#define OMP_EVENT_PLAYLIST_TRACK_COUNT_CHANGED "playlist_track_count_changed"
 
 /// Modes available for repetitive track playback
 enum omp_repeat_modes
@@ -42,20 +48,40 @@
 
 extern struct spiff_list *omp_playlist;
 extern guint omp_playlist_track_count;
+extern gchar *omp_playlist_title;
 
 extern struct spiff_track *omp_playlist_current_track;
 extern guint omp_playlist_current_track_id;
 
+/// Playlist iterator
+typedef struct playlist_iter
+{
+	struct spiff_track *track;
+	guint track_num;
+} playlist_iter;
+
 void omp_playlist_init();
 void omp_playlist_free();
-void omp_playlist_load(gchar *playlist_file);
+gboolean omp_playlist_load(gchar *playlist_file, gboolean do_state_reset);
+void omp_playlist_create(gchar *playlist_file);
+void omp_playlist_save();
+void omp_playlist_delete(gchar *playlist_file);
 
 gboolean omp_playlist_set_current_track(gint playlist_pos);
 gboolean omp_playlist_set_prev_track();
 gboolean omp_playlist_set_next_track();
-void omp_playlist_process_eos_event();
 
 gchar *omp_playlist_resolve_track(struct spiff_track *track);
 gboolean omp_playlist_load_current_track();
+void omp_playlist_get_track_info(guint track_id, gchar **title, guint *duration);
+void omp_playlist_update_track_count();
 
+playlist_iter *omp_playlist_init_iterator();
+void omp_playlist_get_track_info_from_iter(playlist_iter *iter, guint *track_num,
+	gchar **track_title, guint *duration);
+void omp_playlist_advance_iter(playlist_iter *iter);
+gboolean omp_playlist_iter_finished(playlist_iter *iter);
+
+gchar *get_playlist_title(gchar *playlist_file);
+
 #endif

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist_page.c
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist_page.c	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist_page.c	2007-08-21 07:41:07 UTC (rev 2752)
@@ -25,79 +25,318 @@
  */
 
 #include <gtk/gtk.h>
+#include <glib/gi18n.h>
 #include <libmokoui2/moko-finger-scroll.h>
+#include <libmokoui2/moko-stock.h>
 
 #include "playlist_page.h"
 #include "main.h"
+#include "guitools.h"
+#include "playlist.h"
 
-/// Enumeration for the list columns
+/// Enumeration for the playlist list columns
 enum
 {
-	ICON_COLUMN,
+	TYPE_COLUMN,
 	NAME_COLUMN,
-	ACTION_COLUMN,
+	ACT_DELETE_COLUMN,
+	FILE_NAME_COLUMN,		// This one isn't shown, it's for internal storage only
 	COLUMN_COUNT
 };
 
-/// The input field where a new playlist name is entered
-GtkWidget *omp_playlist_page_entry;
+/// Input field where a new playlist name is entered
+GtkWidget *omp_playlist_page_entry = NULL;
 
+/// List store for the playlist selector
+GtkListStore *omp_playlist_page_list_store = NULL;
+
+// Just a forward declaration we don't want in the header file
+void omp_playlist_page_list_populate();
+
+
+
 /**
+ * Called when a row was selected and queries user whether he wants to load selected playlist
+ * @param playlist_name Name of the playlist
+ * @param playlist_file_abs File name of playlist with absolute path
+ */
+void
+omp_playlist_page_list_entry_select(gchar *playlist_name, gchar *playlist_file_abs)
+{
+	GtkWidget *dialog;
+	gint dialog_result;
+
+	// Get user confirmation
+	dialog = gtk_message_dialog_new(0,
+		GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
+		_("\nLoad playlist '%s'?"), playlist_name);
+
+	dialog_result = gtk_dialog_run(GTK_DIALOG(dialog));
+
+	if (dialog_result == GTK_RESPONSE_YES)
+	{
+		// Load playlist with state reset to have sane playlist values
+		if (omp_playlist_load(playlist_file_abs, TRUE))
+		{
+			// Switch back to main UI page
+/*			gtk_notebook_set_current_page(GTK_NOTEBOOK(omp_notebook),
+				omp_notebook_tab_ids->main); */
+		}
+	}
+
+	// Clean up
+	gtk_widget_destroy(dialog);
+}
+
+/**
+ * Called after the user clicked the "delete" icon in a row of the list view
+ * @param playlist_name Name of the playlist
+ * @param playlist_file_abs File name of playlist with absolute path
+ */
+void
+omp_playlist_page_list_entry_delete(gchar *playlist_name, gchar *playlist_file)
+{
+	GtkWidget *dialog;
+	gint dialog_result;
+
+	// Get user confirmation
+	dialog = gtk_message_dialog_new(0,
+		GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
+		_("\nDelete playlist '%s'?"), playlist_name);
+
+	dialog_result = gtk_dialog_run(GTK_DIALOG(dialog));
+
+	if (dialog_result == GTK_RESPONSE_YES)
+	{
+		// Delete playlist
+		omp_playlist_delete(playlist_file);
+
+		// Rebuild the list
+		omp_playlist_page_list_populate();
+	}
+
+	// Clean up
+	gtk_widget_destroy(dialog);
+}
+
+/**
+ * Monitors general click events on the list view and acts appropriately
+ */
+gboolean
+omp_playlist_page_list_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
+{
+	GtkTreePath *tree_path;
+	GtkTreeViewColumn *tree_column;
+	GtkTreeIter iterator;
+	GtkTreeModel *model;
+	GList *columns;
+	gint column_id;
+	gchar *playlist_name, *playlist_file, *playlist_file_abs;
+
+	g_return_if_fail(GTK_IS_TREE_VIEW(widget));
+
+	// Find colum that was hit
+	gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), event->x, event->y,
+		&tree_path, &tree_column, NULL, NULL);
+	if (!tree_path) return;
+
+	columns = gtk_tree_view_get_columns(GTK_TREE_VIEW(widget));
+	column_id = g_list_index(columns, (gpointer)tree_column);
+	g_list_free(columns);
+
+	// Find row that was hit
+	model = GTK_TREE_MODEL(gtk_tree_view_get_model(GTK_TREE_VIEW(widget)));
+	gtk_tree_model_get_iter(model, &iterator, tree_path);
+
+	// Select row so the selection gets updated right now
+	gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tree_path, NULL, FALSE);
+
+	// Get playlist names
+	gtk_tree_model_get(model, &iterator,
+		NAME_COLUMN, &playlist_name,
+		FILE_NAME_COLUMN, &playlist_file, -1);
+
+	playlist_file_abs =
+		g_build_filename(g_get_home_dir(), RELATIVE_PLAYLIST_PATH, playlist_file, NULL);
+
+	// Determine what to do
+	switch (column_id)
+	{
+		case NAME_COLUMN:
+		{
+			omp_playlist_page_list_entry_select(playlist_name, playlist_file_abs);
+			break;
+		}
+
+		case ACT_DELETE_COLUMN:
+		{
+			omp_playlist_page_list_entry_delete(playlist_name, playlist_file_abs);
+			break;
+		}
+	}
+
+	// Clean up
+	g_free(playlist_file_abs);
+	gtk_tree_path_free(tree_path);
+
+	return TRUE;
+}
+
+/**
+ * Click handler for the "add new list" button
+ */
+void
+omp_playlist_page_add_list(GtkButton *button, gpointer user_data)
+{
+	gchar *path, *file_name;
+	const gchar *name = gtk_entry_get_text(GTK_ENTRY(omp_playlist_page_entry));
+
+	g_return_if_fail(name);
+
+	// Playlist path is relative to user's home dir
+	path = g_build_path("/", g_get_home_dir(), RELATIVE_PLAYLIST_PATH, NULL);
+	file_name = g_strdup_printf("%s/%s.%s", path, name, OMP_PLAYLIST_FILE_EXTENSION);
+
+	omp_playlist_create(file_name);
+
+	// Rebuild the list
+	omp_playlist_page_list_populate();
+
+	gtk_entry_set_text(GTK_ENTRY(omp_playlist_page_entry), "");
+}
+
+/**
+ * Fills the playlist data model with names from the file system
+ */
+void
+omp_playlist_page_list_populate()
+{
+	gchar *path, *dir_entry, *title;
+	GDir *playlist_dir;
+	GError *error;
+	GtkTreeIter iterator;
+
+	gtk_list_store_clear(omp_playlist_page_list_store);
+
+	// Playlist path is relative to user's home dir
+	path = g_build_path("/", g_get_home_dir(), RELATIVE_PLAYLIST_PATH, NULL);
+
+	playlist_dir = g_dir_open(path, 0, &error);
+
+	if (!playlist_dir)
+	{
+		g_printerr("Could not read playlist directory: %s\n", error->message);
+		g_error_free(error);
+		return;
+	}
+
+	do
+	{
+		dir_entry = (gchar*)g_dir_read_name(playlist_dir);
+
+		// Add entry to list if it's valid
+		if (dir_entry)
+		{
+			title = get_playlist_title(dir_entry);
+
+			gtk_list_store_append(omp_playlist_page_list_store, &iterator);
+			gtk_list_store_set(omp_playlist_page_list_store, &iterator,
+				NAME_COLUMN, title,
+				FILE_NAME_COLUMN, dir_entry, -1);
+
+			g_free(title);
+		}
+	} while (dir_entry);
+
+	g_dir_close(playlist_dir);
+	g_free(path);
+}
+
+/**
  * Creates the playlist view
+ * @param container Destination container of the view
  */
 void
 omp_playlist_page_list_create(GtkContainer *container)
 {
-	GtkListStore *store;
 	GtkWidget *tree_view;
+	GtkTreeSelection *select;
 	GtkCellRenderer *renderer;
 	GtkTreeViewColumn *column;
 
 	// Create and populate data model
-	store = gtk_list_store_new(1, G_TYPE_STRING);
+	omp_playlist_page_list_store = gtk_list_store_new(COLUMN_COUNT,
+		G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
 
+	omp_playlist_page_list_populate();
+
 	// Create data view
-	tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
+	tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(omp_playlist_page_list_store));
+	g_object_unref(G_OBJECT(omp_playlist_page_list_store));
 
+	g_signal_connect(G_OBJECT(tree_view), "button-press-event",
+		G_CALLBACK(omp_playlist_page_list_clicked), NULL);
+
+	// Configure selection handler
+	select = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
+	gtk_tree_selection_set_mode(GTK_TREE_SELECTION(select), GTK_SELECTION_SINGLE);
+
+	// Set up columns
 	renderer = gtk_cell_renderer_pixbuf_new();
+	g_object_set(G_OBJECT(renderer), "stock-id", MOKO_STOCK_VIEW, NULL);
 	column = gtk_tree_view_column_new_with_attributes("", renderer, NULL);
 	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
 	gtk_tree_view_column_set_fixed_width(column, BUTTON_PIXMAP_SIZE);
 	gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
 
 	renderer = gtk_cell_renderer_text_new();
-	column = gtk_tree_view_column_new_with_attributes("Playlist Name", renderer,
-		"name", NAME_COLUMN, NULL);
+	column = gtk_tree_view_column_new_with_attributes(_("Playlist Name"), renderer,
+		"text", NAME_COLUMN, NULL);
 	gtk_tree_view_column_set_expand(column, TRUE);
 	gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
 
+	// Column with "delete" icon
 	renderer = gtk_cell_renderer_pixbuf_new();
-	column = gtk_tree_view_column_new_with_attributes("Actions", renderer, NULL);
+	g_object_set(G_OBJECT(renderer), "stock-id", "gtk-delete", NULL);
+	column = gtk_tree_view_column_new_with_attributes(_("Del?"), renderer, NULL);
 	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
-	gtk_tree_view_column_set_fixed_width(column, 2*BUTTON_PIXMAP_SIZE+10);
+	gtk_tree_view_column_set_fixed_width(column, 2*BUTTON_PIXMAP_SIZE);
 	gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
 
-	// Add view to container
+	// Add playlist view to container
 	gtk_container_add(container, GTK_WIDGET(tree_view));
 }
 
 /**
- * Create the playlist UI page and all its elements
+ * Creates the playlist UI page and all its elements
  */
 GtkWidget *
-omp_playlist_page_create(GtkWindow *window)
+omp_playlist_page_create()
 {
-	GtkWidget *main_vbox, *scroll_box, *input_box, *button, *image;
+	GtkWidget *main_vbox, *scroll_box, *input_box, *button, *image, *alignment, *label;
 
 	// Create main container
 	main_vbox = gtk_vbox_new(FALSE, 0);
 
+	// Caption #1
+	alignment = create_label(&label, "Sans 14", "black", 0, 0, 0, 0, 0);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 5, 5, 5, 5);
+	gtk_box_pack_start(GTK_BOX(main_vbox), GTK_WIDGET(alignment), FALSE, FALSE, 0);
+	gtk_label_set_text(GTK_LABEL(label), _("Select Playlist to load:"));
+
+	// Playlist list viewport
 	scroll_box = moko_finger_scroll_new();
 	gtk_box_pack_start(GTK_BOX(main_vbox), GTK_WIDGET(scroll_box), TRUE, TRUE, 0);
 
 	// Create playlist view
 	omp_playlist_page_list_create(GTK_CONTAINER(scroll_box));
 
+	// Caption #2
+	alignment = create_label(&label, "Sans 14", "black", 0, 0, 0, 0, 0);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 5, 5, 5, 5);
+	gtk_box_pack_start(GTK_BOX(main_vbox), GTK_WIDGET(alignment), FALSE, FALSE, 0);
+	gtk_label_set_text(GTK_LABEL(label), _("Enter name to create a new playlist:"));
+
 	// Add entry field for creation of a new playlist
 	input_box = gtk_hbox_new(FALSE, 0);
 	omp_playlist_page_entry = gtk_entry_new();
@@ -105,11 +344,15 @@
 	image = gtk_image_new_from_icon_name("gtk-add", BUTTON_PIXMAP_SIZE);
 	gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
 
+	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(omp_playlist_page_add_list), NULL);
+
 	gtk_box_pack_start(GTK_BOX(input_box), GTK_WIDGET(omp_playlist_page_entry), TRUE, TRUE, 5);
 	gtk_box_pack_start(GTK_BOX(input_box), GTK_WIDGET(button), FALSE, TRUE, 5);
 
 	gtk_box_pack_start(GTK_BOX(main_vbox), GTK_WIDGET(input_box), FALSE, TRUE, 10);
 
+	// Make all widgets visible
+	gtk_widget_show_all(main_vbox);
+
 	return main_vbox;
 }
-

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist_page.h
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist_page.h	2007-08-20 22:31:27 UTC (rev 2751)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist_page.h	2007-08-21 07:41:07 UTC (rev 2752)
@@ -30,6 +30,5 @@
 #include <gtk/gtk.h>
 
 GtkWidget *omp_playlist_page_create();
-void omp_playlist_connect_signals();
 
 #endif





More information about the commitlog mailing list