gstreamer/tests/examples/ges-ui.c

675 lines
17 KiB
C
Raw Normal View History

/* GStreamer Editing Services
* Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk>
* 2010 Nokia Corporation
*
* 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.
*/
2010-07-19 17:02:41 +00:00
#include <gtk/gtk.h>
2010-07-20 15:05:26 +00:00
#include <glib.h>
#include <ges/ges.h>
2010-07-20 15:55:06 +00:00
typedef struct App
{
GESTimeline *timeline;
GESTimelinePipeline *pipeline;
2010-07-20 16:24:14 +00:00
GESTimelineLayer *layer;
2010-07-20 15:55:06 +00:00
GtkWidget *main_window;
GtkListStore *model;
2010-07-21 11:53:01 +00:00
GtkTreeSelection *selection;
GtkWidget *properties;
GList *selected_objects;
int n_selected;
int n_objects;
GtkHScale *duration;
2010-07-21 18:21:01 +00:00
GtkHScale *in_point;
2010-07-22 10:54:01 +00:00
GtkAction *add_file;
GtkAction *delete;
GtkAction *play;
2010-07-22 10:17:24 +00:00
GstState state;
GType selected_type;
2010-07-20 15:55:06 +00:00
} App;
App *app_new (void);
void app_dispose (App * app);
void app_add_file (App * app, gchar * filename);
2010-07-23 16:36:54 +00:00
void app_add_title (App * app);
GList *app_get_selected_objects (App * app);
void app_delete_objects (App * app, GList * objects);
void app_update_selection (App * app);
2010-07-20 15:59:59 +00:00
void window_destroy_cb (GtkObject * window, App * app);
2010-07-20 15:05:26 +00:00
2010-07-20 15:59:59 +00:00
void quit_item_activate_cb (GtkMenuItem * item, App * app);
2010-07-20 15:05:26 +00:00
2010-07-22 10:08:28 +00:00
void delete_activate_cb (GtkAction * item, App * app);
2010-07-20 15:05:26 +00:00
2010-07-22 10:17:24 +00:00
void play_activate_cb (GtkAction * item, App * app);
2010-07-22 10:08:28 +00:00
void add_file_activate_cb (GtkAction * item, App * app);
2010-07-20 15:05:26 +00:00
2010-07-23 16:36:54 +00:00
void add_text_activate_cb (GtkAction * item, App * app);
void app_selection_changed_cb (GtkTreeSelection * selection, App * app);
2010-07-22 10:17:24 +00:00
void app_toggle_playback (App * app);
gboolean duration_scale_change_value_cb (GtkRange * range, GtkScrollType
unused, gdouble value, App * app);
2010-07-21 18:21:01 +00:00
gboolean in_point_scale_change_value_cb (GtkRange * range, GtkScrollType
unused, gdouble value, App * app);
2010-07-21 17:01:33 +00:00
void duration_cell_func (GtkTreeViewColumn * column, GtkCellRenderer * renderer,
GtkTreeModel * model, GtkTreeIter * iter, gpointer user);
gboolean create_ui (App * app);
2010-07-19 17:02:41 +00:00
void connect_to_filesource (GESTimelineObject * object, App * app);
void disconnect_from_filesource (GESTimelineObject * object, App * app);
2010-07-22 10:48:34 +00:00
/* UI state functions *******************************************************/
static void
update_properties_sensitivity (App * app)
{
gtk_widget_set_sensitive (app->properties,
(app->n_selected == 1) && (app->state != GST_STATE_PLAYING));
}
2010-07-22 10:51:07 +00:00
static void
update_delete_sensitivity (App * app)
{
/* delete will work for multiple items */
gtk_action_set_sensitive (app->delete,
(app->n_selected > 0) && (app->state != GST_STATE_PLAYING));
}
/* UI callbacks ************************************************************/
2010-07-19 17:39:26 +00:00
void
2010-07-20 15:59:59 +00:00
window_destroy_cb (GtkObject * window, App * app)
2010-07-19 17:39:26 +00:00
{
gtk_main_quit ();
}
2010-07-20 11:57:28 +00:00
void
2010-07-20 15:59:59 +00:00
quit_item_activate_cb (GtkMenuItem * item, App * app)
2010-07-20 11:57:28 +00:00
{
gtk_main_quit ();
}
void
2010-07-22 10:08:28 +00:00
delete_activate_cb (GtkAction * item, App * app)
2010-07-20 11:57:28 +00:00
{
/* get a gslist of selected track objects */
GList *objects = NULL;
objects = app_get_selected_objects (app);
app_delete_objects (app, objects);
2010-07-20 11:57:28 +00:00
}
2010-07-20 13:56:12 +00:00
void
2010-07-22 10:08:28 +00:00
add_file_activate_cb (GtkAction * item, App * app)
2010-07-20 13:56:12 +00:00
{
GtkFileChooserDialog *dlg;
GST_DEBUG ("add file signal handler");
dlg = (GtkFileChooserDialog *) gtk_file_chooser_dialog_new ("Add File...",
GTK_WINDOW (app->main_window),
GTK_FILE_CHOOSER_ACTION_OPEN,
GTK_STOCK_CANCEL,
GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
2010-07-22 16:07:26 +00:00
g_object_set (G_OBJECT (dlg), "select-multiple", TRUE, NULL);
if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
2010-07-22 16:07:26 +00:00
GSList *uris;
GSList *cur;
uris = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (dlg));
for (cur = uris; cur; cur = cur->next)
app_add_file (app, cur->data);
g_slist_free (uris);
}
gtk_widget_destroy ((GtkWidget *) dlg);
2010-07-20 13:56:12 +00:00
}
2010-07-23 16:36:54 +00:00
void
add_text_activate_cb (GtkAction * item, App * app)
{
app_add_title (app);
}
2010-07-22 10:17:24 +00:00
void
play_activate_cb (GtkAction * item, App * app)
{
app_toggle_playback (app);
}
void
app_selection_changed_cb (GtkTreeSelection * selection, App * app)
{
app_update_selection (app);
2010-07-22 08:18:41 +00:00
/* doesn't make sense to set properties on more than one item */
2010-07-22 10:48:34 +00:00
update_properties_sensitivity (app);
2010-07-22 10:51:07 +00:00
update_delete_sensitivity (app);
}
gboolean
duration_scale_change_value_cb (GtkRange * range, GtkScrollType unused,
gdouble value, App * app)
{
GList *i;
for (i = app->selected_objects; i; i = i->next) {
2010-07-21 18:21:01 +00:00
guint64 duration, maxduration;
maxduration = GES_TIMELINE_FILE_SOURCE (i->data)->maxduration;
duration = (value < maxduration ? (value > 0 ? value : 0) : maxduration);
g_object_set (G_OBJECT (i->data), "duration", (guint64) duration, NULL);
}
return TRUE;
}
gboolean
in_point_scale_change_value_cb (GtkRange * range, GtkScrollType unused,
gdouble value, App * app)
{
GList *i;
for (i = app->selected_objects; i; i = i->next) {
guint64 in_point, maxduration;
maxduration = GES_TIMELINE_FILE_SOURCE (i->data)->maxduration -
GES_TIMELINE_OBJECT_DURATION (i->data);
in_point = (value < maxduration ? (value > 0 ? value : 0) : maxduration);
g_object_set (G_OBJECT (i->data), "in-point", (guint64) in_point, NULL);
}
return TRUE;
}
2010-07-21 17:01:33 +00:00
void
duration_cell_func (GtkTreeViewColumn * column, GtkCellRenderer * renderer,
GtkTreeModel * model, GtkTreeIter * iter, gpointer user)
{
gchar buf[30];
guint64 duration;
gtk_tree_model_get (model, iter, 1, &duration, -1);
g_snprintf (buf, sizeof (buf), "%u:%02u:%02u.%09u", GST_TIME_ARGS (duration));
g_object_set (renderer, "text", &buf, NULL);
}
/* application methods ******************************************************/
static void selection_foreach (GtkTreeModel * model, GtkTreePath * path,
GtkTreeIter * iter, gpointer user);
2010-07-22 10:17:24 +00:00
void
app_toggle_playback (App * app)
{
if (app->state != GST_STATE_PLAYING) {
gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PLAYING);
} else {
gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PAUSED);
}
}
void
app_update_selection (App * app)
{
GList *cur;
GType type;
/* clear old selection */
for (cur = app->selected_objects; cur; cur = cur->next) {
disconnect_from_object (cur->data, app);
g_object_unref (cur->data);
cur->data = NULL;
}
g_list_free (app->selected_objects);
app->selected_objects = NULL;
app->n_selected = 0;
/* get new selection */
gtk_tree_selection_selected_foreach (GTK_TREE_SELECTION (app->selection),
selection_foreach, &app->selected_objects);
for (cur = app->selected_objects; cur; cur = cur->next) {
connect_to_object (cur->data, app);
app->n_selected++;
}
type = G_TYPE_NONE;
if (app->selected_objects) {
/* TODO: when we allow multiple selections, only set this if all values
* are the same */
type = G_TYPE_FROM_INSTANCE (app->selected_objects->data);
}
app->selected_type = type;
}
static void
selection_foreach (GtkTreeModel * model, GtkTreePath * path, GtkTreeIter
* iter, gpointer user)
{
GList **ret = user;
GESTimelineObject *obj;
gtk_tree_model_get (model, iter, 2, &obj, -1);
*ret = g_list_append (*ret, obj);
return;
}
GList *
app_get_selected_objects (App * app)
{
return g_list_copy (app->selected_objects);
}
void
app_delete_objects (App * app, GList * objects)
{
GList *cur;
for (cur = objects; cur; cur = cur->next) {
ges_timeline_layer_remove_object (app->layer,
GES_TIMELINE_OBJECT (cur->data));
cur->data = NULL;
}
g_list_free (objects);
}
void
2010-07-22 16:07:26 +00:00
app_add_file (App * app, gchar * uri)
{
GESTimelineObject *obj;
2010-07-22 16:07:26 +00:00
GST_DEBUG ("adding file %s", uri);
obj = GES_TIMELINE_OBJECT (ges_timeline_filesource_new (uri));
ges_timeline_layer_add_object (app->layer, obj);
}
2010-07-23 16:36:54 +00:00
void
app_add_title (App * app)
{
GESTimelineObject *obj;
GST_DEBUG ("adding title");
obj = GES_TIMELINE_OBJECT (ges_timeline_title_source_new ());
g_object_set (G_OBJECT (obj), "duration", GST_SECOND, NULL);
ges_simple_timeline_layer_add_object (GES_SIMPLE_TIMELINE_LAYER (app->layer),
obj, -1);
}
2010-07-20 15:55:06 +00:00
App *
app_new (void)
{
App *ret;
ret = g_new0 (App, 1);
ret->selected_type = G_TYPE_NONE;
2010-07-20 15:55:06 +00:00
if (!ret)
return NULL;
if (!(ret->timeline = ges_timeline_new_audio_video ()))
goto fail;
if (!(ret->pipeline = ges_timeline_pipeline_new ()))
goto fail;
if (!ges_timeline_pipeline_add_timeline (ret->pipeline, ret->timeline))
goto fail;
2010-07-20 16:24:14 +00:00
if (!(ret->layer = (GESTimelineLayer *) ges_simple_timeline_layer_new ()))
goto fail;
if (!(ges_timeline_add_layer (ret->timeline, ret->layer)))
goto fail;
if (!(create_ui (ret)))
2010-07-20 15:55:06 +00:00
goto fail;
return ret;
fail:
app_dispose (ret);
return NULL;
}
void
app_dispose (App * app)
{
if (app) {
if (app->pipeline)
gst_object_unref (app->pipeline);
g_free (app);
}
}
/* Backend callbacks ********************************************************/
static gboolean
find_row_for_object (GtkListStore * model, GtkTreeIter * ret,
GESTimelineObject * object)
{
gtk_tree_model_get_iter_first ((GtkTreeModel *) model, ret);
while (gtk_list_store_iter_is_valid (model, ret)) {
GESTimelineObject *obj;
gtk_tree_model_get ((GtkTreeModel *) model, ret, 2, &obj, -1);
if (obj == object) {
g_object_unref (obj);
return TRUE;
}
g_object_unref (obj);
gtk_tree_model_iter_next ((GtkTreeModel *) model, ret);
}
return FALSE;
}
2010-07-21 16:15:06 +00:00
/* this callback is registered for every timeline object, and updates the
* corresponding duration cell in the model */
static void
timeline_object_notify_duration_cb (GESTimelineObject * object,
GParamSpec * arg G_GNUC_UNUSED, App * app)
{
GtkTreeIter iter;
guint64 duration = 0;
g_object_get (object, "duration", &duration, NULL);
find_row_for_object (app->model, &iter, object);
gtk_list_store_set (app->model, &iter, 1, duration, -1);
}
/* these guys are only connected to filesources that are the target of the
* current selection */
static void
filesource_notify_duration_cb (GESTimelineObject * object,
GParamSpec * arg G_GNUC_UNUSED, App * app)
{
2010-07-21 18:21:01 +00:00
guint64 duration, max_inpoint;
duration = GES_TIMELINE_OBJECT_DURATION (object);
max_inpoint = GES_TIMELINE_FILE_SOURCE (object)->maxduration - duration;
gtk_range_set_value (GTK_RANGE (app->duration), duration);
gtk_range_set_fill_level (GTK_RANGE (app->in_point), max_inpoint);
if (max_inpoint < GES_TIMELINE_OBJECT_INPOINT (object))
g_object_set (object, "in-point", max_inpoint, NULL);
2010-07-21 16:15:06 +00:00
}
static void
filesource_notify_max_duration_cb (GESTimelineObject * object,
GParamSpec * arg G_GNUC_UNUSED, App * app)
{
gtk_range_set_range (GTK_RANGE (app->duration),
0, (gdouble) GES_TIMELINE_FILE_SOURCE (object)->maxduration);
2010-07-21 18:21:01 +00:00
gtk_range_set_range (GTK_RANGE (app->in_point),
0, (gdouble) GES_TIMELINE_FILE_SOURCE (object)->maxduration);
}
static void
filesource_notify_in_point_cb (GESTimelineObject * object,
GParamSpec * arg G_GNUC_UNUSED, App * app)
{
gtk_range_set_value (GTK_RANGE (app->in_point),
GES_TIMELINE_OBJECT_INPOINT (object));
2010-07-21 16:15:06 +00:00
}
static gchar *
desc_for_object (GESTimelineObject * object)
{
gchar *uri;
/* there is only one type of object at the moment */
/* return the uri */
g_object_get (object, "uri", &uri, NULL);
return uri;
}
static void
object_count_changed (App * app)
{
gtk_action_set_sensitive (app->play, app->n_objects > 0);
}
static void
layer_object_added_cb (GESTimelineLayer * layer, GESTimelineObject * object,
App * app)
{
GtkTreeIter iter;
gchar *description;
GST_INFO ("layer object added cb %p %p %p", layer, object, app);
description = desc_for_object (object);
gtk_list_store_append (app->model, &iter);
gtk_list_store_set (app->model, &iter, 0, description, 2, object, -1);
g_signal_connect (G_OBJECT (object), "notify::duration",
G_CALLBACK (timeline_object_notify_duration_cb), app);
timeline_object_notify_duration_cb (object, NULL, app);
app->n_objects++;
object_count_changed (app);
}
static void
layer_object_removed_cb (GESTimelineLayer * layer, GESTimelineObject * object,
App * app)
{
GtkTreeIter iter;
GST_INFO ("layer object removed cb %p %p %p", layer, object, app);
if (!find_row_for_object (GTK_LIST_STORE (app->model), &iter, object)) {
g_print ("object deleted but we don't own it");
return;
}
app->n_objects--;
object_count_changed (app);
gtk_list_store_remove (app->model, &iter);
}
static void
pipeline_state_changed_cb (App * app)
{
if (app->state == GST_STATE_PLAYING)
gtk_action_set_stock_id (app->play, GTK_STOCK_MEDIA_PAUSE);
else
gtk_action_set_stock_id (app->play, GTK_STOCK_MEDIA_PLAY);
2010-07-22 10:48:34 +00:00
update_properties_sensitivity (app);
2010-07-22 10:51:07 +00:00
update_delete_sensitivity (app);
2010-07-22 10:54:01 +00:00
gtk_action_set_sensitive (app->add_file, app->state != GST_STATE_PLAYING);
}
static void
bus_message_cb (GstBus * bus, GstMessage * message, App * app)
{
const GstStructure *s;
s = gst_message_get_structure (message);
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_ERROR:
g_print ("ERROR\n");
break;
case GST_MESSAGE_EOS:
break;
case GST_MESSAGE_STATE_CHANGED:
if (s && GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (app->pipeline)) {
GstState old, new, pending;
gst_message_parse_state_changed (message, &old, &new, &pending);
app->state = new;
pipeline_state_changed_cb (app);
}
break;
default:
break;
}
}
/* UI Configuration *********************************************************/
2010-07-21 13:43:28 +00:00
#define GET_WIDGET(dest,name,type) {\
if (!(dest =\
type(gtk_builder_get_object(builder, name))))\
goto fail;\
}
void
connect_to_filesource (GESTimelineObject * object, App * app)
{
g_signal_connect (G_OBJECT (object), "notify::max-duration",
G_CALLBACK (filesource_notify_max_duration_cb), app);
filesource_notify_max_duration_cb (object, NULL, app);
g_signal_connect (G_OBJECT (object), "notify::duration",
G_CALLBACK (filesource_notify_duration_cb), app);
filesource_notify_duration_cb (object, NULL, app);
2010-07-21 18:21:01 +00:00
g_signal_connect (G_OBJECT (object), "notify::in-point",
G_CALLBACK (filesource_notify_in_point_cb), app);
filesource_notify_in_point_cb (object, NULL, app);
}
void
disconnect_from_filesource (GESTimelineObject * object, App * app)
{
g_signal_handlers_disconnect_by_func (G_OBJECT (object),
filesource_notify_duration_cb, app);
g_signal_handlers_disconnect_by_func (G_OBJECT (object),
filesource_notify_max_duration_cb, app);
}
gboolean
2010-07-20 15:59:59 +00:00
create_ui (App * app)
2010-07-19 17:02:41 +00:00
{
GtkBuilder *builder;
GtkTreeView *timeline;
2010-07-21 17:01:33 +00:00
GtkTreeViewColumn *duration_col;
GtkCellRenderer *duration_renderer;
GstBus *bus;
2010-07-19 17:02:41 +00:00
2010-07-21 13:43:28 +00:00
/* construct widget tree */
2010-07-19 17:02:41 +00:00
builder = gtk_builder_new ();
gtk_builder_add_from_file (builder, "ges-ui.glade", NULL);
2010-07-21 13:43:28 +00:00
gtk_builder_connect_signals (builder, app);
2010-07-21 13:43:28 +00:00
/* get a bunch of widgets from the XML tree */
2010-07-21 13:43:28 +00:00
GET_WIDGET (timeline, "timeline_treeview", GTK_TREE_VIEW);
GET_WIDGET (app->properties, "properties", GTK_WIDGET);
GET_WIDGET (app->main_window, "window", GTK_WIDGET);
GET_WIDGET (app->duration, "duration_scale", GTK_HSCALE);
2010-07-21 18:21:01 +00:00
GET_WIDGET (app->in_point, "in_point_scale", GTK_HSCALE);
2010-07-21 17:01:33 +00:00
GET_WIDGET (duration_col, "duration_column", GTK_TREE_VIEW_COLUMN);
GET_WIDGET (duration_renderer, "duration_renderer", GTK_CELL_RENDERER);
2010-07-22 10:54:01 +00:00
GET_WIDGET (app->add_file, "add_file", GTK_ACTION);
GET_WIDGET (app->delete, "delete", GTK_ACTION);
GET_WIDGET (app->play, "play", GTK_ACTION);
2010-07-21 13:43:28 +00:00
/* we care when the tree selection changes */
2010-07-21 11:53:01 +00:00
2010-07-21 13:43:28 +00:00
if (!(app->selection = gtk_tree_view_get_selection (timeline)))
2010-07-21 11:53:01 +00:00
goto fail;
g_signal_connect (app->selection, "changed",
G_CALLBACK (app_selection_changed_cb), app);
2010-07-21 13:43:28 +00:00
/* create the model for the treeview */
2010-07-21 13:43:28 +00:00
if (!(app->model =
gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_OBJECT)))
goto fail;
gtk_tree_view_set_model (timeline, GTK_TREE_MODEL (app->model));
2010-07-19 17:02:41 +00:00
2010-07-21 17:01:33 +00:00
/* register custom cell data function */
gtk_tree_view_column_set_cell_data_func (duration_col, duration_renderer,
duration_cell_func, NULL, NULL);
2010-07-21 13:43:28 +00:00
/* register callbacks on GES objects */
2010-07-20 16:24:14 +00:00
g_signal_connect (app->layer, "object-added",
G_CALLBACK (layer_object_added_cb), app);
g_signal_connect (app->layer, "object-removed",
G_CALLBACK (layer_object_removed_cb), app);
bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
gst_bus_add_signal_watch (bus);
g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), app);
2010-07-21 13:43:28 +00:00
/* success */
g_object_unref (G_OBJECT (builder));
return TRUE;
fail:
g_object_unref (G_OBJECT (builder));
return FALSE;
2010-07-20 15:05:26 +00:00
}
2010-07-21 13:43:28 +00:00
#undef GET_WIDGET
/* main *********************************************************************/
2010-07-20 15:05:26 +00:00
int
main (int argc, char *argv[])
{
2010-07-20 15:55:06 +00:00
App *app;
2010-07-20 15:05:26 +00:00
/* intialize GStreamer and GES */
if (!g_thread_supported ())
g_thread_init (NULL);
gst_init (&argc, &argv);
ges_init ();
/* initialize UI */
gtk_init (&argc, &argv);
if ((app = app_new ())) {
gtk_main ();
app_dispose (app);
}
2010-07-20 15:55:06 +00:00
2010-07-19 17:02:41 +00:00
return 0;
}