/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 *  Authors: Iain Holmes <iain@prettypeople.org>
 *           Johan Dahlin <johan@gnome.org>
 *           Tim-Philipp Müller <tim centricular net>
 *
 *  Copyright 2002 Iain Holmes
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * 4th Februrary 2005: Christian Schaller: changed license to LGPL with
 * permission of Iain Holmes, Ronald Bultje, Leontine Binchy (SUN), Johan Dalhin
 * and Joe Marcus Clarke
 *
 */

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

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <math.h>

#include <glib/gi18n.h>
#include <gio/gio.h>
#include <gtk/gtk.h>
#include <gconf/gconf-client.h>
#include <gst/gst.h>
#include <gst/interfaces/mixer.h>

#include <profiles/gnome-media-profiles.h>

#include "gsr-window.h"

GST_DEBUG_CATEGORY_STATIC (gsr_debug);
#define GST_CAT_DEFAULT gsr_debug

extern GtkWidget * gsr_open_window (const char *filename);
extern void gsr_quit (void);

extern GConfClient *gconf_client;

extern void gsr_add_recent (gchar *filename);

#define GCONF_DIR           "/apps/gnome-sound-recorder/"
#define KEY_OPEN_DIR        GCONF_DIR "system-state/open-file-directory"
#define KEY_SAVE_DIR        GCONF_DIR "system-state/save-file-directory"
#define KEY_LAST_PROFILE_ID GCONF_DIR "last-profile-id"
#define KEY_LAST_INPUT      GCONF_DIR "last-input"
#define EBUSY_TRY_AGAIN     3    /* Empirical data */

typedef struct _GSRWindowPipeline {
	GstElement *pipeline;
	GstState    state;     /* last seen (async) pipeline state */
	GstBus     *bus;

	GstElement *src;
	GstElement *sink;

	guint       tick_id;
} GSRWindowPipeline;

enum {
	PROP_0,
	PROP_LOCATION
};

static GtkWindowClass *parent_class = NULL;

#define GSR_WINDOW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GSR_TYPE_WINDOW, GSRWindowPrivate))

struct _GSRWindowPrivate {
	GtkWidget *main_vbox;
	GtkWidget *scale;
	GtkWidget *profile, *input;
	GtkWidget *rate, *time_sec, *format, *channels;
	GtkWidget *input_label;
	GtkWidget *name_label;
	GtkWidget *length_label;
	GtkWidget *align;
	GtkWidget *volume_label;
	GtkWidget *level;

	gulong seek_id;

	GtkUIManager *ui_manager;
	GtkActionGroup *action_group;
	GtkWidget *recent_view;
	GtkRecentFilter *recent_filter;

	/* statusbar */
	GtkWidget *statusbar;
	guint status_message_cid;
	guint tip_message_cid;

	/* Pipelines */
	GSRWindowPipeline *play;
	GSRWindowPipeline *record;
	char *record_filename;
	char *filename;
	char *extension;
	char *working_file; /* Working file: Operations only occur on the
			       working file. The result of that operation then
			       becomes the new working file. */
	int record_fd;

	/* File info */
	int len_secs; /* In seconds */
	int get_length_attempts;

	/* ATOMIC access */
	struct {
		gint n_channels;
		gint bitrate;
		gint samplerate;
	} atomic;

	gboolean has_file;
	gboolean saved;
	gboolean dirty;
	gboolean seek_in_progress;

	gboolean quit_after_save;

	guint32 tick_id; /* tick_callback timeout ID */
	guint32 record_id; /* record idle callback timeout ID */

	GstElement *ebusy_pipeline;  /* which pipeline we're trying to start */
	guint       ebusy_timeout_id;

	GstElement *source;
	GstMixer *mixer;
};

static gboolean            make_record_source      (GSRWindow         *window);
static void                fill_record_input       (GSRWindow         *window, gchar *selected);
static GSRWindowPipeline * make_record_pipeline    (GSRWindow         *window);
static GSRWindowPipeline * make_play_pipeline      (GSRWindow         *window);

static void
show_error_dialog (GtkWindow   *window,
                   const gchar *debug_message,
                   const gchar *format, ...) G_GNUC_PRINTF (3,4);

static void
show_error_dialog (GtkWindow *win, const gchar *dbg, const gchar * format, ...)
{
	GtkWidget *dialog;
	va_list args;
	gchar *s;

	va_start (args, format);
	s = g_strdup_vprintf (format, args);
	va_end (args);

	dialog = gtk_message_dialog_new (win,
					 GTK_DIALOG_DESTROY_WITH_PARENT,
					 GTK_MESSAGE_ERROR,
					 GTK_BUTTONS_CLOSE,
					 "%s",
					 s);

	if (dbg != NULL) {
		g_printerr ("ERROR: %s\nDEBUG MESSAGE: %s\n", s, dbg);
	}

	gtk_dialog_run (GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);
	g_free (s);
}

static void
show_missing_known_element_error (GtkWindow *win, gchar *description,
	gchar *element, gchar *plugin, gchar *module)
{
	show_error_dialog (win, NULL,
            _("Could not create the GStreamer %s element.\n"
	      "Please install the '%s' plug-in from the '%s' module.\n"
	      "Verify that the installation is correct by running\n"
              "    gst-inspect-0.10 %s\n"
	      "and then restart gnome-sound-recorder."),
            description, plugin, module, element);
}

static void
show_profile_error (GtkWindow *win, gchar *debug, gchar *description,
	const char *profile)
{
	gchar *first;

	first = g_strdup_printf (description, profile);
	show_error_dialog (win, debug, "%s%s", first,
		  _("Please verify its settings.\n"
		    "You may be missing the necessary plug-ins."));
	g_free (first);
}
/* Why do we need this? when a bin changes from READY => NULL state, its
 * bus is set to flushing and we're unlikely to ever see any of its messages
 * if the bin's state reaches NULL before we/the watch in the main thread
 * collects them. That's why we set the state to READY first, process all
 * messages 'manually', and then finally set it to NULL. This makes sure
 * our state-changed handler actually gets to see all the state changes */

static void
set_pipeline_state_to_null (GstElement *pipeline)
{
	GstMessage *msg;
	GstState cur_state, pending;
	GstBus *bus;

	gst_element_get_state (pipeline, &cur_state, &pending, 0);

	if (cur_state == GST_STATE_NULL && pending == GST_STATE_VOID_PENDING)
		return;

	if (cur_state == GST_STATE_NULL && pending != GST_STATE_VOID_PENDING) {
		gst_element_set_state (pipeline, GST_STATE_NULL);
		return;
	}

	gst_element_set_state (pipeline, GST_STATE_READY);
	gst_element_get_state (pipeline, NULL, NULL, -1);

	bus = gst_element_get_bus (pipeline);
	while ((msg = gst_bus_pop (bus))) {
		gst_bus_async_signal_func (bus, msg, NULL);
	}
	gst_object_unref (bus);

	gst_element_set_state (pipeline, GST_STATE_NULL);
	/* maybe we should be paranoid and do _get_state() and check for
	 * the return value here, but then errors in shutdown should be
	 * rather unlikely */
}


static void
shutdown_pipeline (GSRWindowPipeline *pipe)
{
	gst_bus_set_flushing (pipe->bus, TRUE);
	gst_bus_remove_signal_watch (pipe->bus);
	gst_element_set_state (pipe->pipeline, GST_STATE_NULL);
	gst_object_unref (pipe->pipeline);	
	gst_object_unref (pipe->bus);
}

static char *
seconds_to_string (guint seconds)
{
	int hour, min, sec;
	
	min = (seconds / 60);
	hour = min / 60;
	min -= (hour * 60);
	sec = seconds - ((hour * 3600) + (min * 60));

	if (hour > 0) {
		return g_strdup_printf ("%d:%02d:%02d", hour, min, sec);
	} else {
		return g_strdup_printf ("%d:%02d", min, sec);
	}
}	

static char *
seconds_to_full_string (guint seconds)
{
	long days, hours, minutes;
	char *time = NULL;
	const char *minutefmt;
	const char *hourfmt;
	const char *secondfmt;

	days    = seconds / (60 * 60 * 24);
	hours   = (seconds / (60 * 60));
	minutes = (seconds / 60) - ((days * 24 * 60) + (hours * 60));
	seconds = seconds % 60;

	minutefmt = ngettext ("%ld minute", "%ld minutes", minutes);
	hourfmt = ngettext ("%ld hour", "%ld hours", hours);
	secondfmt = ngettext ("%ld second", "%ld seconds", seconds);

	if (hours > 0) {
		if (minutes > 0)
			if (seconds > 0) {
				char *fmt;
				/* Translators: the format is "X hours, X minutes and X seconds" */
				fmt = g_strdup_printf (_("%s, %s and %s"), hourfmt, minutefmt, secondfmt);
				time = g_strdup_printf (fmt, hours, minutes, seconds);
				g_free (fmt);
			} else {
				char *fmt;
				/* Translators: the format is "X hours and X minutes" */
				fmt = g_strdup_printf (_("%s and %s"), hourfmt, minutefmt);
				time = g_strdup_printf (fmt, hours, minutes);
				g_free (fmt);
			}
		else
			if (seconds > 0) {
				char *fmt;
				/* Translators: the format is "X minutes and X seconds" */
				fmt = g_strdup_printf (_("%s and %s"), minutefmt, secondfmt);
				time = g_strdup_printf (fmt, minutes, seconds);
				g_free (fmt);
			} else {
				time = g_strdup_printf (minutefmt, minutes);
			}
	} else {
		if (minutes > 0) {
			if (seconds > 0) {
				char *fmt;
				/* Translators: the format is "X minutes and X seconds" */
				fmt = g_strdup_printf (_("%s and %s"), minutefmt, secondfmt);
				time = g_strdup_printf (fmt, minutes, seconds);
				g_free (fmt);
			} else {
				time = g_strdup_printf (minutefmt, minutes);
			}

		} else {
			time = g_strdup_printf (secondfmt, seconds);
		}
	}

	return time;
}

static void
set_action_sensitive (GSRWindow  *window,
		      const char *name,
		      gboolean    sensitive)
{
	GtkAction *action = gtk_action_group_get_action (window->priv->action_group,
							 name);
	gtk_action_set_sensitive (action, sensitive);
}

static void
file_new_cb (GtkAction *action,
	     GSRWindow *window)
{
	gsr_open_window (NULL);
}

static void
file_open_cb (GtkAction *action,
	      GSRWindow *window)
{
	GtkWidget *file_chooser;
	gchar *directory;
	gchar *locale_directory = NULL;
	gint response;

	g_return_if_fail (GSR_IS_WINDOW (window));

	file_chooser = gtk_file_chooser_dialog_new (_("Open a File"),
						    GTK_WINDOW (window),
						    GTK_FILE_CHOOSER_ACTION_OPEN,
						    GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
						    GTK_STOCK_OPEN, GTK_RESPONSE_OK,
						    NULL);

	directory = gconf_client_get_string (gconf_client, KEY_OPEN_DIR, NULL);

	if (directory != NULL && *directory != 0) {
		locale_directory = g_filename_from_utf8 (directory, -1, NULL, NULL, NULL);
		if (!locale_directory || !g_file_test (locale_directory, G_FILE_TEST_EXISTS))
			locale_directory = g_strdup (directory);
		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (file_chooser),
		                                     locale_directory);
		g_free (locale_directory);
	}

	response = gtk_dialog_run (GTK_DIALOG (file_chooser));

	if (response == GTK_RESPONSE_OK) {
		gchar *name;
		gchar *utf8_name = NULL;

		name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (file_chooser));
		if (name) {
			gchar *dirname;

			utf8_name = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
			dirname = g_path_get_dirname (utf8_name);
			gconf_client_set_string (gconf_client, KEY_OPEN_DIR, dirname, NULL);
			g_free (dirname);
			g_free (utf8_name);

			if (window->priv->has_file == TRUE) {
				/* Just open a new window with the file */
				gsr_open_window (name);
			} else {
				/* Set the file in this window */
				g_object_set (G_OBJECT (window), "location", name, NULL);
				window->priv->dirty = FALSE;
			}
		
			g_free (name);
		}
    }

	gtk_widget_destroy (file_chooser);
	g_free (directory);
}

