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

abraxa at sita.openmoko.org abraxa at sita.openmoko.org
Tue Sep 11 11:01:13 CEST 2007


Author: abraxa
Date: 2007-09-11 11:00:58 +0200 (Tue, 11 Sep 2007)
New Revision: 2949

Added:
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/ico-file-chooser.png
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/ico-filetype-generic.png
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/ico-filetype-mp3.png
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/ico-filetype-ogg.png
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/ico-filetype-unplayable.png
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/ico-playlist-editor.png
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/ico-playlist-new.png
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/ico-playlists.png
Removed:
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/ico-tracktype-general.png
Modified:
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/AUTHORS
   trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/README
   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/editor_page.c
   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/guitools.h
   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/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/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
Log:
Added "Add file" UI and necessary backend functions
Forcing alsasink as gstreamer output plugin, hoping that'll bring down the CPU usage from 90% with PulseAudio
Various bugfixes



Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/AUTHORS
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/AUTHORS	2007-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/AUTHORS	2007-09-11 09:00:58 UTC (rev 2949)
@@ -1,138 +1,2 @@
-BMP - beep media player (C) GPL 2003-2005
-
-Authors alphabetically
-----------------------
-
-Artem Baguinski <artm at v2.nl>
-Edward Brocklesby <ejb at goth.net>
-Chong Kai Xiong <descender at phreaker.net>
-Milosz Derezynski <m.derezynski at arcor.de>
-David Lau <coder_sku at users.sourceforge.net>
-Ole Andre Vadla Ravnaas <oleavr at jblinux.net>
-Michiel Sikkes <michiel at eyesopened.nl>
-
-ClearSky skin by Will Reinhart <willsan at xepher.net>
-
-
-Patch Authors
--------------
-
-Andrei Badea
-Peter Behroozi
-Bernard Blackham
-Oliver Blin
-David Le Brun
-Tomas Bzatek
-Liviu Danicel
-Jon Dowland
-Artur Frysiak
-Sebastian Kapfer
-Lukas Koberstein
-Dan Korostelev
-Oliver Lehmann
-Jolan Luff
-Mike Lundy
-Michael Marineau
-Tim-Philipp Muller
-Julien Portalier
-Andrew Ruder
-Olivier Samyn
-John Spray
-Takashi Iwai
-Martijn Vernooij
-Thierry Vignaud
-
-
-Translators
------------
-
-Brazilian Portuguese - Philipi Pinto <philipi at gmx.net>
-Breton               - Thierry Vignaud <tvignaud at mandrakesoft.com>
-Czech                - Jan Narovec <jnarovec at students.zcu.cz>
-Dutch                - Laurens Buhler <masterpe at xs4all.nl>
-German               - Matthias Debus <psic4t at netbands.de>
-Georgian             - George Machitidze <giomac at global-erty.net>
-Greek                - Kouzinopoulos Haris <haris at mpa.gr>
-                       Stavros Giannouris <stavrosg2002 at freemail.gr>
-Finnish              - Pauli Virtanen <pauli.virtanen at hut.fi>
-French               - David Le Brun <david at dyn-ns.net>
-Hindi                - Dhananjaya Sharma <dysxhi at yahoo.co.in>
-Hungarian            - Laszlo Dvornik <dvornik at gnome.hu>
-Italian              - Alessio D'Ascanio <otaku at fastwebnet.it>
-Japanese             - Takeshi Aihana <aihana at gnome.gr.jp>
-Korean               - DongCheon Park <dcpark at kaist.ac.kr>
-Lithuanian           - Rimas Kudelis <rq at akl.lt>
-Macedonian 	     - Arangel Angov <ufo at linux.net.mk>
-Polish               - Jacek Wolszczak <shutdownrunner at o2.pl>
-Romanian             - Liviu Danicel <liviu.danicel at spymac.com>
-Russian              - Pavlo Bohmat <bohm at ukr.net>
-                       Dan Korostelev <dan at ats.energo.ru>
-                       Vitaly Lipatov <lav at altlinux.ru>
-Simplified Chinese   - Chong Kai Xiong <descender at phreaker.net>
-Traditional Chinese  - Chao-Hsiung Liao <pesder.liao at msa.hinet.net>
-Slovak               - Pavel Kanzelsberger <kanzels at zmail.sk>
-Spanish              - Francisco Javier F. Serrador <serrador at cvs.gnome.org>
-Swedish              - Martin Persenius <martin at persenius.net>
-Ukrainian            - Mykola Lynnyk<definer at users.sf.net>
-Welsh                - Edward Brocklesby <ejb at goth.net>
-                       (Based on XMMS from Rhoslyn Prys <rhoslyn.prys at meddal.org.uk>)
-
-
-(please tell us if we left your name out)
-
--
-
-Based on:
-
-XMMS - X Multimedia System (C)1998-2003
-
-       Main Programming: Peter Alm
-
- Additional Programming: Håvard Kvålen
-                         Derrik Pates
-
-   With Additional Help: Sean Atkinson
-                         Jorn Baayen
-                         James M. Cape
-                         Anders Carlsson (effect plugins)
-                         Chun-Chung Chen (xfont patch)
-                         Tim Ferguson (joystick plugin)
-                         Ben Gertzfield
-                         Vesa Halttunen
-                         Logan Hanks
-                         Eric L. Hernes (FreeBSD patches)
-                         Ville Herva
-                         higway (MMX)
-                         Michael Hipp and others (MPG123 engine)
-                         Olle Hällnäs (compiling fixes)
-                         David Jacoby
-                         Osamu Kayasono (3DNow!)
-                         Lyle B Kempler
-                         J. Nick Koston (MikMod plugin)
-                         Aaron Lehmann
-                         Johan Levin (echo + stereo plugin)
-                         Eric Lindvall
-                         Colin Marquardt
-                         Willem Monsuwe
-                         John Riddoch (Solaris plugin)
-                         Josip Rodin
-                         Pablo Saratxaga (i18n)
-                         Carl van Schaik (pro logic plugin)
-                         Jörg Schuler
-                         Charles Sielski (irman plugin)
-                         Espen Skoglund
-                         Matthieu Sozeau (ALSA plugin)
-                         Kimura Takuhiro (3DNow!)
-                         Zinx Verituse
-                         Ryan Weaver (RPMs among other things)
-                         Chris Wilson
-                         Dave Yearke
-                         Stephan K. Zitz
-
-           Default skin: Leonard "Blayde" Tan
-                         Robin Sylvestre (Equalizer and Playlist)
-                         Thomas Nilsson (New titles, and cleanups)
-
-  Homepage and Graphics: Thomas Nilsson
-
-       Support and Docs: Olle Hällnäs
+Soeren Apel (Abraxa) <abraxa at dar-clan.de>
+Michael 'Mickey' Lauer <mlauer at vanille-media.de>

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/README
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/README	2007-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/README	2007-09-11 09:00:58 UTC (rev 2949)
@@ -0,0 +1,8 @@
+This is the OpenMoko media player for OM2007.2, enabling you to playback audio
+(and eventually video) on your smartphone.
+
+It stores XSPF playlists in ~/playlists and uses the SD card as media storage
+by default. Please be aware that there are issues still - especially when it
+comes to the user interface. They'll be fixed however, so bear with me :)
+
+ -Soeren

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/TODO
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/TODO	2007-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/TODO	2007-09-11 09:00:58 UTC (rev 2949)
@@ -1,9 +1,9 @@
 
 UI:
