1 /* GStreamer Editing Services
2 * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk>
3 * 2010 Nokia Corporation
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
26 #include <glib/gprintf.h>
29 /* Application Data ********************************************************/
32 * Contains most of the application data so that signal handlers
33 * and other callbacks have easy access.
38 /* back-end objects */
39 GESTimeline *timeline;
40 GESTimelinePipeline *pipeline;
42 GESTrack *audio_track;
43 GESTrack *video_track;
47 /* application state */
52 GList *selected_objects;
54 gboolean first_selected;
55 gboolean last_selected;
57 gboolean ignore_input;
61 GtkTreeSelection *selection;
64 GtkWidget *main_window;
65 GtkWidget *add_effect_dlg;
66 GtkWidget *properties;
67 GtkWidget *filesource_properties;
68 GtkWidget *text_properties;
69 GtkWidget *generic_duration;
70 GtkWidget *background_properties;
71 GtkWidget *audio_effect_entry;
72 GtkWidget *video_effect_entry;
79 GtkAction *add_effect;
82 GtkAction *add_transition;
88 GtkToggleAction *audio_track_action;
89 GtkToggleAction *video_track_action;
93 GtkComboBox *background_type;
98 GtkSpinButton *frequency;
101 static int n_instances = 0;
103 /* Prototypes for auto-connected signal handlers ***************************/
106 * These are declared non-static for signal auto-connection
109 gboolean window_delete_event_cb (GtkObject * window, GdkEvent * event,
111 void new_activate_cb (GtkMenuItem * item, App * app);
112 void open_activate_cb (GtkMenuItem * item, App * app);
113 void save_as_activate_cb (GtkMenuItem * item, App * app);
114 void launch_pitivi_project_activate_cb (GtkMenuItem * item, App * app);
115 void quit_item_activate_cb (GtkMenuItem * item, App * app);
116 void delete_activate_cb (GtkAction * item, App * app);
117 void play_activate_cb (GtkAction * item, App * app);
118 void stop_activate_cb (GtkAction * item, App * app);
119 void move_up_activate_cb (GtkAction * item, App * app);
120 void move_down_activate_cb (GtkAction * item, App * app);
121 void add_effect_activate_cb (GtkAction * item, App * app);
122 void add_file_activate_cb (GtkAction * item, App * app);
123 void add_text_activate_cb (GtkAction * item, App * app);
124 void add_test_activate_cb (GtkAction * item, App * app);
125 void audio_track_activate_cb (GtkToggleAction * item, App * app);
126 void video_track_activate_cb (GtkToggleAction * item, App * app);
127 void add_transition_activate_cb (GtkAction * item, App * app);
128 void app_selection_changed_cb (GtkTreeSelection * selection, App * app);
129 void halign_changed_cb (GtkComboBox * widget, App * app);
130 void valign_changed_cb (GtkComboBox * widget, App * app);
131 void background_type_changed_cb (GtkComboBox * widget, App * app);
132 void frequency_value_changed_cb (GtkSpinButton * widget, App * app);
133 void on_apply_effect_cb (GtkButton * button, App * app);
134 void on_cancel_add_effect_cb (GtkButton * button, App * app);
135 gboolean add_effect_dlg_delete_event_cb (GtkWidget * widget, GdkEvent * event,
139 duration_scale_change_value_cb (GtkRange * range,
140 GtkScrollType unused, gdouble value, App * app);
143 in_point_scale_change_value_cb (GtkRange * range,
144 GtkScrollType unused, gdouble value, App * app);
147 volume_change_value_cb (GtkRange * range,
148 GtkScrollType unused, gdouble value, App * app);
150 /* UI state functions *******************************************************/
153 * Update properties of UI elements that depend on more than one thing.
157 update_effect_sensitivity (App * app)
162 /* effects will work for multiple FileSource */
163 for (i = app->selected_objects; i; i = i->next) {
164 if (!GES_IS_URI_CLIP (i->data)) {
170 gtk_action_set_sensitive (app->add_effect,
171 ok && (app->n_selected > 0) && (app->state != GST_STATE_PLAYING)
172 && (app->state != GST_STATE_PAUSED));
176 update_delete_sensitivity (App * app)
178 /* delete will work for multiple items */
179 gtk_action_set_sensitive (app->delete,
180 (app->n_selected > 0) && (app->state != GST_STATE_PLAYING)
181 && (app->state != GST_STATE_PAUSED));
185 update_add_transition_sensitivity (App * app)
187 gtk_action_set_sensitive (app->add_transition,
188 (app->state != GST_STATE_PLAYING) && (app->state != GST_STATE_PAUSED));
192 update_move_up_down_sensitivity (App * app)
196 can_move = (app->n_selected == 1) &&
197 (app->state != GST_STATE_PLAYING) && (app->state != GST_STATE_PAUSED);
199 gtk_action_set_sensitive (app->move_up, can_move && (!app->first_selected));
200 gtk_action_set_sensitive (app->move_down, can_move && (!app->last_selected));
204 update_play_sensitivity (App * app)
208 g_object_get (app->layer, "valid", &valid, NULL);
210 gtk_action_set_sensitive (app->play, (app->n_objects && valid));
213 /* Backend callbacks ********************************************************/
216 test_source_notify_volume_changed_cb (GESClip * clip, GParamSpec *
217 unused G_GNUC_UNUSED, App * app)
221 g_object_get (G_OBJECT (clip), "volume", &volume, NULL);
223 gtk_range_set_value (GTK_RANGE (app->volume), volume);
227 layer_notify_valid_changed_cb (GObject * object, GParamSpec * unused
228 G_GNUC_UNUSED, App * app)
230 update_play_sensitivity (app);
234 find_row_for_object (GtkListStore * model, GtkTreeIter * ret, GESClip * clip)
236 gtk_tree_model_get_iter_first ((GtkTreeModel *) model, ret);
238 while (gtk_list_store_iter_is_valid (model, ret)) {
240 gtk_tree_model_get ((GtkTreeModel *) model, ret, 2, &clip2, -1);
242 gst_object_unref (clip2);
245 gst_object_unref (clip2);
246 gtk_tree_model_iter_next ((GtkTreeModel *) model, ret);
251 /* this callback is registered for every clip, and updates the
252 * corresponding duration cell in the model */
254 clip_notify_duration_cb (GESClip * clip,
255 GParamSpec * arg G_GNUC_UNUSED, App * app)
258 guint64 duration = 0;
260 g_object_get (clip, "duration", &duration, NULL);
261 find_row_for_object (app->model, &iter, clip);
262 gtk_list_store_set (app->model, &iter, 1, duration, -1);
265 /* these guys are only connected to filesources that are the target of the
266 * current selection */
269 filesource_notify_duration_cb (GESClip * clip,
270 GParamSpec * arg G_GNUC_UNUSED, App * app)
272 guint64 duration, max_inpoint;
273 duration = GES_TIMELINE_ELEMENT_DURATION (clip);
274 max_inpoint = GES_TIMELINE_ELEMENT_MAX_DURATION (clip) - duration;
276 gtk_range_set_value (GTK_RANGE (app->duration), duration);
277 gtk_range_set_fill_level (GTK_RANGE (app->in_point), max_inpoint);
279 if (max_inpoint < GES_TIMELINE_ELEMENT_INPOINT (clip))
280 g_object_set (clip, "in-point", max_inpoint, NULL);
285 filesource_notify_max_duration_cb (GESClip * clip,
286 GParamSpec * arg G_GNUC_UNUSED, App * app)
288 gtk_range_set_range (GTK_RANGE (app->duration), 0, (gdouble)
289 GES_TIMELINE_ELEMENT_MAX_DURATION (clip));
290 gtk_range_set_range (GTK_RANGE (app->in_point), 0, (gdouble)
291 GES_TIMELINE_ELEMENT_MAX_DURATION (clip));
295 filesource_notify_in_point_cb (GESClip * clip,
296 GParamSpec * arg G_GNUC_UNUSED, App * app)
298 gtk_range_set_value (GTK_RANGE (app->in_point),
299 GES_TIMELINE_ELEMENT_INPOINT (clip));
303 app_update_first_last_selected (App * app)
307 /* keep track of whether the first or last items are selected */
308 path = gtk_tree_path_new_from_indices (0, -1);
309 app->first_selected =
310 gtk_tree_selection_path_is_selected (app->selection, path);
311 gtk_tree_path_free (path);
313 path = gtk_tree_path_new_from_indices (app->n_objects - 1, -1);
315 gtk_tree_selection_path_is_selected (app->selection, path);
316 gtk_tree_path_free (path);
320 object_count_changed (App * app)
322 app_update_first_last_selected (app);
323 update_move_up_down_sensitivity (app);
324 update_play_sensitivity (app);
328 title_source_text_changed_cb (GESClip * clip,
329 GParamSpec * arg G_GNUC_UNUSED, App * app)
334 g_object_get (clip, "text", &text, NULL);
336 find_row_for_object (app->model, &iter, clip);
337 gtk_list_store_set (app->model, &iter, 0, text, -1);
342 layer_object_added_cb (GESLayer * layer, GESClip * clip, App * app)
347 GST_INFO ("layer clip added cb %p %p %p", layer, clip, app);
349 gtk_list_store_append (app->model, &iter);
351 if (GES_IS_URI_CLIP (clip)) {
352 g_object_get (G_OBJECT (clip), "uri", &description, NULL);
353 gtk_list_store_set (app->model, &iter, 0, description, 2, clip, -1);
356 else if (GES_IS_TITLE_CLIP (clip)) {
357 gtk_list_store_set (app->model, &iter, 2, clip, -1);
358 g_signal_connect (G_OBJECT (clip), "notify::text",
359 G_CALLBACK (title_source_text_changed_cb), app);
360 title_source_text_changed_cb (clip, NULL, app);
363 else if (GES_IS_TEST_CLIP (clip)) {
364 gtk_list_store_set (app->model, &iter, 2, clip, 0, "Test Source", -1);
367 else if (GES_IS_BASE_TRANSITION_CLIP (clip)) {
368 gtk_list_store_set (app->model, &iter, 2, clip, 0, "Transition", -1);
371 g_signal_connect (G_OBJECT (clip), "notify::duration",
372 G_CALLBACK (clip_notify_duration_cb), app);
373 clip_notify_duration_cb (clip, NULL, app);
376 object_count_changed (app);
380 layer_object_removed_cb (GESLayer * layer, GESClip * clip, App * app)
384 GST_INFO ("layer clip removed cb %p %p %p", layer, clip, app);
386 if (!find_row_for_object (GTK_LIST_STORE (app->model), &iter, clip)) {
387 g_print ("clip deleted but we don't own it");
391 object_count_changed (app);
393 gtk_list_store_remove (app->model, &iter);
397 layer_object_moved_cb (GESClip * layer, GESClip * clip,
398 gint old, gint new, App * app)
403 /* we can take the old position as given, but the new position might have to
405 new = new < 0 ? (app->n_objects - 1) : new;
407 path = gtk_tree_path_new_from_indices (old, -1);
408 gtk_tree_model_get_iter (GTK_TREE_MODEL (app->model), &a, path);
409 gtk_tree_path_free (path);
411 path = gtk_tree_path_new_from_indices (new, -1);
412 gtk_tree_model_get_iter (GTK_TREE_MODEL (app->model), &b, path);
413 gtk_tree_path_free (path);
415 gtk_list_store_swap (app->model, &a, &b);
416 app_selection_changed_cb (app->selection, app);
417 update_move_up_down_sensitivity (app);
421 pipeline_state_changed_cb (App * app)
423 gboolean playing_or_paused;
425 if (app->state == GST_STATE_PLAYING)
426 gtk_action_set_stock_id (app->play, GTK_STOCK_MEDIA_PAUSE);
428 gtk_action_set_stock_id (app->play, GTK_STOCK_MEDIA_PLAY);
430 update_delete_sensitivity (app);
431 update_add_transition_sensitivity (app);
432 update_move_up_down_sensitivity (app);
434 playing_or_paused = (app->state == GST_STATE_PLAYING) ||
435 (app->state == GST_STATE_PAUSED);
437 gtk_action_set_sensitive (app->add_file, !playing_or_paused);
438 gtk_action_set_sensitive (app->add_title, !playing_or_paused);
439 gtk_action_set_sensitive (app->add_test, !playing_or_paused);
440 gtk_action_set_sensitive ((GtkAction *) app->audio_track_action,
442 gtk_action_set_sensitive ((GtkAction *) app->video_track_action,
444 gtk_widget_set_sensitive (app->properties, !playing_or_paused);
448 project_bus_message_cb (GstBus * bus, GstMessage * message,
449 GMainLoop * mainloop)
451 switch (GST_MESSAGE_TYPE (message)) {
452 case GST_MESSAGE_ERROR:
453 g_printerr ("ERROR\n");
454 g_main_loop_quit (mainloop);
456 case GST_MESSAGE_EOS:
457 g_printerr ("Done\n");
458 g_main_loop_quit (mainloop);
466 bus_message_cb (GstBus * bus, GstMessage * message, App * app)
468 const GstStructure *s;
469 s = gst_message_get_structure (message);
471 switch (GST_MESSAGE_TYPE (message)) {
472 case GST_MESSAGE_ERROR:
475 case GST_MESSAGE_EOS:
476 gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_READY);
478 case GST_MESSAGE_STATE_CHANGED:
479 if (s && GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (app->pipeline)) {
480 GstState old, new, pending;
481 gst_message_parse_state_changed (message, &old, &new, &pending);
483 pipeline_state_changed_cb (app);
491 /* Static UI Callbacks ******************************************************/
494 check_time (const gchar * time)
496 static GRegex *re = NULL;
500 g_regex_new ("^[0-9][0-9]:[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?$",
501 G_REGEX_EXTENDED, 0, NULL)))
505 if (g_regex_match (re, time, 0, NULL))
511 str_to_time (const gchar * str)
522 h = strtoull (buf, NULL, 10);
528 m = strtoull (buf, NULL, 10);
530 strncpy (buf, &str[6], sizeof (buf));
531 s = strtod (buf, NULL);
533 ret = (h * 3600 * GST_SECOND) +
534 (m * 60 * GST_SECOND) + ((guint64) (s * GST_SECOND));
540 text_notify_text_changed_cb (GtkEntry * widget, GParamSpec * unused, App * app)
545 if (app->ignore_input)
548 text = gtk_entry_get_text (widget);
550 for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
551 g_object_set (G_OBJECT (tmp->data), "text", text, NULL);
556 seconds_notify_text_changed_cb (GtkEntry * widget, GParamSpec * unused,
562 if (app->ignore_input)
565 text = gtk_entry_get_text (app->seconds);
567 if (!check_time (text)) {
568 gtk_entry_set_icon_from_stock (app->seconds,
569 GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_DIALOG_WARNING);
571 gtk_entry_set_icon_from_stock (app->seconds,
572 GTK_ENTRY_ICON_SECONDARY, NULL);
573 for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
574 g_object_set (GES_CLIP (tmp->data), "duration",
575 (guint64) str_to_time (text), NULL);
581 duration_cell_func (GtkTreeViewColumn * column, GtkCellRenderer * renderer,
582 GtkTreeModel * model, GtkTreeIter * iter, gpointer user)
587 gtk_tree_model_get (model, iter, 1, &duration, -1);
588 g_snprintf (buf, sizeof (buf), "%u:%02u:%02u.%09u", GST_TIME_ARGS (duration));
589 g_object_set (renderer, "text", &buf, NULL);
592 /* UI Initialization ********************************************************/
595 connect_to_filesource (GESClip * clip, App * app)
597 g_signal_connect (G_OBJECT (clip), "notify::max-duration",
598 G_CALLBACK (filesource_notify_max_duration_cb), app);
599 filesource_notify_max_duration_cb (clip, NULL, app);
601 g_signal_connect (G_OBJECT (clip), "notify::duration",
602 G_CALLBACK (filesource_notify_duration_cb), app);
603 filesource_notify_duration_cb (clip, NULL, app);
605 g_signal_connect (G_OBJECT (clip), "notify::in-point",
606 G_CALLBACK (filesource_notify_in_point_cb), app);
607 filesource_notify_in_point_cb (clip, NULL, app);
611 disconnect_from_filesource (GESClip * clip, App * app)
613 g_signal_handlers_disconnect_by_func (G_OBJECT (clip),
614 filesource_notify_duration_cb, app);
616 g_signal_handlers_disconnect_by_func (G_OBJECT (clip),
617 filesource_notify_max_duration_cb, app);
621 connect_to_title_source (GESClip * clip, App * app)
623 GESTitleClip *titleclip;
624 titleclip = GES_TITLE_CLIP (clip);
625 gtk_combo_box_set_active (app->halign,
626 ges_title_clip_get_halignment (titleclip));
627 gtk_combo_box_set_active (app->valign,
628 ges_title_clip_get_valignment (titleclip));
629 gtk_entry_set_text (app->text, ges_title_clip_get_text (titleclip));
633 disconnect_from_title_source (GESClip * clip, App * app)
638 connect_to_test_source (GESClip * clip, App * app)
641 GParamSpecDouble *pspec;
643 GESTestClip *testclip;
644 testclip = GES_TEST_CLIP (clip);
645 gtk_combo_box_set_active (app->background_type,
646 ges_test_clip_get_vpattern (testclip));
648 g_signal_connect (G_OBJECT (testclip), "notify::volume",
649 G_CALLBACK (test_source_notify_volume_changed_cb), app);
650 test_source_notify_volume_changed_cb (clip, NULL, app);
652 klass = G_OBJECT_GET_CLASS (G_OBJECT (testclip));
654 pspec = G_PARAM_SPEC_DOUBLE (g_object_class_find_property (klass, "volume"));
655 gtk_range_set_range (GTK_RANGE (app->volume), pspec->minimum, pspec->maximum);
657 pspec = G_PARAM_SPEC_DOUBLE (g_object_class_find_property (klass, "freq"));
658 gtk_spin_button_set_range (app->frequency, pspec->minimum, pspec->maximum);
659 gtk_spin_button_set_value (app->frequency,
660 ges_test_clip_get_frequency (GES_TEST_CLIP (clip)));
664 disconnect_from_test_source (GESClip * clip, App * app)
666 g_signal_handlers_disconnect_by_func (G_OBJECT (clip),
667 test_source_notify_volume_changed_cb, app);
671 connect_to_object (GESClip * clip, App * app)
676 app->ignore_input = TRUE;
678 duration = GES_TIMELINE_ELEMENT_DURATION (clip);
679 g_snprintf (buf, sizeof (buf), "%02u:%02u:%02u.%09u",
680 GST_TIME_ARGS (duration));
681 gtk_entry_set_text (app->seconds, buf);
683 if (GES_IS_URI_CLIP (clip)) {
684 connect_to_filesource (clip, app);
685 } else if (GES_IS_TITLE_CLIP (clip)) {
686 connect_to_title_source (clip, app);
687 } else if (GES_IS_TEST_CLIP (clip)) {
688 connect_to_test_source (clip, app);
691 app->ignore_input = FALSE;
695 disconnect_from_object (GESClip * clip, App * app)
697 if (GES_IS_URI_CLIP (clip)) {
698 disconnect_from_filesource (clip, app);
699 } else if (GES_IS_TITLE_CLIP (clip)) {
700 disconnect_from_title_source (clip, app);
701 } else if (GES_IS_TEST_CLIP (clip)) {
702 disconnect_from_test_source (clip, app);
706 static GtkListStore *
707 get_video_patterns (void)
709 GEnumClass *enum_class;
711 GESTestClipClass *klass;
717 m = gtk_list_store_new (1, G_TYPE_STRING);
719 tr = ges_test_clip_new ();
720 klass = GES_TEST_CLIP_GET_CLASS (tr);
722 pspec = g_object_class_find_property (G_OBJECT_CLASS (klass), "vpattern");
724 enum_class = G_ENUM_CLASS (g_type_class_ref (pspec->value_type));
726 for (v = enum_class->values; v->value_nick != NULL; v++) {
727 gtk_list_store_append (m, &i);
728 gtk_list_store_set (m, &i, 0, v->value_name, -1);
731 g_type_class_unref (enum_class);
732 gst_object_unref (tr);
737 #define GET_WIDGET(dest,name,type) {\
739 type(gtk_builder_get_object(builder, name))))\
745 layer_added_cb (GESTimeline * timeline, GESLayer * layer, App * app)
747 if (!GES_IS_SIMPLE_LAYER (layer)) {
748 GST_ERROR ("This timeline contains a layer type other than "
749 "GESSimpleLayer. Timeline editing disabled");
757 if (layer != app->layer) {
758 GST_ERROR ("This demo doesn't support editing timelines with multiple"
763 g_signal_connect (app->layer, "clip-added",
764 G_CALLBACK (layer_object_added_cb), app);
765 g_signal_connect (app->layer, "clip-removed",
766 G_CALLBACK (layer_object_removed_cb), app);
767 g_signal_connect (app->layer, "object-moved",
768 G_CALLBACK (layer_object_moved_cb), app);
769 g_signal_connect (app->layer, "notify::valid",
770 G_CALLBACK (layer_notify_valid_changed_cb), app);
774 update_track_actions (App * app)
776 g_signal_handlers_disconnect_by_func (app->audio_track_action,
777 audio_track_activate_cb, app);
778 g_signal_handlers_disconnect_by_func (app->video_track_action,
779 video_track_activate_cb, app);
780 gtk_toggle_action_set_active (app->audio_track_action, app->audio_tracks);
781 gtk_toggle_action_set_active (app->video_track_action, app->video_tracks);
782 gtk_action_set_sensitive ((GtkAction *) app->audio_track_action,
783 app->audio_tracks <= 1);
784 gtk_action_set_sensitive ((GtkAction *) app->video_track_action,
785 app->video_tracks <= 1);
786 g_signal_connect (G_OBJECT (app->audio_track_action), "activate",
787 G_CALLBACK (audio_track_activate_cb), app);
788 g_signal_connect (G_OBJECT (app->video_track_action), "activate",
789 G_CALLBACK (video_track_activate_cb), app);
793 track_added_cb (GESTimeline * timeline, GESTrack * track, App * app)
795 if (track->type == GES_TRACK_TYPE_AUDIO) {
797 if (!app->audio_track)
798 app->audio_track = track;
800 if (track->type == GES_TRACK_TYPE_VIDEO) {
802 if (!app->video_track)
803 app->video_track = track;
807 update_track_actions (app);
811 track_removed_cb (GESTimeline * timeline, GESTrack * track, App * app)
813 if (track->type == GES_TRACK_TYPE_AUDIO)
815 if (track->type == GES_TRACK_TYPE_VIDEO)
818 update_track_actions (app);
822 create_ui (App * app)
825 GtkTreeView *timeline;
826 GtkTreeViewColumn *duration_col;
827 GtkCellRenderer *duration_renderer;
828 GtkCellRenderer *background_type_renderer;
829 GtkListStore *backgrounds;
832 /* construct widget tree */
834 builder = gtk_builder_new ();
835 gtk_builder_add_from_file (builder, "ges-ui.glade", NULL);
837 /* get a bunch of widgets from the XML tree */
839 GET_WIDGET (timeline, "timeline_treeview", GTK_TREE_VIEW);
840 GET_WIDGET (app->properties, "properties", GTK_WIDGET);
841 GET_WIDGET (app->filesource_properties, "filesource_properties", GTK_WIDGET);
842 GET_WIDGET (app->text_properties, "text_properties", GTK_WIDGET);
843 GET_WIDGET (app->main_window, "window", GTK_WIDGET);
844 GET_WIDGET (app->add_effect_dlg, "add_effect_dlg", GTK_WIDGET);
845 GET_WIDGET (app->audio_effect_entry, "entry1", GTK_WIDGET);
846 GET_WIDGET (app->video_effect_entry, "entry2", GTK_WIDGET);
847 GET_WIDGET (app->duration, "duration_scale", GTK_HSCALE);
848 GET_WIDGET (app->in_point, "in_point_scale", GTK_HSCALE);
849 GET_WIDGET (app->halign, "halign", GTK_COMBO_BOX);
850 GET_WIDGET (app->valign, "valign", GTK_COMBO_BOX);
851 GET_WIDGET (app->text, "text", GTK_ENTRY);
852 GET_WIDGET (duration_col, "duration_column", GTK_TREE_VIEW_COLUMN);
853 GET_WIDGET (duration_renderer, "duration_renderer", GTK_CELL_RENDERER);
854 GET_WIDGET (app->add_file, "add_file", GTK_ACTION);
855 GET_WIDGET (app->add_effect, "add_effect", GTK_ACTION);
856 GET_WIDGET (app->add_title, "add_text", GTK_ACTION);
857 GET_WIDGET (app->add_test, "add_test", GTK_ACTION);
858 GET_WIDGET (app->add_transition, "add_transition", GTK_ACTION);
859 GET_WIDGET (app->delete, "delete", GTK_ACTION);
860 GET_WIDGET (app->play, "play", GTK_ACTION);
861 GET_WIDGET (app->stop, "stop", GTK_ACTION);
862 GET_WIDGET (app->move_up, "move_up", GTK_ACTION);
863 GET_WIDGET (app->move_down, "move_down", GTK_ACTION);
864 GET_WIDGET (app->seconds, "seconds", GTK_ENTRY);
865 GET_WIDGET (app->generic_duration, "generic_duration", GTK_WIDGET);
866 GET_WIDGET (app->background_type, "background_type", GTK_COMBO_BOX);
867 GET_WIDGET (app->background_properties, "background_properties", GTK_WIDGET);
868 GET_WIDGET (app->frequency, "frequency", GTK_SPIN_BUTTON);
869 GET_WIDGET (app->volume, "volume", GTK_HSCALE);
870 GET_WIDGET (app->audio_track_action, "audio_track", GTK_TOGGLE_ACTION);
871 GET_WIDGET (app->video_track_action, "video_track", GTK_TOGGLE_ACTION);
873 /* get text notifications */
875 g_signal_connect (app->text, "notify::text",
876 G_CALLBACK (text_notify_text_changed_cb), app);
878 g_signal_connect (app->seconds, "notify::text",
879 G_CALLBACK (seconds_notify_text_changed_cb), app);
881 /* we care when the tree selection changes */
883 if (!(app->selection = gtk_tree_view_get_selection (timeline)))
886 gtk_tree_selection_set_mode (app->selection, GTK_SELECTION_MULTIPLE);
888 g_signal_connect (app->selection, "changed",
889 G_CALLBACK (app_selection_changed_cb), app);
891 /* create the model for the treeview */
894 gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_OBJECT)))
897 gtk_tree_view_set_model (timeline, GTK_TREE_MODEL (app->model));
899 /* register custom cell data function */
901 gtk_tree_view_column_set_cell_data_func (duration_col, duration_renderer,
902 duration_cell_func, NULL, NULL);
904 /* initialize combo boxes */
906 if (!(backgrounds = get_video_patterns ()))
909 if (!(background_type_renderer = gtk_cell_renderer_text_new ()))
912 gtk_combo_box_set_model (app->background_type, (GtkTreeModel *)
915 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (app->background_type),
916 background_type_renderer, FALSE);
918 gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (app->background_type),
919 background_type_renderer, "text", 0);
921 g_signal_connect (app->timeline, "layer-added", G_CALLBACK
922 (layer_added_cb), app);
923 g_signal_connect (app->timeline, "track-added", G_CALLBACK
924 (track_added_cb), app);
925 g_signal_connect (app->timeline, "track-removed", G_CALLBACK
926 (track_removed_cb), app);
928 /* register callbacks on GES objects */
929 bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
930 gst_bus_add_signal_watch (bus);
931 g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), app);
934 gtk_builder_connect_signals (builder, app);
935 gst_object_unref (G_OBJECT (builder));
939 gst_object_unref (G_OBJECT (builder));
945 /* application methods ******************************************************/
947 static void selection_foreach (GtkTreeModel * model, GtkTreePath * path,
948 GtkTreeIter * iter, gpointer user);
951 app_toggle_playpause (App * app)
953 if (app->state != GST_STATE_PLAYING) {
954 gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PLAYING);
956 gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PAUSED);
961 app_stop_playback (App * app)
963 if ((app->state != GST_STATE_NULL) && (app->state != GST_STATE_READY)) {
964 gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_READY);
975 app_update_selection (App * app)
979 select_info info = { NULL, 0 };
981 /* clear old selection */
982 for (cur = app->selected_objects; cur; cur = cur->next) {
983 disconnect_from_object (cur->data, app);
984 gst_object_unref (cur->data);
987 g_list_free (app->selected_objects);
988 app->selected_objects = NULL;
991 /* get new selection */
992 gtk_tree_selection_selected_foreach (GTK_TREE_SELECTION (app->selection),
993 selection_foreach, &info);
994 app->selected_objects = info.objects;
995 app->n_selected = info.n;
998 if (app->selected_objects) {
999 type = G_TYPE_FROM_INSTANCE (app->selected_objects->data);
1000 for (cur = app->selected_objects; cur; cur = cur->next) {
1001 if (type != G_TYPE_FROM_INSTANCE (cur->data)) {
1008 if (type != G_TYPE_NONE) {
1009 for (cur = app->selected_objects; cur; cur = cur->next) {
1010 connect_to_object (cur->data, app);
1014 app->selected_type = type;
1015 app_update_first_last_selected (app);
1019 selection_foreach (GtkTreeModel * model, GtkTreePath * path, GtkTreeIter
1020 * iter, gpointer user)
1022 select_info *info = (select_info *) user;
1025 gtk_tree_model_get (model, iter, 2, &clip, -1);
1026 info->objects = g_list_append (info->objects, clip);
1033 app_get_selected_objects (App * app)
1035 return g_list_copy (app->selected_objects);
1039 app_delete_objects (App * app, GList * objects)
1043 for (cur = objects; cur; cur = cur->next) {
1044 ges_layer_remove_clip (app->layer, GES_CLIP (cur->data));
1048 g_list_free (objects);
1051 /* the following two methods assume exactly one clip is selected and that the
1052 * requested action is valid */
1055 app_move_selected_up (App * app)
1057 GList *objects, *tmp;
1060 objects = ges_layer_get_clips (app->layer);
1061 pos = g_list_index (objects, app->selected_objects->data);
1063 ges_simple_layer_move_object (GES_SIMPLE_LAYER (app->layer),
1064 GES_CLIP (app->selected_objects->data), pos - 1);
1066 for (tmp = objects; tmp; tmp = tmp->next) {
1067 gst_object_unref (tmp->data);
1072 app_add_effect_on_selected_clips (App * app, const gchar * bin_desc)
1074 GList *objects, *tmp;
1075 GESTrackElement *effect = NULL;
1077 /* No crash if the video is playing */
1078 gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PAUSED);
1079 objects = ges_layer_get_clips (app->layer);
1081 for (tmp = objects; tmp; tmp = tmp->next) {
1082 effect = GES_TRACK_ELEMENT (ges_effect_new (bin_desc));
1083 ges_container_add (GES_CONTAINER (tmp->data),
1084 GES_TIMELINE_ELEMENT (effect));
1085 gst_object_unref (tmp->data);
1090 add_effect_dlg_delete_event_cb (GtkWidget * widget, GdkEvent * event,
1093 gtk_widget_hide_all (((App *) app)->add_effect_dlg);
1098 on_cancel_add_effect_cb (GtkButton * button, App * app)
1100 gtk_widget_hide_all (app->add_effect_dlg);
1104 on_apply_effect_cb (GtkButton * button, App * app)
1106 const gchar *effect;
1108 effect = gtk_entry_get_text (GTK_ENTRY (app->video_effect_entry));
1109 if (g_strcmp0 (effect, ""))
1110 app_add_effect_on_selected_clips (app, effect);
1112 gtk_entry_set_text (GTK_ENTRY (app->video_effect_entry), "");
1114 effect = gtk_entry_get_text (GTK_ENTRY (app->audio_effect_entry));
1115 if (g_strcmp0 (effect, ""))
1116 app_add_effect_on_selected_clips (app, effect);
1118 gtk_entry_set_text (GTK_ENTRY (app->audio_effect_entry), "");
1120 gtk_widget_hide_all (app->add_effect_dlg);
1124 app_move_selected_down (App * app)
1126 GList *objects, *tmp;
1129 objects = ges_layer_get_clips (app->layer);
1130 pos = g_list_index (objects, app->selected_objects->data);
1132 ges_simple_layer_move_object (GES_SIMPLE_LAYER (app->layer),
1133 GES_CLIP (app->selected_objects->data), pos - 1);
1135 for (tmp = objects; tmp; tmp = tmp->next) {
1136 gst_object_unref (tmp->data);
1141 app_add_file (App * app, gchar * uri)
1145 GST_DEBUG ("adding file %s", uri);
1147 clip = GES_CLIP (ges_uri_clip_new (uri));
1149 ges_simple_layer_add_object (GES_SIMPLE_LAYER (app->layer), clip, -1);
1153 app_launch_project (App * app, gchar * uri)
1155 GESTimeline *timeline;
1156 GMainLoop *mainloop;
1157 GESTimelinePipeline *pipeline;
1159 GESFormatter *formatter;
1161 uri = g_strsplit (uri, "//", 2)[1];
1162 printf ("we will launch this uri : %s\n", uri);
1163 formatter = GES_FORMATTER (ges_pitivi_formatter_new ());
1164 timeline = ges_timeline_new ();
1165 pipeline = ges_timeline_pipeline_new ();
1166 bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
1167 mainloop = g_main_loop_new (NULL, FALSE);
1169 ges_timeline_pipeline_add_timeline (pipeline, timeline);
1170 ges_formatter_load_from_uri (formatter, timeline, uri, NULL);
1171 ges_timeline_pipeline_set_mode (pipeline, TIMELINE_MODE_PREVIEW_VIDEO);
1172 gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
1173 gst_bus_add_signal_watch (bus);
1174 g_signal_connect (bus, "message", G_CALLBACK (project_bus_message_cb),
1176 g_main_loop_run (mainloop);
1180 app_add_title (App * app)
1184 GST_DEBUG ("adding title");
1186 clip = GES_CLIP (ges_title_clip_new ());
1187 g_object_set (G_OBJECT (clip), "duration", GST_SECOND, NULL);
1189 ges_simple_layer_add_object (GES_SIMPLE_LAYER (app->layer), clip, -1);
1193 app_add_test (App * app)
1197 GST_DEBUG ("adding test");
1199 clip = GES_CLIP (ges_test_clip_new ());
1200 g_object_set (G_OBJECT (clip), "duration", GST_SECOND, NULL);
1202 ges_simple_layer_add_object (GES_SIMPLE_LAYER (app->layer), clip, -1);
1206 app_add_transition (App * app)
1210 GST_DEBUG ("adding transition");
1212 clip = GES_CLIP (ges_transition_clip_new
1213 (GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE));
1214 g_object_set (G_OBJECT (clip), "duration", GST_SECOND, NULL);
1216 ges_simple_layer_add_object (GES_SIMPLE_LAYER (app->layer), clip, -1);
1220 app_save_to_uri (App * app, gchar * uri)
1222 ges_timeline_save_to_uri (app->timeline, uri, NULL, FALSE, NULL);
1226 app_add_audio_track (App * app)
1228 if (app->audio_tracks)
1231 app->audio_track = GES_TRACK (ges_audio_track_new ());
1232 ges_timeline_add_track (app->timeline, app->audio_track);
1236 app_remove_audio_track (App * app)
1238 if (!app->audio_tracks)
1241 ges_timeline_remove_track (app->timeline, app->audio_track);
1242 app->audio_track = NULL;
1246 app_add_video_track (App * app)
1248 if (app->video_tracks)
1251 app->video_track = GES_TRACK (ges_video_track_new ());
1252 ges_timeline_add_track (app->timeline, app->video_track);
1256 app_remove_video_track (App * app)
1258 if (!app->video_tracks)
1261 ges_timeline_remove_track (app->timeline, app->video_track);
1262 app->video_track = NULL;
1266 app_dispose (App * app)
1269 if (app->pipeline) {
1270 gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_NULL);
1271 gst_object_unref (app->pipeline);
1279 if (n_instances == 0) {
1288 ret = g_new0 (App, 1);
1291 ret->selected_type = G_TYPE_NONE;
1296 if (!(ret->timeline = ges_timeline_new ()))
1299 if (!(ret->pipeline = ges_timeline_pipeline_new ()))
1302 if (!ges_timeline_pipeline_add_timeline (ret->pipeline, ret->timeline))
1305 if (!(create_ui (ret)))
1319 GESTrack *a = NULL, *v = NULL;
1323 /* add base audio and video track */
1325 if (!(a = GES_TRACK (ges_audio_track_new ())))
1328 if (!(ges_timeline_add_track (ret->timeline, a)))
1331 if (!(v = GES_TRACK (ges_video_track_new ())))
1334 if (!(ges_timeline_add_track (ret->timeline, v)))
1337 if (!(ret->layer = (GESLayer *) ges_simple_layer_new ()))
1340 if (!(ges_timeline_add_layer (ret->timeline, ret->layer)))
1343 ret->audio_track = a;
1344 ret->video_track = v;
1350 gst_object_unref (a);
1352 gst_object_unref (v);
1358 load_file_async (App * app)
1360 ges_timeline_load_from_uri (app->timeline, app->pending_uri, NULL);
1362 g_free (app->pending_uri);
1363 app->pending_uri = NULL;
1369 app_new_from_uri (gchar * uri)
1374 ret->pending_uri = g_strdup (uri);
1375 g_idle_add ((GSourceFunc) load_file_async, ret);
1380 /* UI callbacks ************************************************************/
1383 window_delete_event_cb (GtkObject * window, GdkEvent * event, App * app)
1390 new_activate_cb (GtkMenuItem * item, App * app)
1396 launch_pitivi_project_activate_cb (GtkMenuItem * item, App * app)
1398 GtkFileChooserDialog *dlg;
1399 GtkFileFilter *filter;
1401 GST_DEBUG ("add file signal handler");
1403 filter = gtk_file_filter_new ();
1404 gtk_file_filter_set_name (filter, "pitivi projects");
1405 gtk_file_filter_add_pattern (filter, "*.xptv");
1406 dlg = (GtkFileChooserDialog *)
1407 gtk_file_chooser_dialog_new ("Preview Project...",
1408 GTK_WINDOW (app->main_window),
1409 GTK_FILE_CHOOSER_ACTION_OPEN,
1411 GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1412 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), filter);
1414 g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL);
1416 if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
1418 uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg));
1419 gtk_widget_destroy ((GtkWidget *) dlg);
1420 app_launch_project (app, uri);
1425 open_activate_cb (GtkMenuItem * item, App * app)
1427 GtkFileChooserDialog *dlg;
1429 GST_DEBUG ("add file signal handler");
1431 dlg = (GtkFileChooserDialog *) gtk_file_chooser_dialog_new ("Open Project...",
1432 GTK_WINDOW (app->main_window),
1433 GTK_FILE_CHOOSER_ACTION_OPEN,
1435 GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1437 g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL);
1439 if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
1441 uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg));
1442 app_new_from_uri (uri);
1445 gtk_widget_destroy ((GtkWidget *) dlg);
1449 save_as_activate_cb (GtkMenuItem * item, App * app)
1451 GtkFileChooserDialog *dlg;
1453 GST_DEBUG ("save as signal handler");
1455 dlg = (GtkFileChooserDialog *)
1456 gtk_file_chooser_dialog_new ("Save project as...",
1457 GTK_WINDOW (app->main_window), GTK_FILE_CHOOSER_ACTION_SAVE,
1458 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_OK,
1461 g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL);
1463 if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
1465 uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg));
1466 app_save_to_uri (app, uri);
1469 gtk_widget_destroy ((GtkWidget *) dlg);
1473 quit_item_activate_cb (GtkMenuItem * item, App * app)
1479 delete_activate_cb (GtkAction * item, App * app)
1481 /* get a gslist of selected track elements */
1482 GList *objects = NULL;
1484 objects = app_get_selected_objects (app);
1485 app_delete_objects (app, objects);
1489 add_effect_activate_cb (GtkAction * item, App * app)
1491 gtk_widget_show_all (app->add_effect_dlg);
1495 add_file_activate_cb (GtkAction * item, App * app)
1497 GtkFileChooserDialog *dlg;
1499 GST_DEBUG ("add file signal handler");
1501 dlg = (GtkFileChooserDialog *) gtk_file_chooser_dialog_new ("Add File...",
1502 GTK_WINDOW (app->main_window),
1503 GTK_FILE_CHOOSER_ACTION_OPEN,
1505 GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1507 g_object_set (G_OBJECT (dlg), "select-multiple", TRUE, NULL);
1509 if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
1512 uris = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (dlg));
1513 for (cur = uris; cur; cur = cur->next)
1514 app_add_file (app, cur->data);
1515 g_slist_free (uris);
1517 gtk_widget_destroy ((GtkWidget *) dlg);
1521 add_text_activate_cb (GtkAction * item, App * app)
1523 app_add_title (app);
1527 add_test_activate_cb (GtkAction * item, App * app)
1533 add_transition_activate_cb (GtkAction * item, App * app)
1535 app_add_transition (app);
1539 play_activate_cb (GtkAction * item, App * app)
1541 app_toggle_playpause (app);
1545 stop_activate_cb (GtkAction * item, App * app)
1547 app_stop_playback (app);
1551 move_up_activate_cb (GtkAction * item, App * app)
1553 app_move_selected_up (app);
1557 move_down_activate_cb (GtkAction * item, App * app)
1559 app_move_selected_down (app);
1563 audio_track_activate_cb (GtkToggleAction * item, App * app)
1565 if (gtk_toggle_action_get_active (item)) {
1566 app_add_audio_track (app);
1568 app_remove_audio_track (app);
1573 video_track_activate_cb (GtkToggleAction * item, App * app)
1575 if (gtk_toggle_action_get_active (item)) {
1576 app_add_video_track (app);
1578 app_remove_video_track (app);
1583 app_selection_changed_cb (GtkTreeSelection * selection, App * app)
1585 app_update_selection (app);
1587 update_delete_sensitivity (app);
1588 update_effect_sensitivity (app);
1589 update_add_transition_sensitivity (app);
1590 update_move_up_down_sensitivity (app);
1592 gtk_widget_set_visible (app->properties, app->n_selected > 0);
1594 gtk_widget_set_visible (app->filesource_properties,
1595 app->selected_type == GES_TYPE_URI_CLIP);
1597 gtk_widget_set_visible (app->text_properties,
1598 app->selected_type == GES_TYPE_TITLE_CLIP);
1600 gtk_widget_set_visible (app->generic_duration,
1601 app->selected_type != G_TYPE_NONE &&
1602 app->selected_type != G_TYPE_INVALID);
1604 gtk_widget_set_visible (app->background_properties,
1605 app->selected_type == GES_TYPE_TEST_CLIP);
1609 duration_scale_change_value_cb (GtkRange * range, GtkScrollType unused,
1610 gdouble value, App * app)
1614 for (i = app->selected_objects; i; i = i->next) {
1615 guint64 duration, maxduration;
1616 maxduration = GES_TIMELINE_ELEMENT_MAX_DURATION (i->data);
1617 duration = (value < maxduration ? (value > 0 ? value : 0) : maxduration);
1618 g_object_set (G_OBJECT (i->data), "duration", (guint64) duration, NULL);
1624 in_point_scale_change_value_cb (GtkRange * range, GtkScrollType unused,
1625 gdouble value, App * app)
1629 for (i = app->selected_objects; i; i = i->next) {
1630 guint64 in_point, maxduration;
1631 maxduration = GES_TIMELINE_ELEMENT_MAX_DURATION (i->data) -
1632 GES_TIMELINE_ELEMENT_DURATION (i->data);
1633 in_point = (value < maxduration ? (value > 0 ? value : 0) : maxduration);
1634 g_object_set (G_OBJECT (i->data), "in-point", (guint64) in_point, NULL);
1640 halign_changed_cb (GtkComboBox * widget, App * app)
1645 if (app->ignore_input)
1648 active = gtk_combo_box_get_active (app->halign);
1650 for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1651 g_object_set (G_OBJECT (tmp->data), "halignment", active, NULL);
1656 valign_changed_cb (GtkComboBox * widget, App * app)
1661 if (app->ignore_input)
1664 active = gtk_combo_box_get_active (app->valign);
1666 for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1667 g_object_set (G_OBJECT (tmp->data), "valignment", active, NULL);
1672 background_type_changed_cb (GtkComboBox * widget, App * app)
1677 if (app->ignore_input)
1680 p = gtk_combo_box_get_active (widget);
1682 for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1683 g_object_set (G_OBJECT (tmp->data), "vpattern", (gint) p, NULL);
1688 frequency_value_changed_cb (GtkSpinButton * widget, App * app)
1693 if (app->ignore_input)
1696 value = gtk_spin_button_get_value (widget);
1698 for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1699 g_object_set (G_OBJECT (tmp->data), "freq", (gdouble) value, NULL);
1704 volume_change_value_cb (GtkRange * widget, GtkScrollType unused, gdouble
1709 value = value >= 0 ? (value <= 2.0 ? value : 2.0) : 0;
1711 for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1712 g_object_set (G_OBJECT (tmp->data), "volume", (gdouble) value, NULL);
1717 /* main *********************************************************************/
1720 main (int argc, char *argv[])
1724 /* intialize GStreamer and GES */
1725 gst_init (&argc, &argv);
1729 gtk_init (&argc, &argv);
1731 if ((app = app_new ())) {