static void
file_open_recent_cb (GtkRecentChooser *chooser,
		     GSRWindow *window)
{
	gchar *uri;
	gchar *filename;

	uri = gtk_recent_chooser_get_current_uri (chooser);
	g_return_if_fail (uri != NULL);

	if (!g_str_has_prefix (uri, "file://"))
		return;

	filename = g_filename_from_uri (uri, NULL, NULL);
	if (filename == NULL)
		goto out;

	if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
		gchar *filename_utf8;
		GtkWidget *dlg;

		filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
		dlg = gtk_message_dialog_new (GTK_WINDOW (window),
					      GTK_DIALOG_MODAL,
					      GTK_MESSAGE_ERROR,
					      GTK_BUTTONS_OK,
					      _("Unable to load file:\n%s"), filename_utf8);

		gtk_widget_show (dlg);
		gtk_dialog_run (GTK_DIALOG (dlg));
		gtk_widget_destroy (dlg);

		gtk_recent_manager_remove_item (gtk_recent_manager_get_default (), uri, NULL);

		g_free (filename_utf8);
		goto out;
  	}

	if (window->priv->has_file == TRUE) {
		/* Just open a new window with the file */
		gsr_open_window (filename);
	} else {
		/* Set the file in this window */
		g_object_set (G_OBJECT (window), "location", filename, NULL);
		window->priv->dirty = FALSE;
	}

 out:
	g_free (filename);
	g_free (uri);
}

#if 0
static gboolean
cb_iterate (GstBin  *bin,
	    gpointer data)
{
	src = gst_element_get_child (bin, "sink");
	sink = gst_element_get_child (bin, "sink");
	
	if (src && sink) {
		gint64 pos, tot, enc;
		GstFormat fmt = GST_FORMAT_BYTES;

		gst_element_query (src, GST_QUERY_POSITION, &fmt, &pos);
		gst_element_query (src, GST_QUERY_TOTAL, &fmt, &tot);
		gst_element_query (sink, GST_QUERY_POSITION, &fmt, &enc);

		g_print ("Iterate: %lld/%lld -> %lld\n", pos, tot, enc);
	} else
		g_print ("Iterate ?\n");

	/* we don't do anything here */
	return FALSE;
}
#endif

static gboolean
handle_ebusy_error (GSRWindow *window)
{
	g_return_val_if_fail (window->priv->ebusy_pipeline != NULL, FALSE);

	gst_element_set_state (window->priv->ebusy_pipeline, GST_STATE_NULL);
	gst_element_get_state (window->priv->ebusy_pipeline, NULL, NULL, -1);
	gst_element_set_state (window->priv->ebusy_pipeline, GST_STATE_PLAYING);

	/* Try only once */
	return FALSE;
}

static GstElement *
notgst_element_get_toplevel (GstElement * element)
{
	g_return_val_if_fail (element != NULL, NULL);
	g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);

	do {
		GstElement *parent;

		parent = (GstElement *) gst_element_get_parent (element);

		if (parent == NULL)
			break;

		gst_object_unref (parent);
		element = parent;
	} while (1);

	return element;
}

static void
pipeline_error_cb (GstBus * bus, GstMessage * msg, GSRWindow * window)
{
	GstElement *pipeline;
	GError *error = NULL;
	gchar *dbg = NULL;

	g_return_if_fail (GSR_IS_WINDOW (window));

	gst_message_parse_error (msg, &error, &dbg);
	g_return_if_fail (error != NULL);

	pipeline = notgst_element_get_toplevel (GST_ELEMENT (msg->src));

	if (error->code == GST_RESOURCE_ERROR_BUSY) {
		if (window->priv->ebusy_timeout_id == 0) {
			set_action_sensitive (window, "FileSave", FALSE);
			set_action_sensitive (window, "FileSaveAs", FALSE);
			set_action_sensitive (window, "Play", FALSE);
			set_action_sensitive (window, "Record", FALSE);

			window->priv->ebusy_pipeline = pipeline;

			window->priv->ebusy_timeout_id = 
				g_timeout_add_seconds (EBUSY_TRY_AGAIN,
						       (GSourceFunc) handle_ebusy_error,
						       window);

			g_error_free (error);
			g_free (dbg);
			return;
		}
	}

	if (window->priv->ebusy_timeout_id) {
		g_source_remove (window->priv->ebusy_timeout_id);
		window->priv->ebusy_timeout_id = 0;
		window->priv->ebusy_pipeline = NULL;
	}


	/* set pipeline to NULL before showing error dialog to make sure
	 * the audio device is freed, in case any accessability software
	 * wants to make use of it to read out the error message */
	set_pipeline_state_to_null (pipeline);

	show_error_dialog (GTK_WINDOW (window), dbg, "%s", error->message);

	gdk_window_set_cursor (window->priv->main_vbox->window, NULL);

	set_action_sensitive (window, "Stop", FALSE);
	set_action_sensitive (window, "Play", TRUE);
	set_action_sensitive (window, "Record", TRUE);
	set_action_sensitive (window, "FileSave", TRUE);
	set_action_sensitive (window, "FileSaveAs", TRUE);
	gtk_widget_set_sensitive (window->priv->scale, TRUE);

	gtk_statusbar_pop (GTK_STATUSBAR (window->priv->statusbar),
			   window->priv->status_message_cid);
	gtk_statusbar_push (GTK_STATUSBAR (window->priv->statusbar),
			    window->priv->status_message_cid,
			    _("Ready"));

	g_error_free (error);
	g_free (dbg);
}

static GtkWidget *
gsr_dialog_add_button (GtkDialog *dialog,
		       const gchar *text,
		       const gchar *stock_id,
		       gint response_id)
{
	GtkWidget *button;

	g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL);
	g_return_val_if_fail (text != NULL, NULL);
	g_return_val_if_fail (stock_id != NULL, NULL);

	button = gtk_button_new_with_mnemonic (text);
	gtk_button_set_image (GTK_BUTTON (button),
			      gtk_image_new_from_stock (stock_id,
							GTK_ICON_SIZE_BUTTON));

	GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);

	gtk_widget_show (button);

	gtk_dialog_add_action_widget (dialog, button, response_id);

	return button;
}

static gboolean
replace_dialog (GtkWindow *parent,
		const gchar *message,
		const gchar *file_name)
{
	GtkWidget *message_dialog;
	gint ret;

	g_return_val_if_fail (file_name != NULL, FALSE);

	message_dialog = gtk_message_dialog_new (parent,
						 GTK_DIALOG_DESTROY_WITH_PARENT,
						 GTK_MESSAGE_QUESTION,
						 GTK_BUTTONS_NONE,
						 message,
						 file_name);
	/* Add cancel button */
	gtk_dialog_add_button (GTK_DIALOG (message_dialog),
			       GTK_STOCK_CANCEL,
			       GTK_RESPONSE_CANCEL);
	/* Add replace button */
	gsr_dialog_add_button (GTK_DIALOG (message_dialog), _("_Replace"),
			       GTK_STOCK_REFRESH,
			       GTK_RESPONSE_YES);

	gtk_dialog_set_default_response (GTK_DIALOG (message_dialog), GTK_RESPONSE_CANCEL);
	gtk_window_set_resizable (GTK_WINDOW (message_dialog), FALSE);
	ret = gtk_dialog_run (GTK_DIALOG (message_dialog));
	gtk_widget_destroy (GTK_WIDGET (message_dialog));

	return (ret == GTK_RESPONSE_YES);
}

static gboolean
replace_existing_file (GtkWindow *parent,
		      const gchar *file_name) 
{
	return replace_dialog (parent,
			       _("A file named \"%s\" already exists. \n"
			       "Do you want to replace it with the "
			       "one you are saving?"),
			       file_name);
}

static void
do_save_file (GSRWindow *window,
	      const char *_name)
{
	GSRWindowPrivate *priv;
	char *name;
	GFile *src, *dst;
	GError *error = NULL;

	priv = window->priv;

	if (window->priv->extension == NULL ||
	    g_str_has_suffix (_name, window->priv->extension))
		name = g_strdup (_name);
	else
		name = g_strdup_printf ("%s.%s", _name,
				window->priv->extension);
	if (g_file_test (name, G_FILE_TEST_EXISTS)) {
		char *utf8_name;
		utf8_name = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
		if (!replace_existing_file (GTK_WINDOW (window), utf8_name)) {
			g_free (utf8_name);
			return;
		}
		g_free (utf8_name);
	}
	src = g_file_new_for_path(priv->record_filename);
	dst = g_file_new_for_path(name);

	/* TODO: Show progress? Where? */
	if (g_file_copy(src, dst, G_FILE_COPY_OVERWRITE,
	                NULL, NULL, NULL, &error)) {
		g_object_set (G_OBJECT (window), "location", name, NULL);
		priv->dirty = FALSE;
		window->priv->saved = TRUE;
		if (window->priv->quit_after_save == TRUE) {
			gsr_window_close (window);
		}
	} else {
		char *utf8_name;
		utf8_name = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
		show_error_dialog (GTK_WINDOW (window), NULL,
			           _("Could not save the file \"%s\""), utf8_name);
		g_free (utf8_name);
	}

	g_object_unref(src);
	g_object_unref(dst);
	g_free (name);
}

static void
file_save_as_cb (GtkAction *action,
		 GSRWindow *window)
{
	GtkWidget *file_chooser;
	gchar *directory;
	gchar *locale_directory = NULL;
	gint response;

	g_return_if_fail (GSR_IS_WINDOW (window));

	file_chooser = gtk_file_chooser_dialog_new (_("Save file as"),
						    GTK_WINDOW (window),
						    GTK_FILE_CHOOSER_ACTION_SAVE,
						    GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
						    GTK_STOCK_SAVE, GTK_RESPONSE_OK,
						    NULL);

	directory = gconf_client_get_string (gconf_client, KEY_SAVE_DIR, NULL);
	if (directory != NULL && *directory != 0) {
		locale_directory = g_filename_from_utf8 (directory, -1, NULL, NULL, NULL);
		if (!locale_directory || !g_file_test (locale_directory, G_FILE_TEST_EXISTS))
			locale_directory = g_strdup (directory);
		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (file_chooser), locale_directory);
		g_free (locale_directory);
	}
	g_free (directory);

	if (window->priv->filename != NULL) {
		char *locale_basename;
		char *basename = NULL;
		gchar *filename, *filename_ext, *extension;
		gint length;

		locale_basename = g_path_get_basename (window->priv->filename);
		basename = g_filename_to_utf8 (locale_basename, -1, NULL, NULL, NULL);
		length = strlen (basename);
		extension = g_strrstr (basename, ".");

		if (extension != NULL) {
			length = length - strlen (extension);
		}

		filename = g_strndup (basename,length);
		if (window->priv->extension)
			filename_ext = g_strdup_printf ("%s.%s", filename,
						window->priv->extension);
		else
			filename_ext = g_strdup (filename);
		gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (file_chooser),
						   filename_ext);
		g_free (filename);
		g_free (filename_ext);
		g_free (basename);
		g_free (locale_basename);
	}

	response = gtk_dialog_run (GTK_DIALOG (file_chooser));

	if (response == GTK_RESPONSE_OK) {
		gchar *name;
		gchar *utf8_name = NULL;

		name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (file_chooser));
		if (name) {
			gchar *dirname;

			utf8_name= g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
			dirname = g_path_get_dirname (utf8_name);
			gconf_client_set_string (gconf_client, KEY_SAVE_DIR, dirname, NULL);
			g_free (dirname);
			g_free (utf8_name);
	
			do_save_file (window, name);
			g_free (name);
	    }
	}

	gtk_widget_destroy (file_chooser);
}