-	Add repeat mode indicator (different button pixmaps)
 	Make FFWD/REW buttons trigger repeatedly
 	Make Prev/Next buttons sensitive depending on playlist state
 	m3u import
+	QVGA support through external style definitions
 	
 Backend:
 	Use GConf
@@ -12,13 +12,9 @@
 	Check for unicode compliance
 	
 Issues:
-	How/where to store playlists? -> Single common path, omitting path chooser
 	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
-
 Documentation:
-	-/-
+	Update wiki

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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/images/Makefile.am	2007-09-11 09:00:58 UTC (rev 2949)
@@ -2,8 +2,6 @@
 
 images_DATA = \
 	background.png \
-	btn-white.png \
-	btn-white-p.png \
 	ico-track.png \
 	ico-time.png \
 	ico-repeat-off.png \
@@ -14,7 +12,14 @@
 	ico-shuffle-off.png \
 	ico-balance-left.png \
 	ico-balance-right.png \
-	ico-tracktype-general.png \
+	ico-filetype-unplayable.png \
+	ico-filetype-generic.png \
+	ico-filetype-mp3.png \
+	ico-filetype-ogg.png \
+	ico-playlists.png \
+	ico-playlist-new.png \
+	ico-playlist-editor.png \
+	ico-file-chooser.png \
 	ind-music-eq-00.png \
 	ind-music-eq-01.png \
 	ind-music-eq-02.png \

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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

Modified: 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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/editor_page.c	2007-09-11 09:00:58 UTC (rev 2949)
@@ -33,7 +33,7 @@
 #include "playlist.h"
 #include "guitools.h"
 
-/// Enumeration for the track list columns
+/// Enumeration of the track list columns
 enum
 {
 	TYPE_COLUMN,
@@ -92,7 +92,7 @@
 
 	if (artist)
 	{
-		if (strcmp(artist, "") != 0)
+		if (artist[0] != 0)
 		{
 			caption = g_strdup_printf(_("%1$s - %2$s"), artist, title);
 		} else {
@@ -122,13 +122,23 @@
 }
 
 /**
+ * Updates the track list if the track count changed
+ */
+void
+omp_editor_page_update_track_count(gpointer instance, gpointer user_data)
+{
+	omp_editor_page_list_populate();
+}
+
+/**
  * Callback for the "add track" button
  */
 void
 omp_editor_page_add_clicked(gpointer instance, gpointer user_data)
 {
 	// Show file chooser
-	omp_show_tab(OMP_TAB_FILE_CHOOSER);
+	omp_tab_show(OMP_TAB_FILE_CHOOSER);
+	omp_tab_focus(OMP_TAB_FILE_CHOOSER);
 }
 
 /**
@@ -137,7 +147,7 @@
 gboolean
 omp_editor_page_list_clicked(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
 {
- return TRUE;
+	return FALSE;
 }
 
 /**
@@ -171,7 +181,7 @@
 
 		if (artist)
 		{
-			if (strcmp(artist, "") != 0)
+			if (artist[0] != 0)
 			{
 				caption = g_strdup_printf(_("%1$s - %2$s"), artist, title);
 			} else {
@@ -307,5 +317,8 @@
 	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_INFO_CHANGED,
 		G_CALLBACK(omp_editor_page_update_track_info), NULL);
 
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_COUNT_CHANGED,
+		G_CALLBACK(omp_editor_page_update_track_count), NULL);
+
 	return main_vbox;
 }

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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/files_page.c	2007-09-11 09:00:58 UTC (rev 2949)
@@ -24,18 +24,476 @@
  * Playlist editor, part 2: file adding window
  */
 
+#include <gtk/gtk.h>
+#include <glib/gprintf.h>
+#include <glib/gi18n.h>
+#include <libmokoui2/moko-finger-scroll.h>
+#include <string.h>
+
 #include "files_page.h"
+#include "guitools.h"
+#include "persistent.h"
+#include "playlist.h"
 
+/// Enumeration of the file list columns
+enum
+{
+	ICON_COLUMN,
+	NAME_COLUMN,
+	ACT_ADD_COLUMN,
+	COLUMN_COUNT
+};
+
+/// Enumeration of the types the list entries may get assigned
+enum
+{
+	UNPLAYABLE_TYPE,
+	FILE_TYPE,
+	DIRECTORY_TYPE,
+	MP3_TYPE,
+	OGG_TYPE,
+	TYPE_COUNT
+};
+
+/// Array holding the icons' GdkPixbufs so we can assign them to the list view rows
+GdkPixbuf *omp_files_page_type_icons[TYPE_COUNT];
+
+/// Array holding the file extensions the icons depict
+gchar *omp_files_page_type_extensions[TYPE_COUNT];
+
+/// Hash table allowing matching of file extensions to icons
+GHashTable *omp_files_page_type_table = NULL;
+
+/// List store for the file selector
+GtkListStore *omp_files_page_list_store = NULL;
+
+/// The label showing the current file system path
+GtkWidget *omp_files_path_label = NULL;
+
+
+
 /**
+ * Finds a given file's extension and returns a pointer to its first char
+ * @return A pointer to the first char of the extension, belongs to original string so do not free
+ * @todo Make unicode safe?
+ */
+gchar *
+get_file_extension(gchar *file_name)
+{
+	guint i;
+
+	g_return_val_if_fail(file_name, NULL);
+
+	for (i=strlen(file_name); (i>0) && (file_name[i]!='.'); i--);
+
+	return file_name+i+1;
+}
+
+/**
+ * Confirms to the user that the files have been added successfully
+ * @param track_count Number of tracks that were added
+ * @note We only use this when adding directories as it would be annoying otherwise
+ */
+void
+omp_files_page_success_report(guint track_count)
+{
+	GtkWidget *dialog;
+
+	dialog = gtk_message_dialog_new(0,
+		GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_OK,
+		_("Successfully added %d files"), track_count);
+
+	// We don't want a title of "<unnamed>"
+	gtk_window_set_title(GTK_WINDOW(dialog), " ");
+
+	g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
+	gtk_widget_show_all(dialog);
+}
+
+/**
+ * Moves one level upwards in the directory hierarchy
+ * @todo Unicode compatibility
+ */
+void
+omp_files_page_set_prev_dir(GtkButton *button, gpointer user_data)
+{
+	gchar *new_path;
+	guint i;
+
+	new_path = g_strdup(omp_session_get_file_chooser_path());
+
+	// Find last directory level and remove it
+	for (i=strlen(new_path); (i>0) && (new_path[i] != '/'); i--);
+
+	if (new_path[i] == '/')
+	{
+		// Root directory is handled differently
+		if (i == 0)
+		{
+			new_path[1] = 0;  // Directory becomes '/'
+		} else {
+			new_path[i] = 0;  // Last directory gets cut off
+		}
+
+		omp_session_set_file_chooser_path(new_path);
+		omp_files_page_update_path();
+	}
+
+	g_free(new_path);
+}
+
+/**
+ * Reads the directory contents of the current path and updates the view's model accordingly
+ */
+void
+omp_files_page_update_path()
+{
+	gchar *path, *dir_entry, *dir_entry_abs, *temp;
+	GDir *dir;
+	GError *error;
+	GtkTreeIter iterator;
+	GdkPixbuf *icon;
+
+	gtk_list_store_clear(omp_files_page_list_store);
+
+	path = omp_session_get_file_chooser_path();
+	gtk_label_set_text(GTK_LABEL(omp_files_path_label), path);
+
+	if (path[0] == 0) return;
+	dir = g_dir_open(path, 0, &error);
+
+	if (!dir)
+	{
+		g_printerr("Could not read directory %s: %s\n", path, error->message);
+		temp = g_strdup_printf(_("Could not read directory: %s"), error->message);
+		error_dialog(temp);
+		g_free(temp);
+
+		g_error_free(error);
+		g_free(path);
+		return;
+	}
+
+	do
+	{
+		dir_entry = (gchar*)g_dir_read_name(dir);
+		icon = NULL;
+
+		if (!dir_entry) break;
+
+		// Skip hidden entries
+		if (dir_entry[0] == '.') continue;
+
+		// Do we need the directory icon?
+		dir_entry_abs = g_build_path("/", path, dir_entry, NULL);
+		if (g_file_test(dir_entry_abs, G_FILE_TEST_IS_DIR))
+		{
+			icon = omp_files_page_type_icons[DIRECTORY_TYPE];
+		}
+		g_free(dir_entry_abs);
+
+		// Determine icon through file extension
+		if (!icon)
+		{
+			temp = g_ascii_strdown(get_file_extension(dir_entry), -1);
+			icon = g_hash_table_lookup(omp_files_page_type_table, temp);
+			g_free(temp);
+		}
+
+		if (!icon)
+			icon = omp_files_page_type_icons[UNPLAYABLE_TYPE];
+
+		// Add entry to list
+		gtk_list_store_append(omp_files_page_list_store, &iterator);
+		gtk_list_store_set(omp_files_page_list_store, &iterator,
+			ICON_COLUMN, icon,
+			NAME_COLUMN, dir_entry, -1);
+
+	} while (TRUE);
+
+	g_dir_close(dir);
+	g_free(path);
+}
+
+/**
+ * Monitors general click events on the list view and acts appropriately
+ */
+gboolean
+omp_files_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 *entry_name, *entry_name_abs;
+	GdkPixbuf *icon;
+
+	g_return_val_if_fail(GTK_IS_TREE_VIEW(widget), TRUE);
+
+	// 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 TRUE;
+
+	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 file/dir name
+	gtk_tree_model_get(model, &iterator,
+		ICON_COLUMN, &icon,
+		NAME_COLUMN, &entry_name, -1);
+
+	entry_name_abs =
+		g_build_filename(omp_session_get_file_chooser_path(), entry_name, NULL);
+
+	// Determine what to do
+	switch (column_id)
+	{
+		case NAME_COLUMN:
+		{
+			// Enter directory if entry is one
+			if (icon == omp_files_page_type_icons[DIRECTORY_TYPE])
+			{
+				omp_session_set_file_chooser_path(entry_name_abs);
+				omp_files_page_update_path();
+			}
+
+			break;
+		}
+
+		case ACT_ADD_COLUMN:
+		{
+			// Add dir/file
+			if (icon == omp_files_page_type_icons[DIRECTORY_TYPE])
+			{
+				omp_files_page_success_report(
+					omp_playlist_track_append_directory(entry_name_abs));
+
+			} else {
+
+				omp_playlist_track_append_file(entry_name_abs);
+			}
+
+			// Save playlist
+			omp_playlist_save();
+
+			break;
+		}
+	}
+
+	// Clean up
+	g_free(entry_name);
+	g_free(entry_name_abs);
+	gtk_tree_path_free(tree_path);
+
+	return TRUE;
+}
+
+/**
+ * Compare function to determine sort order for the file list view
+ */
+gint
+omp_files_page_view_compare_func(GtkTreeModel *model, GtkTreeIter  *a, GtkTreeIter  *b,
+	gpointer userdata)
+{
+	gchar *entry1, *entry2;
+	GdkPixbuf *icon1, *icon2;
+	gint order;
+
+	// Directories come before files
+	gtk_tree_model_get(model, a, ICON_COLUMN, &icon1, -1);
+	gtk_tree_model_get(model, b, ICON_COLUMN, &icon2, -1);
+
+	if ( (icon1 == omp_files_page_type_icons[DIRECTORY_TYPE]) ||
+		   (icon2 == omp_files_page_type_icons[DIRECTORY_TYPE]) )
+	{
+		// Look at the names if both are directories
+		if (icon1 == icon2)
+		{
+			gtk_tree_model_get(model, a, NAME_COLUMN, &entry1, -1);
+			gtk_tree_model_get(model, b, NAME_COLUMN, &entry2, -1);
+			order = g_utf8_collate(entry1, entry2);
+			g_free(entry1);
+			g_free(entry2);
+
+		} else {
+
+			// One is a dir and one is not, so no need to look at names
+			order = (icon1 == omp_files_page_type_icons[DIRECTORY_TYPE]) ? -1 : 1;
+		}
+
+	} else {
+
+		// Neither entry is a directory, so sort by name
+		gtk_tree_model_get(model, a, NAME_COLUMN, &entry1, -1);
+		gtk_tree_model_get(model, b, NAME_COLUMN, &entry2, -1);
+		order = g_utf8_collate(entry1, entry2);
+		g_free(entry1);
+		g_free(entry2);
+	}
+
+	return order;
+}
+
+/**
+ * Sets up the icons and file extensions used for distinguishing files in the list view
+ */
+void
+omp_files_page_type_setup()
+{
+	// Load file type icons
+	omp_files_page_type_icons[UNPLAYABLE_TYPE] =
+		pixbuf_new_from_file("ico-filetype-unplayable.png");
+
+	omp_files_page_type_icons[FILE_TYPE] =
+		pixbuf_new_from_file("ico-filetype-generic.png");
+
+	omp_files_page_type_icons[DIRECTORY_TYPE] =
+		gtk_widget_render_icon(GTK_WIDGET(omp_window), "gtk-directory",
+		GTK_ICON_SIZE_BUTTON, NULL);
+
+	omp_files_page_type_icons[MP3_TYPE] =
+		pixbuf_new_from_file("ico-filetype-mp3.png");
+
+	omp_files_page_type_icons[OGG_TYPE] =
+		pixbuf_new_from_file("ico-filetype-ogg.png");
+
+	// Fill file extension array
+	omp_files_page_type_extensions[MP3_TYPE] = g_strdup("mp3");
+	omp_files_page_type_extensions[OGG_TYPE] = g_strdup("ogg");
+
+	// Create and fill type hash table
+	omp_files_page_type_table = g_hash_table_new(g_str_hash, g_str_equal);
+
+	g_hash_table_insert(omp_files_page_type_table,
+		omp_files_page_type_extensions[MP3_TYPE], omp_files_page_type_icons[MP3_TYPE]);
+
+	g_hash_table_insert(omp_files_page_type_table,
+		omp_files_page_type_extensions[OGG_TYPE], omp_files_page_type_icons[OGG_TYPE]);
+}
+
+/**
+ * Creates the file view
+ * @param container Destination container of the view
+ */
+void
+omp_files_page_list_create(GtkContainer *container)
+{
+	GtkWidget *tree_view;
+	GtkTreeSelection *select;
+	GtkTreeSortable *sortable;
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *column;
+
+	// Create data model
+	omp_files_page_list_store = gtk_list_store_new(COLUMN_COUNT,
+		GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING);
+
+	// Set up sorting
+	sortable = GTK_TREE_SORTABLE(omp_files_page_list_store);
+	gtk_tree_sortable_set_sort_func(sortable, 0, omp_files_page_view_compare_func, 0, NULL);
+	gtk_tree_sortable_set_sort_column_id(sortable, 0, GTK_SORT_ASCENDING);
+
+	// Create data view
+	tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(omp_files_page_list_store));
+	g_object_unref(G_OBJECT(omp_files_page_list_store));
+
+	g_signal_connect(G_OBJECT(tree_view), "button-press-event",
+		G_CALLBACK(omp_files_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();
+	column = gtk_tree_view_column_new_with_attributes("", renderer,
+		"pixbuf", ICON_COLUMN, NULL);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
+
+	renderer = gtk_cell_renderer_text_new();
+	g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, "ellipsize-set", 1, NULL);
+	column = gtk_tree_view_column_new_with_attributes(_("File/Folder 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);
+
+	renderer = gtk_cell_renderer_pixbuf_new();
+	g_object_set(G_OBJECT(renderer), "stock-id", "gtk-add", NULL);
+	column = gtk_tree_view_column_new_with_attributes("", renderer, NULL);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
+
+	// Add file view to container
+	gtk_container_add(container, GTK_WIDGET(tree_view));
+}
+
+/**
  * Creates the file chooser UI page and all its elements
  */
 GtkWidget *
 omp_files_page_create()
 {
-	GtkWidget *main_vbox;
+	GtkWidget *main_vbox, *alignment, *label, *scroll_box, *hbox, *button, *image;
 
 	// Create main container
 	main_vbox = gtk_vbox_new(FALSE, 0);
 
+	// HBox containing "back" button and path label
+	hbox = gtk_hbox_new(FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(main_vbox), GTK_WIDGET(hbox), FALSE, FALSE, 0);
+
+	button = gtk_button_new();
+	image = gtk_image_new_from_icon_name("gtk-undo-ltr", GTK_ICON_SIZE_MENU);
+	gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
+	gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(button), FALSE, FALSE, 0);
+
+	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(omp_files_page_set_prev_dir), NULL);
+
+	alignment = label_create(&label, "Sans 6", "black", 0, 0, 0, 0, 0);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 5, 5, 5, 5);
+	gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(alignment), FALSE, FALSE, 0);
+	omp_files_path_label = label;
+
+	// File list viewport
+	scroll_box = moko_finger_scroll_new();
+	gtk_box_pack_start(GTK_BOX(main_vbox), GTK_WIDGET(scroll_box), TRUE, TRUE, 0);
+
+	// Set up file types we distinguish
+	omp_files_page_type_setup();
+
+	// Create and populate file view
+	omp_files_page_list_create(GTK_CONTAINER(scroll_box));
+
+	// Make all widgets visible
+	gtk_widget_show_all(main_vbox);
+
 	return main_vbox;
 }