static void
file_save_cb (GtkAction *action,
	      GSRWindow *window)
{
	if (!window->priv->has_file) {
		file_save_as_cb (NULL, window);
	} else {
		do_save_file (window, window->priv->filename);
	}
}

static void
run_mixer_cb (GtkAction *action,
	       GSRWindow *window)
{
	char *mixer_path;
	char *argv[4] = {NULL, "--page", "recording",  NULL};
	GError *error = NULL;
	gboolean ret;

	/* Open the mixer */
	mixer_path = g_find_program_in_path ("gnome-volume-control");
	if (mixer_path == NULL) {
		show_error_dialog (GTK_WINDOW (window), NULL,
		                   _("%s is not installed in the path."),
		                   "gnome-volume-control");
		return;
	}

	argv[0] = mixer_path;
	ret = g_spawn_async (NULL, argv, NULL, 0, NULL, NULL, NULL, &error);
	if (ret == FALSE) {
		show_error_dialog (GTK_WINDOW (window), NULL,
		                   _("There was an error starting %s: %s"),
		                   mixer_path, error->message);
		g_error_free (error);
	}

	g_free (mixer_path);
}

gboolean
gsr_window_is_saved (GSRWindow *window)
{
	return window->priv->saved;
}

gboolean
gsr_discard_confirmation_dialog (GSRWindow *window, gboolean closing)
{
	GtkWidget *confirmation_dialog;
	AtkObject *atk_obj;
	gint response_id;
	gchar *utf8_filename = NULL;
	gboolean ret = TRUE;

	confirmation_dialog = gtk_message_dialog_new_with_markup (NULL,
								  GTK_DIALOG_MODAL,
								  GTK_MESSAGE_WARNING,
								  GTK_BUTTONS_NONE,
								  "<span weight=\"bold\" size=\"larger\">%s</span>",
								  closing ?
								    _("Save recording before closing?") :
								    _("Save recording?"));

	gtk_dialog_add_buttons (GTK_DIALOG (confirmation_dialog),
				closing ?
				  _("Close _without Saving") :
				  _("Continue _without Saving"),
				GTK_RESPONSE_YES,
				GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
				GTK_STOCK_SAVE_AS, GTK_RESPONSE_NO, NULL);

	gtk_window_set_title (GTK_WINDOW (confirmation_dialog), "");

	atk_obj = gtk_widget_get_accessible (confirmation_dialog);
	atk_object_set_name (atk_obj, _("Question"));

	response_id = gtk_dialog_run (GTK_DIALOG (confirmation_dialog));

	switch (response_id) {
		case GTK_RESPONSE_NO:
		/* hiding the confirmation dialog allows the user to
		see only one dialog at a time if the user click cancel
		in the file dialog, they won't expect to return to the
		confirmation dialog*/
			gtk_widget_hide (confirmation_dialog);
			file_save_as_cb (NULL, window);
			ret = window->priv->has_file;
			break;

		case GTK_RESPONSE_YES:
			ret = TRUE;
			break;

		case GTK_RESPONSE_CANCEL:
		default:
			ret = FALSE;
			break;
	}

	gtk_widget_destroy (confirmation_dialog);

	return ret;
}

static GtkWidget *
make_title_label (const char *text)
{
	GtkWidget *label;
	char *fulltext;
	
	fulltext = g_strdup_printf ("<span weight=\"bold\">%s</span>", text);
	label = gtk_label_new (fulltext);
	g_free (fulltext);

	gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.0);
	return label;
}

static GtkWidget *
make_info_label (const char *text)
{
	GtkWidget *label;

	label = gtk_label_new (text);
	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
	gtk_label_set_selectable (GTK_LABEL (label), TRUE);
	gtk_label_set_line_wrap (GTK_LABEL (label), GTK_WRAP_WORD);

	return label;
}

static void
pack_table_widget (GtkWidget *table,
		   GtkWidget *widget,
		   int left,
		   int top)
{
	gtk_table_attach (GTK_TABLE (table), widget,
			  left, left + 1, top, top + 1,
			  GTK_FILL, GTK_FILL, 0, 0);
}

struct _file_props {
	GtkWidget *dialog;

	GtkWidget *dirname;
	GtkWidget *filename;
	GtkWidget *size;
	GtkWidget *length;
	GtkWidget *samplerate;
	GtkWidget *channels;
	GtkWidget *bitrate;
};

static void
fill_in_information (GSRWindow *window,
		     struct _file_props *fp)
{
	struct stat buf;
	guint64 file_size = 0;
	gchar *text, *name;
	gchar *utf8_name = NULL;
	gint n_channels, bitrate, samplerate;

	/* dirname */
	if (window->priv->dirty) {
		gtk_label_set_text (GTK_LABEL (fp->dirname), "");
	} else {
		name = g_path_get_dirname (window->priv->filename);
		text = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
		gtk_label_set_text (GTK_LABEL (fp->dirname), text);
		g_free (text);
		g_free (name);
	}

	/* filename */
	name = g_path_get_basename (window->priv->filename);
	utf8_name = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);

	if (window->priv->dirty) {
		text = g_strdup_printf (_("%s (Has not been saved)"), utf8_name);
	} else {
		text = g_strdup (utf8_name);
	}
	gtk_label_set_text (GTK_LABEL (fp->filename), text);
	g_free (text);
	g_free (utf8_name);
	g_free (name);
	
	/* Size */
	if (stat (window->priv->working_file, &buf) == 0) {
		gchar *human;

		file_size = (guint64) buf.st_size;
		human = g_format_size_for_display (file_size);

		text = g_strdup_printf (ngettext ("%s (%llu byte)", "%s (%llu bytes)", 
		                        file_size), human, file_size);
		g_free (human);
	} else {
		text = g_strdup (_("Unknown size"));
	}
	gtk_label_set_text (GTK_LABEL (fp->size), text);
	g_free (text);

	/* FIXME: Set up and run our own pipeline 
	 *        till we can get the info */
	/* Length */
	if (window->priv->len_secs == 0) {
		text = g_strdup (_("Unknown"));
	} else {
		text = seconds_to_full_string (window->priv->len_secs);
	}
	gtk_label_set_text (GTK_LABEL (fp->length), text);
	g_free (text);

	/* sample rate */
	samplerate = g_atomic_int_get (&window->priv->atomic.samplerate);
	if (samplerate == 0) {
		text = g_strdup (_("Unknown"));
	} else {
		text = g_strdup_printf (_("%.1f kHz"), (float) samplerate / 1000);
	}
	gtk_label_set_text (GTK_LABEL (fp->samplerate), text);
	g_free (text);

	/* bit rate */
	bitrate = g_atomic_int_get (&window->priv->atomic.bitrate);
	if (bitrate > 0) {
		text = g_strdup_printf (_("%.0f kb/s"), (float) bitrate / 1000);
	} else if (window->priv->len_secs > 0 && file_size > 0) {
		bitrate = (file_size * 8.0) / window->priv->len_secs;
		text = g_strdup_printf (_("%.0f kb/s (Estimated)"),
		                        (float) bitrate / 1000);
	} else {
		text = g_strdup (_("Unknown"));
	}
	gtk_label_set_text (GTK_LABEL (fp->bitrate), text);
	g_free (text);

	/* channels */
	n_channels = g_atomic_int_get (&window->priv->atomic.n_channels);
	switch (n_channels) {
	case 0:
		text = g_strdup (_("Unknown"));
		break;
	case 1:
		text = g_strdup (_("1 (mono)"));
		break;
	case 2:
		text = g_strdup (_("2 (stereo)"));
		break;
	default:
		text = g_strdup_printf ("%d", n_channels);
		break;
	}
	gtk_label_set_text (GTK_LABEL (fp->channels), text);
	g_free (text);
}

static void
dialog_closed_cb (GtkDialog *dialog,
		  guint response_id,
		  struct _file_props *fp)
{
	gtk_widget_destroy (fp->dialog);
	g_free (fp);
}

static void
file_properties_cb (GtkAction *action,
		    GSRWindow *window)
{
	GtkWidget *dialog, *vbox, *inner_vbox, *hbox, *table, *label;
	char *title, *shortname;
	struct _file_props *fp;
	shortname = g_path_get_basename (window->priv->filename);
	title = g_strdup_printf (_("%s Information"), shortname);
	g_free (shortname);

	dialog = gtk_dialog_new_with_buttons (title, GTK_WINDOW (window),
					      GTK_DIALOG_DESTROY_WITH_PARENT,
					      GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
	g_free (title);
	gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
	gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
	gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
	gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 2);
	fp = g_new (struct _file_props, 1);
	fp->dialog = dialog;

	g_signal_connect (G_OBJECT (dialog), "response",
			  G_CALLBACK (dialog_closed_cb), fp);

	vbox = gtk_vbox_new (FALSE, 18);
	gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
	gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox, TRUE, TRUE, 0);

	inner_vbox = gtk_vbox_new (FALSE, 6);
	gtk_box_pack_start (GTK_BOX (vbox), inner_vbox, FALSE, FALSE,0);

	label = make_title_label (_("File Information"));
	gtk_box_pack_start (GTK_BOX (inner_vbox), label, FALSE, FALSE, 0);

	hbox = gtk_hbox_new (FALSE, 0);
	gtk_box_pack_start (GTK_BOX (inner_vbox), hbox, TRUE, TRUE, 0);

	label = gtk_label_new ("    ");
	gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);

	/* File properties */	
	table = gtk_table_new (3, 2, FALSE);
	gtk_table_set_col_spacings (GTK_TABLE (table), 12);
	gtk_table_set_row_spacings (GTK_TABLE (table), 6);
	gtk_box_pack_start (GTK_BOX (hbox), table, TRUE, TRUE, 0);

	label = make_info_label (_("Folder:"));
	pack_table_widget (table, label, 0, 0);

	fp->dirname = make_info_label ("");
	pack_table_widget (table, fp->dirname, 1, 0);

	label = make_info_label (_("Filename:"));
	pack_table_widget (table, label, 0, 1);

	fp->filename = make_info_label ("");
	pack_table_widget (table, fp->filename, 1, 1);

	label = make_info_label (_("File size:"));
	pack_table_widget (table, label, 0, 2);

	fp->size = make_info_label ("");
	pack_table_widget (table, fp->size, 1, 2);

	inner_vbox = gtk_vbox_new (FALSE, 6);
	gtk_box_pack_start (GTK_BOX (vbox), inner_vbox, FALSE, FALSE, 0);

	label = make_title_label (_("Audio Information"));
	gtk_box_pack_start (GTK_BOX (inner_vbox), label, FALSE, FALSE, 0);

	hbox = gtk_hbox_new (FALSE, 0);
	gtk_box_pack_start (GTK_BOX (inner_vbox), hbox, TRUE, TRUE, 0);

	label = gtk_label_new ("    ");
	gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);

	/* Audio info */
	table = gtk_table_new (4, 2, FALSE);
	gtk_table_set_col_spacings (GTK_TABLE (table), 12);
	gtk_table_set_row_spacings (GTK_TABLE (table), 6);
	gtk_box_pack_start (GTK_BOX (hbox), table, TRUE, TRUE, 0);

	label = make_info_label (_("File duration:"));
	pack_table_widget (table, label, 0, 0);

	fp->length = make_info_label ("");
	pack_table_widget (table, fp->length, 1, 0);

	label = make_info_label (_("Number of channels:"));
	pack_table_widget (table, label, 0, 1);

	fp->channels = make_info_label ("");
	pack_table_widget (table, fp->channels, 1, 1);

	label = make_info_label (_("Sample rate:"));
	pack_table_widget (table, label, 0, 2);

	fp->samplerate = make_info_label ("");
	pack_table_widget (table, fp->samplerate, 1, 2);

	label = make_info_label (_("Bit rate:"));
	pack_table_widget (table, label, 0, 3);

	fp->bitrate = make_info_label ("");
	pack_table_widget (table, fp->bitrate, 1, 3);

	fill_in_information (window, fp);
	gtk_widget_show_all (dialog);
}