+
+/**
+ * Frees resources used by the file chooser UI
+ */
+void
+omp_files_page_free()
+{
+	guint i;
+
+	g_hash_table_destroy(omp_files_page_type_table);
+
+	for (i=0; i<TYPE_COUNT; i++)
+		if (omp_files_page_type_extensions[i])
+			g_free(omp_files_page_type_extensions[i]);
+
+	for (i=0; i<TYPE_COUNT; i++)
+		g_object_unref(omp_files_page_type_icons[i]);
+}

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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/files_page.h	2007-09-11 09:00:58 UTC (rev 2949)
@@ -30,7 +30,10 @@
 #include <gtk/gtk.h>
 
 GtkWidget *omp_files_page_create();
+void omp_files_page_free();
 
+void omp_files_page_update_path();
+
 #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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/guitools.c	2007-09-11 09:00:58 UTC (rev 2949)
@@ -35,6 +35,7 @@
 
 /**
  * Loads an image from a file into a pixel buffer
+ * @return Pixbuf with the image, must be unref'd after use
  */
 GdkPixbuf*
 pixbuf_new_from_file(const gchar *file_name)
@@ -177,7 +178,7 @@
  * Adds a child to a GtkNotebook, filling the page handle with a stock icon
  */
 void
-notebook_add_page_with_icon(GtkWidget *notebook, GtkWidget *child, const gchar *icon_name, int padding)
+notebook_add_page_with_stock(GtkWidget *notebook, GtkWidget *child, const gchar *icon_name, int padding)
 {
 	GtkWidget *icon, *alignment;
 
@@ -192,3 +193,24 @@
 	gtk_container_child_set(GTK_CONTAINER(notebook), child, "tab-expand", TRUE, NULL);
 }
 
+/**
+ * Adds a child to a GtkNotebook, filling the page handle with an application-specific image
+ */
+void
+notebook_add_page_with_image(GtkWidget *notebook, GtkWidget *child, const gchar *image_name, int padding)
+{
+	gchar *image_file_name;
+	GtkWidget *icon, *alignment;
+
+	image_file_name = g_build_path("/", ui_image_path, image_name, NULL);
+	icon = gtk_image_new_from_file(image_file_name);
+	g_free(image_file_name);
+
+	alignment = gtk_alignment_new(0.5, 0.5, 1, 1);
+	gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), padding, padding, padding, padding);
+	gtk_container_add(GTK_CONTAINER(alignment), icon);
+	gtk_widget_show_all(GTK_WIDGET(alignment));
+
+	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), child, alignment);
+	gtk_container_child_set(GTK_CONTAINER(notebook), child, "tab-expand", TRUE, NULL);
+}

Modified: trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/guitools.h
===================================================================
--- trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/guitools.h	2007-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/guitools.h	2007-09-11 09:00:58 UTC (rev 2949)
@@ -44,6 +44,7 @@
 void container_add_image_with_ref(GtkContainer *container, gchar *image_name, GtkWidget **image);
 void container_add_image(GtkContainer *container, gchar *image_name);
 
-void notebook_add_page_with_icon(GtkWidget *notebook, GtkWidget *child, const gchar *icon_name, int padding);
+void notebook_add_page_with_stock(GtkWidget *notebook, GtkWidget *child, const gchar *icon_name, int padding);
+void notebook_add_page_with_image(GtkWidget *notebook, GtkWidget *child, const gchar *image_name, int padding);
 
 #endif

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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main.c	2007-09-11 09:00:58 UTC (rev 2949)
@@ -252,28 +252,37 @@
 
 	// Add main page
 	page = omp_main_page_create();
-	notebook_add_page_with_icon(omp_notebook, page, MOKO_STOCK_SPEAKER, 0);
+	notebook_add_page_with_stock(omp_notebook, page, MOKO_STOCK_SPEAKER, 0);
 	omp_notebook_tabs[OMP_TAB_MAIN] = page;
 
 	// Add playlist page
 	page = omp_playlist_page_create();
-	notebook_add_page_with_icon(omp_notebook, page, MOKO_STOCK_VIEW, 0);
+	notebook_add_page_with_image(omp_notebook, page, "ico-playlists.png", 0);
 	omp_notebook_tabs[OMP_TAB_PLAYLISTS] = page;
 
 	// Add playlist editor page
 	page = omp_editor_page_create();
-	notebook_add_page_with_icon(omp_notebook, page, "gtk-index", 0);
+	notebook_add_page_with_image(omp_notebook, page, "ico-playlist-editor.png", 0);
 	omp_notebook_tabs[OMP_TAB_PLAYLIST_EDITOR] = page;
 	gtk_widget_hide(page);	// We show the page once a playlist was loaded
 
 	// Add file chooser page
 	page = omp_files_page_create();
-	notebook_add_page_with_icon(omp_notebook, page, "gtk-index", 0);
+	notebook_add_page_with_image(omp_notebook, page, "ico-file-chooser.png", 0);
 	omp_notebook_tabs[OMP_TAB_FILE_CHOOSER] = page;
 	gtk_widget_hide(page);	// We show the page once user wants to add files
 }
 
 /**
+ * Lets all UI pages clean up
+ */
+void
+omp_window_free_pages()
+{
+	omp_files_page_free();
+}
+
+/**
  * Displays the main window and all widgets it contains
  */
 void
@@ -288,7 +297,7 @@
  * @see omp_notebook_tabs
  */
 void
-omp_show_tab(guint tab_id)
+omp_tab_show(guint tab_id)
 {
 	g_return_if_fail(tab_id < OMP_TABS);
 
@@ -301,7 +310,7 @@
  * @see omp_notebook_tabs
  */
 void
-omp_hide_tab(guint tab_id)
+omp_tab_hide(guint tab_id)
 {
 	g_return_if_fail(tab_id < OMP_TABS);
 
@@ -309,6 +318,17 @@
 }
 
 /**
+ *
+ */
+void
+omp_tab_focus(guint tab_id)
+{
+	g_return_if_fail(tab_id < OMP_TABS);
+
+	gtk_notebook_set_current_page(GTK_NOTEBOOK(omp_notebook), tab_id);
+}
+
+/**
  * If only I knew what this is
  */
 gint
@@ -379,7 +399,8 @@
 	signal(SIGSEGV, handler_sigsegfault);
 	signal(SIGUSR1, handler_sigusr1);
 
-	// Initialize playback, playlist and UI handling
+	// Initialize backends and user interfaces
+	omp_session_init();
 	omp_config_init();
 	omp_window_create();
 	if (!omp_playback_init()) return EXIT_FAILURE;
@@ -395,6 +416,7 @@
 	gtk_main();
 
 	// Clean up
+	omp_window_free_pages();
 	omp_playback_save_state();
 	omp_playback_free();
 	omp_playlist_free();

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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main.h	2007-09-11 09:00:58 UTC (rev 2949)
@@ -52,7 +52,8 @@
 
 void omp_application_terminate();
 
-void omp_show_tab(guint tab_id);
-void omp_hide_tab(guint tab_id);
+void omp_tab_show(guint tab_id);
+void omp_tab_hide(guint tab_id);
+void omp_tab_focus(guint tab_id);
 
 #endif

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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/main_page.c	2007-09-11 09:00:58 UTC (rev 2949)
@@ -60,10 +60,14 @@
 
 GtkWidget *omp_main_window = NULL;
 
-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
+/// Determines whether the time slider can be updated or not
+gboolean omp_main_time_slider_can_update = TRUE;
 
+/// Is toggled after the user finished dragging the time slider's button
+gboolean omp_main_time_slider_was_dragged = FALSE;
+
 // Forward declarations for internal use
+void omp_main_playlist_loaded(gpointer instance, gchar *title, gpointer user_data);
 void omp_main_update_track_change(gpointer instance, gpointer user_data);
 void omp_main_update_track_info_changed(gpointer instance, guint track_id, gpointer user_data);
 void omp_main_update_shuffle_state(gpointer instance, gboolean state, gpointer user_data);
@@ -586,6 +590,9 @@
 
 
 	// Set up playlist signal handlers
+	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_LOADED,
+		G_CALLBACK(omp_main_playlist_loaded), NULL);
+
 	g_signal_connect(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_CHANGED,
 		G_CALLBACK(omp_main_update_track_change), NULL);
 