void
gsr_window_close (GSRWindow *window)
{
	gtk_widget_destroy (GTK_WIDGET (window));
}

static void
file_close_cb (GtkAction *action,
	       GSRWindow *window)
{
	if (gsr_window_is_saved (window) || gsr_discard_confirmation_dialog (window, TRUE))
		gsr_window_close (window);
}

static void
quit_cb (GtkAction *action,
	 GSRWindow *window)
{
	gsr_quit ();
}

static void
help_contents_cb (GtkAction *action,
	          GSRWindow *window)
{
	GError *error = NULL;

	gtk_show_uri (gtk_window_get_screen (GTK_WINDOW (window)),
		      "ghelp:gnome-sound-recorder",
		      gtk_get_current_event_time (), &error);

	if (error != NULL)
	{
		g_warning ("%s", error->message);

		g_error_free (error);
	}
}

static void
about_cb (GtkAction *action,
	  GSRWindow *window)
{
	const char * const authors[] = {"Iain Holmes <iain@prettypeople.org>", 
		"Ronald Bultje <rbultje@ronald.bitfreak.net>", 
		"Johan Dahlin  <johan@gnome.org>", 
		"Tim-Philipp M\303\274ller <tim centricular net>",
		NULL};
	const char * const documenters[] = {"Sun Microsystems", NULL};
 
	gtk_show_about_dialog (GTK_WINDOW (window),
			       "name", _("Sound Recorder"),
			       "version", VERSION,
			       "copyright", "Copyright \xc2\xa9 2002 Iain Holmes",
			       "comments", _("A sound recorder for GNOME\n gnome-multimedia@gnome.org"),
			       "authors", authors,
			       "documenters", documenters,
			       "logo-icon-name", "gnome-sound-recorder",
			       NULL);
}

static void
play_cb (GtkAction *action,
	 GSRWindow *window)
{
	GSRWindowPrivate *priv = window->priv;

	if (priv->has_file == FALSE && !priv->working_file)
		return;

	if (priv->play) {
		shutdown_pipeline (priv->play);
	}

	if ((priv->play = make_play_pipeline (window))) {
		gchar *uri;
		gchar *usefile;
		GFile *file;

		if(priv->has_file == FALSE && priv->working_file) usefile = priv->working_file;
		else usefile = priv->filename;

		file = g_file_new_for_commandline_arg (usefile);
		uri = g_file_get_uri (file);
		g_object_unref (file);
		g_object_set (window->priv->play->pipeline, "uri", uri, NULL);
		g_free (uri);

		if (priv->record && priv->record->state == GST_STATE_PLAYING) {
			set_pipeline_state_to_null (priv->record->pipeline);
		}

		gst_element_set_state (priv->play->pipeline, GST_STATE_PLAYING);
	}
}

static void
stop_cb (GtkAction *action,
	 GSRWindow *window)
{
	GSRWindowPrivate *priv = window->priv;

	/* Work out what's playing */
	if (priv->play && priv->play->state >= GST_STATE_PAUSED) {
		GST_DEBUG ("Stopping play pipeline");
		set_pipeline_state_to_null (priv->play->pipeline);
	} else if (priv->record && priv->record->state == GST_STATE_PLAYING) {
		GST_DEBUG ("Stopping recording source");
		/* GstBaseSrc will automatically send an EOS when stopping */
		gst_element_set_state (priv->record->src, GST_STATE_NULL);
		gst_element_get_state (priv->record->src, NULL, NULL, -1);
		gst_element_set_locked_state (priv->record->src, TRUE);

		GST_DEBUG ("Stopping recording pipeline");
		set_pipeline_state_to_null (priv->record->pipeline);
		gtk_widget_set_sensitive (window->priv->level, FALSE);
		gtk_widget_set_sensitive (window->priv->volume_label, FALSE);
		gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (window->priv->level), 0.0);
	}
}

static void
record_cb (GtkAction *action,
	   GSRWindow *window)
{
	if (!gsr_window_is_saved(window) && !gsr_discard_confirmation_dialog (window, FALSE))
		return;

	GSRWindowPrivate *priv = window->priv;

	if (priv->record) {
		char *current_source;
		shutdown_pipeline (priv->record);
		if (!make_record_source (window))
			return;
		current_source = gtk_combo_box_get_active_text (GTK_COMBO_BOX (window->priv->input));
		fill_record_input (window, current_source);
	}

	if ((priv->record = make_record_pipeline (window))) {
		window->priv->len_secs = 0;
		window->priv->saved = FALSE;

		g_print ("%s", priv->record_filename);
		g_object_set (G_OBJECT (priv->record->sink),
			      "location", priv->record_filename,
			      NULL);

		gst_element_set_state (priv->record->pipeline, GST_STATE_PLAYING);
		gtk_widget_set_sensitive (window->priv->level, TRUE);
		gtk_widget_set_sensitive (window->priv->volume_label, TRUE);

	}
}

static gboolean
seek_started (GtkRange *range,
	      GdkEventButton *event,
	      GSRWindow *window)
{
	g_return_val_if_fail (window->priv != NULL, FALSE);

	window->priv->seek_in_progress = TRUE;
	return FALSE;
}

static gboolean
seek_to (GtkRange *range,
	 GdkEventButton *gdkevent,
	 GSRWindow *window)
{
	gdouble value;
	gint64 time;

	if (window->priv->play->state < GST_STATE_PLAYING)
		return FALSE;

	value = range->adjustment->value;
	time = ((value / 100.0) * window->priv->len_secs) * GST_SECOND;

	gst_element_seek (window->priv->play->pipeline, 1.0, GST_FORMAT_TIME,
	                  GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, time,
	                  GST_SEEK_TYPE_NONE, 0);

	window->priv->seek_in_progress = FALSE;

	return FALSE;
}

static gboolean
play_tick_callback (GSRWindow *window)
{
	GstElement *playbin;
	GstFormat format = GST_FORMAT_TIME;
	gint64 val = -1;

	g_return_val_if_fail (window->priv->play != NULL, FALSE);
	g_return_val_if_fail (window->priv->play->pipeline != NULL, FALSE);

	playbin = window->priv->play->pipeline;

	/* This check stops us from doing an unnecessary query */
	if (window->priv->play->state != GST_STATE_PLAYING) {
		GST_DEBUG ("pipeline in wrong state: %s",
			gst_element_state_get_name (window->priv->play->state));
		window->priv->play->tick_id = 0;
		return FALSE;
	}

	if (gst_element_query_duration (playbin, &format, &val) && val != -1) {
		gchar *len_str;

		window->priv->len_secs = val / GST_SECOND;

		len_str = seconds_to_full_string (window->priv->len_secs);
		gtk_label_set_text (GTK_LABEL (window->priv->length_label),
				    len_str);
		g_free (len_str);
	} else {
		if (window->priv->get_length_attempts <= 0) {
			/* Attempts to get length ran out. */
			gtk_label_set_text (GTK_LABEL (window->priv->length_label), _("Unknown"));
		} else {
			--window->priv->get_length_attempts;
		}
	}

	if (window->priv->seek_in_progress) {
		GST_DEBUG ("seek in progress, try again later");
		return TRUE;
	}

	if (window->priv->len_secs == 0) {
		GST_DEBUG ("no duration, try again later");
		return TRUE;
	}

	if (gst_element_query_position (playbin, &format, &val) && val != -1) {
		gdouble pos, len, percentage;

		pos = (gdouble) (val - (val % GST_SECOND));
		len = (gdouble) window->priv->len_secs * GST_SECOND;
		percentage = pos / len * 100.0;
		
		gtk_adjustment_set_value (GTK_RANGE (window->priv->scale)->adjustment,
		                          CLAMP (percentage + 0.5, 0.0, 100.0));
	} else {
		GST_DEBUG ("failed to query position");
	}

	return TRUE;
}

static gboolean
record_tick_callback (GSRWindow *window)
{
	GstElement *pipeline;
	GstFormat format = GST_FORMAT_TIME;
	gint64 val = -1;
	gint secs;

	/* This check stops us from doing an unnecessary query */
	if (window->priv->record->state != GST_STATE_PLAYING) {
		GST_DEBUG ("pipeline in wrong state: %s",
			gst_element_state_get_name (window->priv->record->state));
		return FALSE;
    }

	if (window->priv->seek_in_progress)
		return TRUE;

	pipeline = window->priv->record->pipeline;

	if (gst_element_query_position (pipeline, &format, &val) && val != -1) {
		gchar* len_str;

		secs = val / GST_SECOND;
		
		len_str = seconds_to_full_string (secs);
		window->priv->len_secs = secs;
		gtk_label_set_text (GTK_LABEL (window->priv->length_label),
				    len_str);
		g_free (len_str);
	} else {
		GST_DEBUG ("failed to query position");
	}

	return TRUE;
}