@@ -622,6 +629,16 @@
 }
 
 /**
+ * Callback for the "playlist loaded" event
+ */
+void
+omp_main_playlist_loaded(gpointer instance, gchar *list_title, gpointer user_data)
+{
+	// Playlist editor can now be used
+	omp_tab_show(OMP_TAB_PLAYLIST_EDITOR);
+}
+
+/**
  * Evaluates current track information and updates the config/UI if necessary
  * @note This function only checks elements that don't change too often
  * @note For the rest we have specialized functions below

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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/persistent.c	2007-09-11 09:00:58 UTC (rev 2949)
@@ -34,8 +34,9 @@
 #include <fcntl.h>
 #include <unistd.h>
 
+#include "files_page.h"
+#include "main.h"
 #include "persistent.h"
-#include "main.h"
 #include "playlist.h"
 #include "playback.h"
 
@@ -59,7 +60,7 @@
 
 
 /**
- * Load application configuration data
+ * Initalize and load application configuration data
  */
 void
 omp_config_init()
@@ -169,9 +170,33 @@
 
 	omp_session->volume = 100;
 	omp_session->fade_speed = 5000;
+	g_snprintf(omp_session->file_chooser_path, sizeof(omp_session->file_chooser_path),
+		"%s", "/home/abraxa/local_nas/audio/mp3s/");
 }
 
 /**
+ * Initialize the session handling mechanism
+ */
+void
+omp_session_init()
+{
+	// This mustn't be called more than once
+	g_return_if_fail(omp_session == NULL);
+
+	omp_session = g_new0(struct _omp_session, 1);
+	omp_session_reset();
+}
+
+/**
+ * Frees the resources used for session data
+ */
+void
+omp_session_free()
+{
+	g_free(omp_session);
+}
+
+/**
  * Restores program state from last session
  */
 void
@@ -181,11 +206,6 @@
 		g_print("Loading session data\n");
 	#endif
 
-	// This mustn't be called more than once
-	g_assert(omp_session == NULL);
-
-	omp_session = g_new0(struct _omp_session, 1);
-
 	// Load config
 	omp_session_load();
 
@@ -218,15 +238,9 @@
 
 		omp_playback_set_track_position(omp_session->track_position);
 	}
-}
 
-/**
- * Free resources used for session data
- */
-void
-omp_session_free()
-{
-	g_free(omp_session);
+	// Restore various states
+	omp_files_page_update_path();
 }
 
 /**
@@ -238,6 +252,8 @@
 	gint session_file, result;
 	gchar *file_name;
 
+	g_return_if_fail(omp_session);
+
 	// SESSION_FILE_NAME is relative to user's home dir
 	file_name = g_build_filename(g_get_home_dir(), SESSION_FILE_NAME, NULL);
 
@@ -272,6 +288,8 @@
 	gint session_file, result;
 	gchar *file_name;
 
+	g_return_if_fail(omp_session);
+
 	// SESSION_FILE_NAME is relative to user's home dir
 	file_name = g_build_filename(g_get_home_dir(), SESSION_FILE_NAME, NULL);
 
@@ -306,6 +324,8 @@
 void
 omp_session_set_playback_state(glong track_position, gboolean is_playing)
 {
+	g_return_if_fail(omp_session);
+
 	omp_session->track_position = track_position;
 	omp_session->was_playing = is_playing;
 
@@ -318,6 +338,8 @@
 void
 omp_session_set_playlist(gchar *playlist_file)
 {
+	g_return_if_fail(omp_session);
+
 	g_snprintf(omp_session->playlist_file, sizeof(omp_session->playlist_file), "%s", playlist_file);
 	omp_session_save();
 }
@@ -328,6 +350,8 @@
 void
 omp_session_set_track_id(guint track_id)
 {
+	g_return_if_fail(omp_session);
+
 	omp_session->playlist_position = track_id;
 	omp_session_save();
 }
@@ -338,15 +362,44 @@
 void
 omp_session_set_volume(guint volume)
 {
+	g_return_if_fail(omp_session);
+
 	omp_session->volume = volume;
 	omp_session_save();
 }
 
 /**
+ * Set path to be used for the file chooser UI
+ */
+void
+omp_session_set_file_chooser_path(gchar *path)
+{
+	g_return_if_fail(omp_session);
+
+	g_snprintf(omp_session->file_chooser_path, sizeof(omp_session->file_chooser_path),
+		"%s", path);
+
+	// We don't save the session immediately - saving at app termination is good enough
+}
+
+/**
  * Returns the number of milliseconds defining the duration of a volume fade
  */
 guint
 omp_session_get_fade_speed()
 {
+	g_return_val_if_fail(omp_session, 0);
+
 	return omp_session->fade_speed;
 }
+
+/**
+ * Returns the path used for the file chooser UI; must be freed after use
+ */
+gchar *
+omp_session_get_file_chooser_path()
+{
+	g_return_val_if_fail(omp_session, NULL);
+
+	return g_strdup((gchar *)&omp_session->file_chooser_path);
+}

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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/persistent.h	2007-09-11 09:00:58 UTC (rev 2949)
@@ -33,6 +33,8 @@
 #define OMP_EVENT_CONFIG_SHUFFLE_STATE_CHANGED "config_shuffle_state_changed"
 #define OMP_EVENT_CONFIG_REPEAT_MODE_CHANGED "config_repeat_mode_changed"
 
+#define OMP_EVENT_SESSION_FILE_CHOOSER_PATH_CHANGED "session_file_chooser_path_changed"
+
 /// Application configuration data
 /// @note Default values are taken from omp_default_config
 /// @note Update that struct as well if you make changes here!
@@ -57,7 +59,7 @@
 	guint playlist_position;					///< Position within the playlist
 	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 file_chooser_path[256];			///< Last path used in the file selection dialog
 	gchar playlist_file[256];					///< Path and file name of current (=last used) playlist
 };
 
@@ -76,8 +78,9 @@
 
 guint omp_config_get_prev_track_treshold();
 
+void omp_session_init();
+void omp_session_free();
 void omp_session_restore_state();
-void omp_session_free();
 void omp_session_save();
 void omp_session_load();
 
@@ -85,7 +88,9 @@
 void omp_session_set_playlist(gchar *playlist_file);
 void omp_session_set_track_id(guint track_id);
 void omp_session_set_volume(guint volume);
+void omp_session_set_file_chooser_path(gchar *path);
 
 guint omp_session_get_fade_speed();
+gchar *omp_session_get_file_chooser_path();
 
 #endif

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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playback.c	2007-09-11 09:00:58 UTC (rev 2949)
@@ -36,6 +36,9 @@
 /// Our ticket to the gstreamer world
 GstElement *omp_gst_playbin = NULL;
 
+/// gstreamer audio output element
+GstElement *omp_gst_audiosink = NULL;
+
 /// Handle of the UI-updating timeout
 guint omp_playback_ui_timeout = 0;
 
@@ -105,16 +108,21 @@
 		G_SIGNAL_RUN_FIRST, 0, 0, NULL, g_cclosure_marshal_VOID__STRING,
 		G_TYPE_NONE, 1, G_TYPE_STRING);
 
+	// Create gstreamer audio sink
+	omp_gst_audiosink = gst_element_factory_make("alsasink", NULL);
+
 	// Set up gstreamer pipe and bins
-	omp_gst_playbin = gst_element_factory_make("playbin", "play");
+	omp_gst_playbin = gst_element_factory_make("playbin", NULL);
 
-	if (!omp_gst_playbin)
+	if ( (!omp_gst_audiosink) || (!omp_gst_playbin) )
 	{
-		error_dialog(_("Error: gstreamer failed to initialize.\nPlease make sure gstreamer and its modules are properly installed."));
+		error_dialog(_("Error: gstreamer failed to initialize.\nPlease make sure gstreamer and its modules are properly installed (esp. gst-meta-audio)."));
 
 		return FALSE;
 	}
 
+	g_object_set(G_OBJECT(omp_gst_playbin), "audio-sink", omp_gst_audiosink, NULL);
+
 	// Set up message hooks
 	bus = gst_pipeline_get_bus(GST_PIPELINE(omp_gst_playbin));
 
@@ -149,6 +157,8 @@
 
 	gst_element_set_state(omp_gst_playbin, GST_STATE_NULL);
 	gst_object_unref(GST_OBJECT(omp_gst_playbin));
+
+	gst_object_unref(GST_OBJECT(omp_gst_audiosink));
 }
 
 /**

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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist.c	2007-09-11 09:00:58 UTC (rev 2949)
@@ -31,6 +31,7 @@
 #include <glib/gstdio.h>
 #include <uriparser/Uri.h>
 
+#include <stdarg.h>
 #include <string.h>
 
 #include "playlist.h"
@@ -52,6 +53,9 @@
 /// Current track's data
 omp_spiff_track *omp_playlist_current_track = NULL;
 
+/// Pointer to the last track of the playlist (saves time when appending tracks)
+omp_spiff_track *omp_playlist_last_track = NULL;
+
 /// Numerical id of the current track within the playlist
 guint omp_playlist_current_track_id = -1;
 
@@ -175,6 +179,7 @@
 			omp_playback_reset();
 			omp_playlist_current_track_id	= 0;
 			omp_playlist_current_track		= omp_playlist->tracks;
+			omp_playlist_last_track = NULL;
 
 			if (omp_playlist_current_track)
 			{
@@ -189,13 +194,11 @@
 		g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_LOADED, title);
 		g_free(title);
 
-		// Show playlist editor
-		omp_show_tab(OMP_TAB_PLAYLIST_EDITOR);
-
 	} else {
 
 		omp_playlist_current_track_id	= -1;
 		omp_playlist_current_track		= NULL;
+		omp_playlist_last_track = NULL;
 
 		#ifdef DEBUG
 			g_printerr("Could not load playlist: %s\n", playlist_file);
@@ -621,7 +624,7 @@
  * @todo Unicode support
  */
 void
-omp_playlist_set_preliminary_metadata(gchar *track_uri)
+omp_playlist_set_preliminary_metadata(omp_spiff_track *track, gchar *track_uri)
 {
 	UriParserStateA state;
 	UriUriA uri;
@@ -630,7 +633,7 @@
 
 	state.uri = &uri;
 
-	g_return_if_fail(omp_playlist_current_track);
+	g_return_if_fail(track);
 
 	if (uriParseUriA(&state, track_uri) != 0)
 	{
@@ -642,19 +645,33 @@
 
 	// The last part of the URI path is the file name of the request - which we want
 	segment = uri.pathTail;
+
+	if (!segment)
+	{
+		uriFreeUriMembersA(&uri);
+		#ifdef DEBUG
+			g_printerr("UriParser did not deliver path tail for %s\n", track_uri);
+		#endif
+		return;
+	}
+
+	if (!segment->text.first)
+	{
+		uriFreeUriMembersA(&uri);
+		#ifdef DEBUG
+			g_printerr("UriParser did not deliver first text element for %s\n", track_uri);
+		#endif
+		return;
+	}
+
 	title = get_base_file_name((gchar*)segment->text.first);
 	uriUnescapeInPlaceA(title);
 
 	// Set preliminary metadata if necessary
-	if (!omp_playlist_current_track->title)
+	if (!track->title)
 	{
-		omp_playlist_current_track->title = g_strdup(title);
-		omp_playlist_current_track->title_is_preliminary = TRUE;
-		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);
+		track->title = g_strdup(title);
+		track->title_is_preliminary = TRUE;
 	}
 
 	g_free(title);
@@ -722,8 +739,12 @@
 		omp_playlist_update_track_duration();
 
 		// Obtain preliminary track title from URI if needed
-		omp_playlist_set_preliminary_metadata(track_uri);
+		omp_playlist_set_preliminary_metadata(omp_playlist_current_track, track_uri);
 
+		// Notify UI of the metadata change
+		g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_INFO_CHANGED,
+			omp_playlist_current_track_id);
+
 		g_free(track_uri);
 
 		return track_loaded;