static void
play_state_changed_cb (GstBus * bus, GstMessage * msg, GSRWindow * window)
{
	GstState new_state;

	gst_message_parse_state_changed (msg, NULL, &new_state, NULL);

	g_return_if_fail (GSR_IS_WINDOW (window));

	/* we are only interested in state changes of the top-level pipeline */
	if (msg->src != GST_OBJECT (window->priv->play->pipeline))
		return;

	window->priv->play->state = new_state;

	GST_DEBUG ("playbin state: %s", gst_element_state_get_name (new_state));

	switch (new_state) {
	case GST_STATE_PLAYING:
		if (window->priv->play->tick_id == 0) {
			window->priv->play->tick_id =
				g_timeout_add (200, (GSourceFunc) play_tick_callback, window);
		}

		set_action_sensitive (window, "Stop", TRUE);
		set_action_sensitive (window, "Play", FALSE);
		set_action_sensitive (window, "Record", FALSE);
		set_action_sensitive (window, "FileSave", FALSE);
		set_action_sensitive (window, "FileSaveAs", FALSE);
		gtk_widget_set_sensitive (window->priv->scale, TRUE);

		gtk_statusbar_pop (GTK_STATUSBAR (window->priv->statusbar),
				   window->priv->status_message_cid);
		gtk_statusbar_push (GTK_STATUSBAR (window->priv->statusbar),
				    window->priv->status_message_cid,
				    _("Playing..."));

		if (window->priv->ebusy_timeout_id) {
			g_source_remove (window->priv->ebusy_timeout_id);
			window->priv->ebusy_timeout_id = 0;
			window->priv->ebusy_pipeline = NULL;
		}
		break;

	case GST_STATE_READY:
		if (window->priv->play->tick_id > 0) {
			g_source_remove (window->priv->play->tick_id);
			window->priv->play->tick_id = 0;
		}
		gtk_adjustment_set_value (GTK_RANGE (window->priv->scale)->adjustment, 0.0);
		gtk_widget_set_sensitive (window->priv->scale, FALSE);
		/* fallthrough */
	case GST_STATE_PAUSED:
		set_action_sensitive (window, "Stop", FALSE);
		set_action_sensitive (window, "Play", TRUE);
		set_action_sensitive (window, "Record", TRUE);
		set_action_sensitive (window, "FileSave", TRUE);
		set_action_sensitive (window, "FileSaveAs", TRUE);

		gtk_statusbar_pop (GTK_STATUSBAR (window->priv->statusbar),
				   window->priv->status_message_cid);
		gtk_statusbar_push (GTK_STATUSBAR (window->priv->statusbar),
				    window->priv->status_message_cid,
				    _("Ready"));
		break;
	default:
		break;
	}
}

static void
pipeline_deep_notify_caps_cb (GstObject  *pipeline,
                              GstObject  *object,
                              GParamSpec *pspec,
                              GSRWindow  *window)
{
	GSRWindowPrivate *priv;
	GstPadDirection direction;

	if (!GST_IS_PAD (object))
		return;

	priv = window->priv;
	if (priv->play && pipeline == GST_OBJECT_CAST (priv->play->pipeline)) {
		direction = GST_PAD_SRC;
	} else if (priv->record && pipeline == GST_OBJECT_CAST (priv->record->pipeline)) {
		direction = GST_PAD_SINK;
	} else {
		g_return_if_reached ();
	}

	if (GST_PAD_DIRECTION (object) == direction) {
		GstObject *pad_parent;

		pad_parent = gst_object_get_parent (object);
		if (GST_IS_ELEMENT (pad_parent)) {
			GstElementFactory *factory;
			GstElement *element;
			const gchar *klass;

			element = GST_ELEMENT_CAST (pad_parent);
			if ((factory = gst_element_get_factory (element)) &&
			    (klass = gst_element_factory_get_klass (factory)) &&
			    strstr (klass, "Audio") &&
			    (strstr (klass, "Decoder") || strstr (klass, "Encoder"))) {
				GstCaps *caps;

				caps = gst_pad_get_negotiated_caps (GST_PAD_CAST (object));
				if (caps) {
					GstStructure *s;
					gint val;

					s = gst_caps_get_structure (caps, 0);
					if (gst_structure_get_int (s, "channels", &val)) {
						gst_atomic_int_set (&priv->atomic.n_channels, val);
					}
					if (gst_structure_get_int (s, "rate", &val)) {
						gst_atomic_int_set (&priv->atomic.samplerate, val);
					}
					gst_caps_unref (caps);
				}
			}
		}
		if (pad_parent)
			gst_object_unref (pad_parent);
	}
}

/* callback for when the recording profile has been changed */
static void
profile_changed_cb (GObject *object, GSRWindow *window)
{
	GMAudioProfile *profile;
	gchar *id;

	g_return_if_fail (GTK_IS_COMBO_BOX (object));

	profile = gm_audio_profile_choose_get_active (GTK_WIDGET (object));

	if (profile != NULL) {
		id = g_strdup (gm_audio_profile_get_id (profile));
		GST_DEBUG ("profile changed to %s", GST_STR_NULL (id));
		gconf_client_set_string (gconf_client, KEY_LAST_PROFILE_ID, id, NULL);
		g_free (id);
	}
}

static void
play_eos_msg_cb (GstBus * bus, GstMessage * msg, GSRWindow * window)
{
	g_return_if_fail (GSR_IS_WINDOW (window));

	GST_DEBUG ("EOS");

	stop_cb (NULL, window);
}

static GSRWindowPipeline *
make_play_pipeline (GSRWindow *window)
{
	GSRWindowPipeline *obj;
	GstElement *playbin;
	GstElement *audiosink;

	audiosink = gst_element_factory_make ("gconfaudiosink", "sink");
	if (audiosink == NULL) {
		show_missing_known_element_error (NULL,
		    _("GConf audio output"), "gconfaudiosink", "gconfelements",
		    "gst-plugins-good");
		return NULL;
	}

	playbin = gst_element_factory_make ("playbin", "playbin");
	if (playbin == NULL) {
		gst_object_unref (audiosink);
		show_missing_known_element_error (NULL,
		    _("Playback"), "playbin", "playback",
		    "gst-plugins-base");
		return NULL;
	}

	obj = g_new0 (GSRWindowPipeline, 1);
	obj->pipeline = playbin;
	obj->src = NULL; /* don't need that for playback */
	obj->sink = NULL; /* don't need that for playback */

	g_object_set (playbin, "audio-sink", audiosink, NULL);

	/* we ultimately want to find out the caps on the decoder's source pad */
	g_signal_connect (playbin, "deep-notify::caps",
	                  G_CALLBACK (pipeline_deep_notify_caps_cb),
	                  window);

	obj->bus = gst_element_get_bus (playbin);

	gst_bus_add_signal_watch_full (obj->bus, G_PRIORITY_HIGH);

	g_signal_connect (obj->bus, "message::state-changed",
	                  G_CALLBACK (play_state_changed_cb),
	                  window);

	g_signal_connect (obj->bus, "message::error",
	                  G_CALLBACK (pipeline_error_cb),
	                  window);

	g_signal_connect (obj->bus, "message::eos",
	                  G_CALLBACK (play_eos_msg_cb),
	                  window);
	
	return obj;
}

static void
record_eos_msg_cb (GstBus * bus, GstMessage * msg, GSRWindow * window)
{
	g_return_if_fail (GSR_IS_WINDOW (window));

	GST_DEBUG ("EOS. Finished recording");

	/* FIXME: this was READY before (why?) */
	set_pipeline_state_to_null (window->priv->record->pipeline);

	g_free (window->priv->working_file);
	window->priv->working_file = g_strdup (window->priv->record_filename);

	g_free (window->priv->filename);
	window->priv->filename = g_strdup (window->priv->record_filename);

	window->priv->has_file = TRUE;
}

extern int gsr_sample_count;

static gboolean
record_start (gpointer user_data) 
{
	GSRWindow *window = GSR_WINDOW (user_data);
	gchar *name;

	g_assert (window->priv->tick_id == 0);

	window->priv->get_length_attempts = 16;
	window->priv->tick_id = g_timeout_add (200, (GSourceFunc) record_tick_callback, window);

	set_action_sensitive (window, "Stop", TRUE);
	set_action_sensitive (window, "Play", FALSE);
	set_action_sensitive (window, "Record", FALSE);
	set_action_sensitive (window, "FileSave", FALSE);
	set_action_sensitive (window, "FileSaveAs", FALSE);
	gtk_widget_set_sensitive (window->priv->scale, FALSE);

	gtk_statusbar_pop (GTK_STATUSBAR (window->priv->statusbar),
			   window->priv->status_message_cid);
	gtk_statusbar_push (GTK_STATUSBAR (window->priv->statusbar),
			    window->priv->status_message_cid,
			    _("Recording..."));

	window->priv->record_id = 0;

	/* Translator comment: untitled here implies that
	 * there is no active sound sample. Any newly
	 * recorded samples will be saved to disk with this
	 * name as default value. */
	if (gsr_sample_count == 1) {
		name = g_strdup (_("Untitled"));
	} else {
		name = g_strdup_printf (_("Untitled-%d"), gsr_sample_count);
	}
	++gsr_sample_count;
	gtk_window_set_title (GTK_WINDOW(window), name);

	g_free (name);

	return FALSE;
}

static void
record_state_changed_cb (GstBus *bus, GstMessage *msg, GSRWindow *window)
{
	GstState  new_state;
	GMAudioProfile *profile;

	gst_message_parse_state_changed (msg, NULL, &new_state, NULL);

	g_return_if_fail (GSR_IS_WINDOW (window));

	/* we are only interested in state changes of the top-level pipeline */
	if (msg->src != GST_OBJECT (window->priv->record->pipeline))
		return;

	window->priv->record->state = new_state;

	GST_DEBUG ("record pipeline state: %s", gst_element_state_get_name (new_state));

	switch (new_state) {
	case GST_STATE_PLAYING:
		window->priv->record_id = g_idle_add (record_start, window);
		g_free (window->priv->extension);
		profile = gm_audio_profile_choose_get_active (window->priv->profile);
		window->priv->extension = g_strdup (profile ? gm_audio_profile_get_extension (profile) : NULL);
		gtk_widget_set_sensitive (window->priv->profile, FALSE);
		gtk_widget_set_sensitive (window->priv->input, FALSE);
		break;
	case GST_STATE_READY:
		gtk_adjustment_set_value (GTK_RANGE (window->priv->scale)->adjustment, 0.0);
		gtk_widget_set_sensitive (window->priv->scale, FALSE);
		gtk_widget_set_sensitive (window->priv->profile, TRUE);
		gtk_widget_set_sensitive (window->priv->input, GST_IS_MIXER (window->priv->mixer));
		/* fall through */
	case GST_STATE_PAUSED:
		set_action_sensitive (window, "Stop", FALSE);
		set_action_sensitive (window, "Play", TRUE);
		set_action_sensitive (window, "Record", TRUE);
		set_action_sensitive (window, "FileSave", TRUE);
		set_action_sensitive (window, "FileSaveAs", TRUE);
		gtk_widget_set_sensitive (window->priv->scale, FALSE);
		gtk_widget_set_sensitive (window->priv->profile, TRUE);
		gtk_widget_set_sensitive (window->priv->input, TRUE);

		gtk_statusbar_pop (GTK_STATUSBAR (window->priv->statusbar),
				   window->priv->status_message_cid);
		gtk_statusbar_push (GTK_STATUSBAR (window->priv->statusbar),
				    window->priv->status_message_cid,
				    _("Ready"));
		if (window->priv->tick_id > 0) {
			g_source_remove (window->priv->tick_id);
			window->priv->tick_id = 0;
		}
		break;
	default:
		break;
	}
}

/* create the gconf-based source for recording.
 * store the source and the mixer in it in our window-private data
 */