@@ -768,16 +789,24 @@
 void
 omp_playlist_update_track_count()
 {
-	omp_spiff_track *track;
-	gint tracks = 0;
+	omp_spiff_track *track, *last_track = NULL;
+	gint old_count, count = 0;
 
 	if (!omp_playlist) return;
 
-	for (track=omp_playlist->tracks; track!=NULL; track=track->next, tracks++);
+	old_count = omp_playlist_track_count;
 
-	omp_playlist_track_count = tracks;
+	// Walk through the entire list to count number of tracks and find last track pointer
+	for (track=omp_playlist->tracks; track!=NULL; last_track=track, track=track->next, count++);
 
-	g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_COUNT_CHANGED);
+	omp_playlist_track_count = count;
+	omp_playlist_last_track  = last_track;
+
+	// Notify UI only if track count actually changed
+	if (old_count != count)
+	{
+		g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_COUNT_CHANGED);
+	}
 }
 
 /**
@@ -875,6 +904,136 @@
 }
 
 /**
+ * Appends a track to the end of the playlist
+ * @todo Make unicode-safe
+ */
+void
+omp_playlist_track_append_file(gchar *file_name)
+{
+	omp_spiff_track *new_track;
+	omp_spiff_mvalue *location;
+	gchar *uri, name_char;
+	guint name_pos, uri_pos, name_len;
+
+	if (!omp_playlist) return;
+	if (!file_name) return;
+
+	// Try to make the "last track" pointer valid - if it stays NULL then the list is empty
+	if (!omp_playlist_last_track)
+	{
+		omp_playlist_update_track_count();
+	}
+
+	// Append track
+	if (omp_playlist_last_track)
+	{
+		new_track = omp_spiff_new_track_before(&omp_playlist_last_track->next);
+		omp_playlist_last_track = omp_playlist_last_track->next;
+	} else {
+		new_track = omp_spiff_new_track_before(&omp_playlist->tracks);
+		omp_playlist_last_track = omp_playlist->tracks;
+	}
+
+	location = omp_spiff_new_mvalue_before(&new_track->locations);
+	omp_playlist_track_count++;
+
+	// Build URI for the file location
+	name_len = strlen(file_name);
+	uri = g_malloc(7+3*name_len);  // Enough for worst case: every char becomes %xx character sequence
+
+	g_sprintf(uri, "file://");
+	uri_pos = 7;
+
+	// We could use uriEscapeA() here but that will also transform '/' to %2F, which we do not want
+	for (name_pos=0; name_pos < name_len; name_pos++)
+	{
+		name_char = file_name[name_pos];
+		if (  ((name_char >= 'a') && (name_char <= 'z'))
+		   || ((name_char >= 'A') && (name_char <= 'Z'))
+		   || ((name_char >= '0') && (name_char <= '9'))
+		   || (name_char == '-') || (name_char == '.')
+		   || (name_char == '_') || (name_char == '~')
+		   || (name_char == '/') )
+		{
+			uri[uri_pos++] = name_char;
+
+		} else {
+
+			uri[uri_pos+0] = '%';
+			uri[uri_pos+1] = uriHexToLetterA(name_char >> 4);
+			uri[uri_pos+2] = uriHexToLetterA(name_char & 0x0F);
+			uri_pos += 3;
+		}
+	}
+	uri[uri_pos] = 0;
+
+	location->value = g_strdup(uri);
+	g_free(uri);
+
+	// Give the track list something to show
+	omp_playlist_last_track->title = get_base_file_name(file_name);
+	omp_playlist_last_track->title_is_preliminary = TRUE;
+
+	// Notify UI of the change
+	g_signal_emit_by_name(G_OBJECT(omp_window), OMP_EVENT_PLAYLIST_TRACK_COUNT_CHANGED);
+}
+
+/**
+ * Recursively adds all files from a directory and its subdirectories
+ * @param dir_name Directory to add
+ * @return Number of files added
+ */
+guint
+omp_playlist_track_append_directory(gchar *dir_name)
+{
+	gchar *dir_entry, *dir_entry_abs, *temp;
+	GDir *dir;
+	GError *error;
+	guint file_count = 0;
+
+	g_return_val_if_fail(dir_name, 0);
+
+	dir = g_dir_open(dir_name, 0, &error);
+
+	if (!dir)
+	{
+		g_printerr("Could not read directory %s: %s\n", dir_name, error->message);
+		temp = g_strdup_printf(_("Could not read directory: %s"), error->message);
+		error_dialog(temp);
+		g_free(temp);
+
+		g_error_free(error);
+		return 0;
+	}
+
+	do
+	{
+		dir_entry = (gchar*)g_dir_read_name(dir);
+
+		if (!dir_entry) break;
+
+		// Skip hidden entries
+		if (dir_entry[0] == '.') continue;
+
+		// Do we need to dive into a subdirectory?
+		dir_entry_abs = g_build_path("/", dir_name, dir_entry, NULL);
+		if (g_file_test(dir_entry_abs, G_FILE_TEST_IS_DIR))
+		{
+			file_count += omp_playlist_track_append_directory(dir_entry_abs);
+		} else {
+			omp_playlist_track_append_file(dir_entry_abs);
+			file_count++;
+		}
+		g_free(dir_entry_abs);
+
+	} while (TRUE);
+
+	g_dir_close(dir);
+
+	return file_count;
+}
+
+/**
  * Utility function that removes the path and extension of a file name
  * @param file_name File name to extract title from, can contain a path
  * @return String holding the title, must be freed after use

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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist.h	2007-09-11 09:00:58 UTC (rev 2949)
@@ -92,7 +92,13 @@
 void omp_playlist_advance_iter(omp_playlist_iter *iter);
 gboolean omp_playlist_iter_finished(omp_playlist_iter *iter);
 
+void omp_playlist_track_append_file(gchar *file_name);
+guint omp_playlist_track_append_directory(gchar *dir_name);
+
 gchar *get_base_file_name(gchar *file_name);
 gchar *get_playlist_title(gchar *playlist_file);
 
+// Taken from uriparser's UriCommon.h, which sadly is not installed with uriparser
+char uriHexToLetterA(unsigned int value);
+
 #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-09-11 05:07:26 UTC (rev 2948)
+++ trunk/src/target/OM-2007.2/applications/openmoko-mediaplayer2/src/playlist_page.c	2007-09-11 09:00:58 UTC (rev 2949)
@@ -24,6 +24,7 @@
  * Playlist UI handling
  */
 
+#include <glib/gprintf.h>
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 #include <libmokoui2/moko-finger-scroll.h>
@@ -31,12 +32,12 @@
 
 #include <string.h>
 
-#include "playlist_page.h"
+#include "guitools.h"
 #include "main.h"
-#include "guitools.h"
 #include "playlist.h"
+#include "playlist_page.h"
 
-/// Enumeration for the playlist list columns
+/// Enumeration of the playlist list columns
 enum
 {
 	TYPE_COLUMN,
@@ -180,6 +181,8 @@
 
 	// Clean up
 	g_free(playlist_file_abs);
+	g_free(playlist_file);
+	g_free(playlist_name);
 	gtk_tree_path_free(tree_path);
 
 	return TRUE;
@@ -194,7 +197,7 @@
 	gchar *path, *file_name;
 	const gchar *name = gtk_entry_get_text(GTK_ENTRY(omp_playlist_page_entry));
 
-	g_return_if_fail(strcmp(name, "") != 0);
+	g_return_if_fail(name[0] != 0);
 
 	// Playlist path is relative to user's home dir
 	path = g_build_path("/", g_get_home_dir(), RELATIVE_PLAYLIST_PATH, NULL);
@@ -314,6 +317,7 @@
 omp_playlist_page_create()
 {
 	GtkWidget *main_vbox, *scroll_box, *input_box, *button, *image, *alignment, *label;
+	gchar *image_file_name;
 
 	// Create main container
 	main_vbox = gtk_vbox_new(FALSE, 0);
@@ -341,7 +345,10 @@
 	input_box = gtk_hbox_new(FALSE, 0);
 	omp_playlist_page_entry = gtk_entry_new();
 	button = gtk_button_new();
-	image = gtk_image_new_from_icon_name("gtk-add", GTK_ICON_SIZE_MENU);
+
+	image_file_name = g_build_filename(ui_image_path, "ico-playlist-new.png", NULL);
+	image = gtk_image_new_from_file(image_file_name);
+	g_free(image_file_name);
 	gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
 
 	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(omp_playlist_page_add_list), NULL);





More information about the commitlog mailing list