static gboolean
make_record_source (GSRWindow *window)
{
	GstElement *source, *e;

	source = gst_element_factory_make ("gconfaudiosrc", "gconfaudiosource");
	if (source == NULL) {
		show_missing_known_element_error (NULL,
		    _("GConf audio recording"), "gconfaudiosrc",
		    "gconfelements", "gst-plugins-good");
		return FALSE;
	}

	/* instantiate the underlying element so we can query it */
	/* FIXME: maybe we want to trap errors in this case ? */
        if (!gst_element_set_state (source, GST_STATE_READY)) {
		show_error_dialog (NULL, NULL,
			_("Your audio capture settings are invalid. "
			  "Please correct them with the \"Sound Preferences\" "
			  "under the System-Preferences menu."));
		return FALSE;
	}
	window->priv->source = source;
	e = gst_bin_get_by_interface (GST_BIN (source), GST_TYPE_MIXER);
	window->priv->mixer = GST_MIXER (e);

	return TRUE;
}

static void
record_input_changed_cb (GtkComboBox *input, GSRWindow *window)
{
	const gchar *text;
	const GList *l;
	GstMixerTrack *t = NULL, *new = NULL;
	static GstMixerTrack *selected = NULL;

	text = gtk_combo_box_get_active_text (input);
	GST_DEBUG ("record input changed to '%s'", GST_STR_NULL (text));

	if (text == NULL)
		return;

	/* The pipeline has been destroyed already, we'll try and remember
	 * the input for the next record run in fill_record_input() */
	if (GST_IS_MIXER (window->priv->mixer) == FALSE)
		return;

	for (l = gst_mixer_list_tracks (window->priv->mixer);
	     l != NULL; l = l->next) {
		t = l->data;
		if (t == NULL || t->label == NULL)
			continue;
		if ((g_str_equal (t->label, text)) &&
		    (t->flags & GST_MIXER_TRACK_INPUT)) {
			if (new == NULL)
				new = g_object_ref (t);
		/* FIXME selected == t is equivalent to NULL == t in this case,
		 * selected, after its initialization to NULL, was never written to
		 * before this read access to it
		 * and NULL == t is equivalent to FALSE, because of the check
		 * "if (t == NULL || t->label == NULL)" above
		 */
		} else if (selected == t)
			/* re-mute old one */
			gst_mixer_set_record (window->priv->mixer,
					      selected, FALSE);
	}

	/* FIXME selected _is_ NULL always at this point - same as 5 lines above*/
	if (selected != NULL)
		g_object_unref (selected);
	if (!(selected = new))
		return;

	gst_mixer_set_record (window->priv->mixer, selected, TRUE);
	GST_DEBUG ("input changed to: %s\n", selected->label);
	gconf_client_set_string (gconf_client, KEY_LAST_INPUT, selected->label, NULL);
}

static void
fill_record_input (GSRWindow *window, gchar *selected)
{
	const GList *l;
	int i = 0;
	int last_possible_i = 0;
	GtkTreeModel *model;

	model = gtk_combo_box_get_model (GTK_COMBO_BOX (window->priv->input));

	if (model) 
		gtk_list_store_clear (GTK_LIST_STORE (model));

	if (GST_IS_MIXER (window->priv->mixer) == FALSE
	    || gst_mixer_list_tracks (window->priv->mixer) == NULL) {
		gtk_widget_hide (window->priv->input);
		gtk_widget_hide (window->priv->input_label);
		return;
	}

	gtk_widget_set_sensitive (window->priv->input, GST_IS_MIXER (window->priv->mixer));
	if (!GST_IS_MIXER (window->priv->mixer))
		return;

	for (l = gst_mixer_list_tracks (window->priv->mixer); l != NULL; l = l->next) {
		GstMixerTrack *t = l->data;
		if (t->label == NULL)
			continue;
		if (t->flags & GST_MIXER_TRACK_INPUT) {
			gtk_combo_box_append_text (GTK_COMBO_BOX (window->priv->input), t->label);
			++i;
		}
		if (t->flags & GST_MIXER_TRACK_RECORD) {
			if (selected == NULL) {
				gtk_combo_box_set_active (GTK_COMBO_BOX (window->priv->input), i - 1);
			} else {
				last_possible_i = i;
			}
		}
		if ((selected != NULL) && g_str_equal (selected, t->label)) {
			gtk_combo_box_set_active (GTK_COMBO_BOX (window->priv->input), i - 1);
		}
	}

	if (gtk_combo_box_get_active (GTK_COMBO_BOX (window->priv->input)) == -1) {
		gtk_combo_box_set_active (GTK_COMBO_BOX (window->priv->input), last_possible_i - 1);
	}

	gtk_widget_show (window->priv->input);
	gtk_widget_show (window->priv->input_label);
}

static gboolean
level_message_handler_cb (GstBus * bus, GstMessage * message, GSRWindow *window)
{
  GSRWindowPrivate *priv = window->priv;

  if (message->type == GST_MESSAGE_ELEMENT) {
    const GstStructure *s = gst_message_get_structure (message);
    const gchar *name = gst_structure_get_name (s);

    if (g_str_equal (name, "level")) {
      gint channels;
      gdouble peak_dB;
      gdouble myind;
      const GValue *list;
      const GValue *value;

      gint i;
      /* we can get the number of channels as the length of any of the value
       * lists */

      list = gst_structure_get_value (s, "rms");
      channels = gst_value_list_get_size (list);

      for (i = 0; i < channels; ++i) {
	list = gst_structure_get_value (s, "peak");
        value = gst_value_list_get_value (list, i);
        peak_dB = g_value_get_double (value);
	myind = exp (peak_dB / 20);
	if (myind > 1.0)
		myind = 1.0;
	gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->level), myind);
      }
    }
  }
  /* we handled the message we want, and ignored the ones we didn't want.
   * so the core can unref the message for us */
  return TRUE;
}

static GSRWindowPipeline *
make_record_pipeline (GSRWindow *window)
{
	GSRWindowPipeline *pipeline;
	GMAudioProfile *profile;
	const gchar *profile_pipeline_desc;
	GstElement *encoder, *source, *filesink, *level;
	GError *err = NULL;
	gchar *pipeline_desc;
	const char *name;

	source = window->priv->source;

	/* Any reason we are not using gnomevfssink here? (tpm) */
	filesink = gst_element_factory_make ("filesink", "sink");
	if (filesink == NULL)
	{
		show_missing_known_element_error (NULL,
		    _("file output"), "filesink", "coreelements",
		    "gstreamer");
		gst_object_unref (source);
		return NULL;
	}

	pipeline = g_new (GSRWindowPipeline, 1);

	pipeline->pipeline = gst_pipeline_new ("record-pipeline");
	pipeline->src = source;
	pipeline->sink = filesink;

	gst_bin_add (GST_BIN (pipeline->pipeline), source);

	level = gst_element_factory_make ("level", "level");
	if (level == NULL)
	{
		show_missing_known_element_error (NULL,
		    _("level"), "level", "level",
		    "gstreamer");
		gst_object_unref (source);
		return NULL;
	}
	gst_element_set_name (level, "level");

	profile = gm_audio_profile_choose_get_active (window->priv->profile);
	if (profile == NULL)
		return NULL;
	profile_pipeline_desc = gm_audio_profile_get_pipeline (profile);
	name = gm_audio_profile_get_name (profile);

	GST_DEBUG ("encoder profile pipeline: '%s'",
		GST_STR_NULL (profile_pipeline_desc));

	pipeline_desc = g_strdup_printf ("audioconvert ! %s", profile_pipeline_desc);
	GST_DEBUG ("making encoder bin from description '%s'", pipeline_desc);
	encoder = gst_parse_bin_from_description (pipeline_desc, TRUE, &err);
	g_free (pipeline_desc);
	pipeline_desc = NULL;

	if (err) {
		show_profile_error (NULL, err->message,
			_("Could not parse the '%s' audio profile. "), name);
		g_printerr ("Failed to create GStreamer encoder plugins [%s]: %s\n",
		           profile_pipeline_desc, err->message);
		g_error_free (err);
		gst_object_unref (pipeline->pipeline);
		gst_object_unref (filesink);
		g_free (pipeline);
		return NULL;
	}

	gst_bin_add (GST_BIN (pipeline->pipeline), level);
	gst_bin_add (GST_BIN (pipeline->pipeline), encoder);
	gst_bin_add (GST_BIN (pipeline->pipeline), filesink);

	/* now link it all together */
	if (!(gst_element_link_many (source, level, encoder, NULL))) {
		show_profile_error (NULL, NULL,
			_("Could not capture using the '%s' audio profile. "),
			name);
		gst_object_unref (pipeline->pipeline);
		g_free (pipeline);
		return NULL;
	}

	if (!gst_element_link (encoder, filesink)) {
		show_profile_error (NULL, NULL,
			_("Could not write to a file using the '%s' audio profile. "),
			name);
		gst_object_unref (pipeline->pipeline);
		g_free (pipeline);
		return NULL;
	}

	/* we ultimately want to find out the caps on the encoder's source pad */
	g_signal_connect (pipeline->pipeline, "deep-notify::caps",
	                  G_CALLBACK (pipeline_deep_notify_caps_cb),
	                  window);

	pipeline->bus = gst_element_get_bus (pipeline->pipeline);

	gst_bus_add_signal_watch (pipeline->bus);

	g_signal_connect (pipeline->bus, "message::element",
	                  G_CALLBACK (level_message_handler_cb),
	                  window);

	g_signal_connect (pipeline->bus, "message::state-changed",
	                  G_CALLBACK (record_state_changed_cb),
	                  window);

	g_signal_connect (pipeline->bus, "message::error",
	                  G_CALLBACK (pipeline_error_cb),
	                  window);

	g_signal_connect (pipeline->bus, "message::eos",
	                  G_CALLBACK (record_eos_msg_cb),
	                  window);

	return pipeline;
}

static char *
calculate_format_value (GtkScale *scale,
			double value,
			GSRWindow *window)
{
	gint seconds;

	if (window->priv->record && window->priv->record->state == GST_STATE_PLAYING) {
		seconds = value;
		return seconds_to_string (seconds);
	} else {
		seconds = window->priv->len_secs * (value / 100);
		return seconds_to_string (seconds);
	}
}
	
static const GtkActionEntry menu_entries[] =
{
	/* File menu.  */
	{ "File", NULL, N_("_File") },
	{ "FileNew", GTK_STOCK_NEW, NULL, NULL,
	  N_("Create a new sample"), G_CALLBACK (file_new_cb) },
	{ "FileOpen", GTK_STOCK_OPEN, NULL, NULL,
	  N_("Open a file"), G_CALLBACK (file_open_cb) },
	{ "FileSave", GTK_STOCK_SAVE, NULL, NULL,
	  N_("Save the current file"), G_CALLBACK (file_save_cb) },
	{ "FileSaveAs", GTK_STOCK_SAVE_AS, NULL, "<shift><control>S",
	  N_("Save the current file with a different name"), G_CALLBACK (file_save_as_cb) },
	{ "RunMixer", GTK_STOCK_EXECUTE, N_("Open Volu_me Control"), NULL,
	  N_("Open the audio mixer"), G_CALLBACK (run_mixer_cb) },
	{ "FileProperties", GTK_STOCK_PROPERTIES, NULL, "<control>I",
	  N_("Show information about the current file"), G_CALLBACK (file_properties_cb) },
	{ "FileClose", GTK_STOCK_CLOSE, NULL, NULL,
	  N_("Close the current file"), G_CALLBACK (file_close_cb) },
	{ "Quit", GTK_STOCK_QUIT, NULL, NULL,
	  N_("Quit the program"), G_CALLBACK (quit_cb) },

	/* Control menu */
	{ "Control", NULL, N_("_Control") },
	{ "Record", GTK_STOCK_MEDIA_RECORD, NULL, "<control>R",
	  N_("Record sound"), G_CALLBACK (record_cb) },
	{ "Play", GTK_STOCK_MEDIA_PLAY, NULL, "<control>P",
	  N_("Play sound"), G_CALLBACK (play_cb) },
	{ "Stop", GTK_STOCK_MEDIA_STOP, NULL, "<control>X",
	  N_("Stop sound"), G_CALLBACK (stop_cb) },

	/* Help menu */
	{ "Help", NULL, N_("_Help") },
	{"HelpContents", GTK_STOCK_HELP, N_("Contents"), "F1",
	 N_("Open the manual"), G_CALLBACK (help_contents_cb) },
	{ "About", GTK_STOCK_ABOUT, NULL, NULL,
	 N_("About this application"), G_CALLBACK (about_cb) }
};

static void
menu_item_select_cb (GtkMenuItem *proxy,
                     GSRWindow *window)
{
	GtkAction *action;
	char *message;

	action = g_object_get_data (G_OBJECT (proxy),  "gtk-action");
	g_return_if_fail (action != NULL);

	g_object_get (G_OBJECT (action), "tooltip", &message, NULL);
	if (message) {
		gtk_statusbar_push (GTK_STATUSBAR (window->priv->statusbar),
				    window->priv->tip_message_cid, message);
		g_free (message);
	}
}

static void
menu_item_deselect_cb (GtkMenuItem *proxy,
                       GSRWindow *window)
{
	gtk_statusbar_pop (GTK_STATUSBAR (window->priv->statusbar),
			   window->priv->tip_message_cid);
}
 
static void
connect_proxy_cb (GtkUIManager *manager,
                  GtkAction *action,
                  GtkWidget *proxy,
                  GSRWindow *window)
{
	if (GTK_IS_MENU_ITEM (proxy)) {
		g_signal_connect (proxy, "select",
				  G_CALLBACK (menu_item_select_cb), window);
		g_signal_connect (proxy, "deselect",
				  G_CALLBACK (menu_item_deselect_cb), window);
	}
}

static void
disconnect_proxy_cb (GtkUIManager *manager,
                     GtkAction *action,
                     GtkWidget *proxy,
                     GSRWindow *window)
{
	if (GTK_IS_MENU_ITEM (proxy)) {
		g_signal_handlers_disconnect_by_func
			(proxy, G_CALLBACK (menu_item_select_cb), window);
		g_signal_handlers_disconnect_by_func
			(proxy, G_CALLBACK (menu_item_deselect_cb), window);
	}
}

/* find the given filename in the uninstalled or installed ui dir */
static gchar *
find_ui_file (const gchar * filename)
{
	gchar * path;

	path = g_build_filename (GSR_UIDIR_UNINSTALLED, filename, NULL);
	if (g_file_test (path, G_FILE_TEST_EXISTS))
		return path;

	g_free (path);
	path = g_build_filename (GSR_UIDIR, filename, NULL);
	if (g_file_test (path, G_FILE_TEST_EXISTS))
		return path;

	g_free (path);
	return NULL;
}

static void
gsr_window_init (GSRWindow *window)
{
	GSRWindowPrivate *priv;
	GError *error = NULL;
	GtkWidget *main_vbox;
	GtkWidget *menubar;
	GtkWidget *file_menu;
	GtkWidget *submenu;
	GtkWidget *rec_menu;
	GtkWidget *toolbar;
	GtkWidget *content_vbox;
	GtkWidget *hbox;
	GtkWidget *label;
	GtkWidget *table;
	GtkWidget *align;
	GtkWidget *frame;
	gchar *id;
	gchar *last_input;
	gchar *path;
	GtkAction *action;
	GtkShadowType shadow_type;
	window->priv = GSR_WINDOW_GET_PRIVATE (window);
	priv = window->priv;

	/* treat gconf client as a singleton */
	if (gconf_client == NULL)
		gconf_client = gconf_client_get_default ();

	main_vbox = gtk_vbox_new (FALSE, 0);
	gtk_container_add (GTK_CONTAINER (window), main_vbox);
	priv->main_vbox = main_vbox;
	gtk_widget_show (main_vbox);

	/* menu & toolbar */
	priv->ui_manager = gtk_ui_manager_new ();

	gtk_window_add_accel_group (GTK_WINDOW (window),
				    gtk_ui_manager_get_accel_group (priv->ui_manager));

	path = find_ui_file ("ui.xml");
	gtk_ui_manager_add_ui_from_file (priv->ui_manager, path, &error);

	if (error != NULL)
	{
		show_error_dialog (GTK_WINDOW (window), error->message,
			_("Could not load ui.xml. The program may be not properly installed"));
		g_error_free (error);
		exit (1);
	}
	g_free (path);

	/* show tooltips in the statusbar */
	g_signal_connect (priv->ui_manager, "connect_proxy",
			  G_CALLBACK (connect_proxy_cb), window);
	g_signal_connect (priv->ui_manager, "disconnect_proxy",
			 G_CALLBACK (disconnect_proxy_cb), window);

	priv->action_group = gtk_action_group_new ("GSRWindowActions");
	gtk_action_group_set_translation_domain (priv->action_group, NULL);
	gtk_action_group_add_actions (priv->action_group,
				      menu_entries,
				      G_N_ELEMENTS (menu_entries),
				      window);

	gtk_ui_manager_insert_action_group (priv->ui_manager, priv->action_group, 0);

	/* set short labels to use in the toolbar */
	action = gtk_action_group_get_action (priv->action_group, "FileOpen");
	g_object_set (action, "short_label", _("Open"), NULL);
	action = gtk_action_group_get_action (priv->action_group, "FileSave");
	g_object_set (action, "short_label", _("Save"), NULL);
	action = gtk_action_group_get_action (priv->action_group, "FileSaveAs");
	g_object_set (action, "short_label", _("Save As"), NULL);

	set_action_sensitive (window, "FileSave", FALSE);
	set_action_sensitive (window, "FileSaveAs", FALSE);
	set_action_sensitive (window, "Play", FALSE);
	set_action_sensitive (window, "Stop", FALSE);

	menubar = gtk_ui_manager_get_widget (priv->ui_manager, "/MenuBar");
	gtk_box_pack_start (GTK_BOX (main_vbox), menubar, FALSE, FALSE, 0);
	gtk_widget_show (menubar);

	toolbar = gtk_ui_manager_get_widget (priv->ui_manager, "/ToolBar");
	gtk_toolbar_set_show_arrow (GTK_TOOLBAR (toolbar), FALSE);
	gtk_box_pack_start (GTK_BOX (main_vbox), toolbar, FALSE, FALSE, 0);
	gtk_widget_show (toolbar);

	/* recent files */
	file_menu = gtk_ui_manager_get_widget (priv->ui_manager,
					      "/MenuBar/FileMenu");
	submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (file_menu));
	rec_menu = gtk_ui_manager_get_widget (priv->ui_manager,
					      "/MenuBar/FileMenu/FileRecentMenu");
	priv->recent_view = gtk_recent_chooser_menu_new ();
	gtk_recent_chooser_set_local_only (GTK_RECENT_CHOOSER (priv->recent_view), TRUE);
	gtk_recent_chooser_set_limit (GTK_RECENT_CHOOSER (priv->recent_view), 5);
	priv->recent_filter = gtk_recent_filter_new ();
	gtk_recent_filter_add_application (priv->recent_filter, g_get_application_name ());
	gtk_recent_chooser_set_filter (GTK_RECENT_CHOOSER (priv->recent_view), priv->recent_filter);
	g_signal_connect (priv->recent_view, "item-activated",
			  G_CALLBACK (file_open_recent_cb), window);

	/* window content: hscale, labels, etc */
	content_vbox = gtk_vbox_new (FALSE, 7);
	gtk_container_set_border_width (GTK_CONTAINER (content_vbox), 6);
	gtk_box_pack_start (GTK_BOX (main_vbox), content_vbox, TRUE, TRUE, 0);
	gtk_widget_show (content_vbox);

	priv->scale = gtk_hscale_new (GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, 100, 1, 1, 0)));
	priv->seek_in_progress = FALSE;
	g_signal_connect (priv->scale, "format-value",
			  G_CALLBACK (calculate_format_value), window);
	g_signal_connect (priv->scale, "button-press-event",
			  G_CALLBACK (seek_started), window);
	g_signal_connect (priv->scale, "button-release-event",
			  G_CALLBACK (seek_to), window);

	gtk_scale_set_value_pos (GTK_SCALE (window->priv->scale), GTK_POS_BOTTOM);
	/* We can't seek until we find out the length */
	gtk_widget_set_sensitive (window->priv->scale, FALSE);
	gtk_box_pack_start (GTK_BOX (content_vbox), priv->scale, FALSE, FALSE, 6);
	gtk_widget_show (window->priv->scale);

        /* create source and choose mixer input */
	hbox = gtk_hbox_new (FALSE, 12);
	gtk_box_pack_start (GTK_BOX (content_vbox), hbox, FALSE, FALSE, 0);

	priv->input_label = gtk_label_new_with_mnemonic (_("Record from _input:"));
	gtk_misc_set_alignment (GTK_MISC (priv->input_label), 0, 0.5);
	gtk_box_pack_start (GTK_BOX (hbox), priv->input_label, FALSE, FALSE, 0);

	priv->input = gtk_combo_box_new_text ();
	gtk_label_set_mnemonic_widget (GTK_LABEL (priv->input_label), priv->input);
	gtk_box_pack_start (GTK_BOX (hbox), priv->input, TRUE, TRUE, 0);

	if (!make_record_source (window))
		exit (1);

	g_signal_connect (priv->input, "changed",
			  G_CALLBACK (record_input_changed_cb), window);

	/* choose profile */
	hbox = gtk_hbox_new (FALSE, 12);
	gtk_box_pack_start (GTK_BOX (content_vbox), hbox, FALSE, FALSE, 0);

	label = gtk_label_new_with_mnemonic (_("_Record as:"));
	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
	gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);

	priv->profile = gm_audio_profile_choose_new ();
	gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->profile);
	gtk_box_pack_start (GTK_BOX (hbox), window->priv->profile, TRUE, TRUE, 0);
	gtk_widget_show (window->priv->profile);

	atk_object_add_relationship (gtk_widget_get_accessible (GTK_WIDGET (priv->profile)),
				ATK_RELATION_LABELLED_BY,
				gtk_widget_get_accessible (GTK_WIDGET (label)));

	id = gconf_client_get_string (gconf_client, KEY_LAST_PROFILE_ID, NULL);
	if (id) {
		gm_audio_profile_choose_set_active (window->priv->profile, id);
		g_free (id);
	}

        g_signal_connect (priv->profile, "changed",
                          G_CALLBACK (profile_changed_cb), window);

	hbox = gtk_hbox_new (FALSE, 0);
	gtk_box_pack_start (GTK_BOX (content_vbox), hbox, FALSE, FALSE, 0);

	label = gtk_label_new ("    "); /* FIXME: better padding? */
	gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);

	table = gtk_table_new (3, 2, FALSE);
	gtk_table_set_col_spacings (GTK_TABLE (table), 12);
	gtk_table_set_row_spacings (GTK_TABLE (table), 6);
	gtk_box_pack_start (GTK_BOX (hbox), table, TRUE, TRUE, 0);

	label = make_title_label (_("File Information"));

	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
	gtk_table_attach (GTK_TABLE (table), label,
			  0, 2, 0, 1,
			  GTK_FILL, 0, 0, 0);

	label = gtk_label_new (_("Filename:"));

	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
	gtk_table_attach (GTK_TABLE (table), label,
			  0, 1, 1, 2,
			  GTK_FILL, 0, 0, 0);

	priv->name_label = gtk_label_new (_("<none>"));
	gtk_label_set_selectable (GTK_LABEL (priv->name_label), TRUE);
	gtk_label_set_line_wrap (GTK_LABEL (priv->name_label), GTK_WRAP_WORD);
	gtk_misc_set_alignment (GTK_MISC (priv->name_label), 0, 0.5);
	gtk_table_attach (GTK_TABLE (table), priv->name_label,
			  1, 2, 1, 2,
			  GTK_FILL  | GTK_EXPAND, 0,
			  0, 0);

	atk_object_add_relationship (gtk_widget_get_accessible (GTK_WIDGET (priv->name_label)),
				ATK_RELATION_LABELLED_BY,
				gtk_widget_get_accessible (GTK_WIDGET (label)));


	label = gtk_label_new (_("Length:"));

	gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
	gtk_table_attach (GTK_TABLE (table), label,
			  0, 1, 2, 3,
			  GTK_FILL, 0, 0, 0);
	
	priv->length_label = gtk_label_new ("");
	gtk_label_set_selectable (GTK_LABEL (priv->length_label), TRUE);
	gtk_misc_set_alignment (GTK_MISC (priv->length_label), 0, 0.5);
	gtk_table_attach (GTK_TABLE (table), priv->length_label,
			  1, 2, 2, 3,
			  GTK_FILL | GTK_EXPAND, 0,
			  0, 0);

	atk_object_add_relationship (gtk_widget_get_accessible (GTK_WIDGET (priv->length_label)),
				ATK_RELATION_LABELLED_BY,
				gtk_widget_get_accessible (GTK_WIDGET (label)));

	/* statusbar */
	priv->statusbar = gtk_statusbar_new ();
	GTK_WIDGET_SET_FLAGS (priv->statusbar, GTK_CAN_FOCUS);
	gtk_box_pack_end (GTK_BOX (main_vbox), priv->statusbar, FALSE, FALSE, 0);
	gtk_widget_show (priv->statusbar);

	/* hack to get the same shadow as the status bar.. */
	gtk_widget_style_get (GTK_WIDGET (priv->statusbar), "shadow-type", &shadow_type, NULL);

	frame = gtk_frame_new (NULL);
	gtk_frame_set_shadow_type (GTK_FRAME (frame), shadow_type);
	gtk_widget_show (frame);

	gtk_box_pack_end (GTK_BOX (priv->statusbar), frame, FALSE, TRUE, 0);

	hbox = gtk_hbox_new (FALSE, 0);
	gtk_container_add (GTK_CONTAINER (frame), hbox);
	gtk_box_set_spacing (GTK_BOX (hbox), 6);

	priv->volume_label = gtk_label_new (_("Level:"));
	gtk_box_pack_start (GTK_BOX (hbox), priv->volume_label, FALSE, TRUE, 0);

	/* initialize priv->level */
	align = gtk_aspect_frame_new ("", 0.0, 0.0, 20, FALSE);
	gtk_frame_set_shadow_type (GTK_FRAME (align), GTK_SHADOW_NONE);
	gtk_widget_show (align);
	gtk_box_pack_start (GTK_BOX (hbox), align, FALSE, FALSE, 0);

	priv->level = gtk_progress_bar_new ();
	gtk_container_add (GTK_CONTAINER (align), priv->level);

	gtk_widget_set_sensitive (window->priv->volume_label, FALSE);
	gtk_widget_set_sensitive (window->priv->level, FALSE);

	priv->status_message_cid = gtk_statusbar_get_context_id
		(GTK_STATUSBAR (priv->statusbar), "status_message");
	priv->tip_message_cid = gtk_statusbar_get_context_id
		(GTK_STATUSBAR (priv->statusbar), "tip_message");

	gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar),
			    priv->status_message_cid,
			    _("Ready"));

	gtk_widget_show_all (main_vbox);
	last_input = gconf_client_get_string (gconf_client, KEY_LAST_INPUT, NULL);
	fill_record_input (window, last_input);
	if (last_input) {
		g_free (last_input);
	}

	/* Make the pipelines */
	priv->play = NULL;
	priv->record = NULL; 

	priv->len_secs = 0;
	priv->get_length_attempts = 16;
	priv->dirty = TRUE;
}

static void
gsr_window_set_property (GObject      *object,
			 guint         prop_id,
			 const GValue *value,
			 GParamSpec   *pspec)
{
	GSRWindow *window;
	GSRWindowPrivate *priv;
	struct stat buf;
	char *title, *short_name;
	char *utf8_name = NULL;
	const char *ext;

	window = GSR_WINDOW (object);
	priv = window->priv;

	switch (prop_id) {
	case PROP_LOCATION:
		if (priv->filename != NULL) {
			if (g_value_get_string (value) == NULL)
				return;
			if (g_str_equal (g_value_get_string (value), priv->filename)) {
				return;
			}
		}

		g_free (priv->filename);
		g_free (priv->working_file);

		priv->filename = g_value_dup_string (value);
		priv->working_file = g_strdup (priv->filename);
		priv->len_secs = 0;

		short_name = g_path_get_basename (priv->filename);
		if (stat (priv->filename, &buf) == 0) {
			window->priv->has_file = TRUE;
		} else {
			window->priv->has_file = FALSE;
		}

		g_free (window->priv->extension);
		if ((ext = strrchr (short_name, '.')) && ext[1] != '\0')
			window->priv->extension = g_strdup (&ext[1]);
		else
			window->priv->extension = NULL;

		utf8_name = g_filename_to_utf8 (short_name, -1, NULL, NULL, NULL);
		if (priv->name_label != NULL) {
			gtk_label_set_text (GTK_LABEL (priv->name_label),
					    utf8_name);
		}

		gsr_add_recent (priv->filename);

		/*Translators: this is the window title, %s is the currently open file's name or Untitled*/
		title = g_strdup_printf (_("%s - Sound Recorder"), utf8_name);
		gtk_window_set_title (GTK_WINDOW (window), title);
		g_free (title);
		g_free (utf8_name);
		g_free (short_name);

		set_action_sensitive (window, "Play", window->priv->has_file ? TRUE : FALSE);
		set_action_sensitive (window, "Stop", FALSE);
		set_action_sensitive (window, "Record", TRUE);
		set_action_sensitive (window, "FileSave", window->priv->has_file ? TRUE : FALSE);
		set_action_sensitive (window, "FileSaveAs", TRUE);
		break;
	default:
		break;
	}
}

static void
gsr_window_get_property (GObject    *object,
			 guint       prop_id,
			 GValue     *value,
			 GParamSpec *pspec)
{
	switch (prop_id) {
	case PROP_LOCATION:
		g_value_set_string (value, GSR_WINDOW (object)->priv->filename);
		break;

	default:
		break;
	}
}

static void
gsr_window_finalize (GObject *object)
{
	GSRWindow *window;
	GSRWindowPrivate *priv;

	window = GSR_WINDOW (object);
	priv = window->priv;

	GST_DEBUG ("finalizing ...");

	if (priv == NULL) {
		return;
	}

	if (priv->ui_manager) {
		g_object_unref (priv->ui_manager);
		priv->ui_manager = NULL;
	}

	if (priv->action_group) {
		g_object_unref (priv->action_group);
		priv->action_group = NULL;
	}

	if (priv->tick_id > 0) { 
		g_source_remove (priv->tick_id);
		window->priv->play->tick_id = 0;
	}

	if (priv->record_id > 0) {
		g_source_remove (priv->record_id);
	}

	if (priv->ebusy_timeout_id > 0) {
		g_source_remove (window->priv->ebusy_timeout_id);
	}

	g_idle_remove_by_data (window);

	if (priv->play != NULL) {
		shutdown_pipeline (priv->play);
		g_free (priv->play);
	}

	if (priv->record != NULL) {
		shutdown_pipeline (priv->record);
		g_free (priv->record);
	}

	unlink (priv->record_filename);
	g_free (priv->record_filename);

	g_free (priv->working_file);
	g_free (priv->filename);

	G_OBJECT_CLASS (parent_class)->finalize (object);

	window->priv = NULL;
}

static void
gsr_window_class_init (GSRWindowClass *klass)
{
	GObjectClass *object_class;

	object_class = G_OBJECT_CLASS (klass);

	object_class->finalize = gsr_window_finalize;
	object_class->set_property = gsr_window_set_property;
	object_class->get_property = gsr_window_get_property;

	parent_class = g_type_class_peek_parent (klass);

	g_object_class_install_property (object_class,
					 PROP_LOCATION,
					 g_param_spec_string ("location",
							      "Location",
							      "",
	/* Translator comment: default trackname is 'untitled', which
	 * has as effect that the user cannot save to this file. The
	 * 'save' action will open the save-as dialog instead to give
	 * a proper filename. See gnome-record.c:94. */
							      _("Untitled"),
							      G_PARAM_READWRITE));

	g_type_class_add_private (object_class, sizeof (GSRWindowPrivate));

	GST_DEBUG_CATEGORY_INIT (gsr_debug, "gsr", 0, "Gnome Sound Recorder");
}

GType
gsr_window_get_type (void)
{
	static GType type = 0;

	if (G_UNLIKELY (type == 0)) {
		GTypeInfo info = {
			sizeof (GSRWindowClass),
			NULL, NULL,
			(GClassInitFunc) gsr_window_class_init,
			NULL, NULL,
			sizeof (GSRWindow), 0,
			(GInstanceInitFunc) gsr_window_init
		};

		type = g_type_register_static (GTK_TYPE_WINDOW,
					       "GSRWindow",
					       &info, 0);
	}

	return type;
}

GtkWidget *
gsr_window_new (const char *filename)
{
	GSRWindow *window;
	char *template;

	/* filename has been changed to be without extension */
	window = g_object_new (GSR_TYPE_WINDOW, 
			       "location", filename,
			       NULL);
        /* FIXME: check extension too */
	window->priv->filename = g_strdup (filename);
	if (g_file_test (filename, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR) != FALSE) {
		window->priv->has_file = TRUE;
		window->priv->dirty = FALSE;
	} else {
		window->priv->has_file = FALSE;
	}

	template = g_strdup_printf ("gsr-record-%s-%d.XXXXXX", filename, getpid ());
	window->priv->record_fd = g_file_open_tmp (template, &window->priv->record_filename, NULL);
	g_free (template);
	close (window->priv->record_fd);

	if (window->priv->has_file == FALSE) {
		g_free (window->priv->working_file);
		window->priv->working_file = g_strdup (window->priv->record_filename);
	} else {
		g_free (window->priv->working_file);
		window->priv->working_file = g_strdup (filename);
	}

	window->priv->saved = TRUE;

	gtk_window_set_default_size (GTK_WINDOW (window), 512, 200);

	return GTK_WIDGET (window);
}
