056716cd52fa92fb29fb341ab1e4a1ef8c59de16
[platform/upstream/gstreamer.git] / tests / examples / ges-ui.c
1 /* GStreamer Editing Services
2  * Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk>
3  *               2010 Nokia Corporation
4  *
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.
9  *
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.
14  *
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.
19  */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <gtk/gtk.h>
25 #include <glib.h>
26 #include <glib/gprintf.h>
27 #include <ges/ges.h>
28
29 /* Application Data ********************************************************/
30
31 /**
32  * Contains most of the application data so that signal handlers
33  * and other callbacks have easy access.
34  */
35
36 typedef struct App
37 {
38   /* back-end objects */
39   GESTimeline *timeline;
40   GESTimelinePipeline *pipeline;
41   GESLayer *layer;
42   GESTrack *audio_track;
43   GESTrack *video_track;
44   guint audio_tracks;
45   guint video_tracks;
46
47   /* application state */
48   gchar *pending_uri;
49   int n_objects;
50
51   int n_selected;
52   GList *selected_objects;
53   GType selected_type;
54   gboolean first_selected;
55   gboolean last_selected;
56
57   gboolean ignore_input;
58   GstState state;
59
60   GtkListStore *model;
61   GtkTreeSelection *selection;
62
63   /* widgets */
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;
73
74   GtkHScale *duration;
75   GtkHScale *in_point;
76   GtkHScale *volume;
77
78   GtkAction *add_file;
79   GtkAction *add_effect;
80   GtkAction *add_test;
81   GtkAction *add_title;
82   GtkAction *add_transition;
83   GtkAction *delete;
84   GtkAction *play;
85   GtkAction *stop;
86   GtkAction *move_up;
87   GtkAction *move_down;
88   GtkToggleAction *audio_track_action;
89   GtkToggleAction *video_track_action;
90
91   GtkComboBox *halign;
92   GtkComboBox *valign;
93   GtkComboBox *background_type;
94
95   GtkEntry *text;
96   GtkEntry *seconds;
97
98   GtkSpinButton *frequency;
99 } App;
100
101 static int n_instances = 0;
102
103 /* Prototypes for auto-connected signal handlers ***************************/
104
105 /**
106  * These are declared non-static for signal auto-connection
107  */
108
109 gboolean window_delete_event_cb (GtkObject * window, GdkEvent * event,
110     App * app);
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,
136     gpointer * app);
137
138 gboolean
139 duration_scale_change_value_cb (GtkRange * range,
140     GtkScrollType unused, gdouble value, App * app);
141
142 gboolean
143 in_point_scale_change_value_cb (GtkRange * range,
144     GtkScrollType unused, gdouble value, App * app);
145
146 gboolean
147 volume_change_value_cb (GtkRange * range,
148     GtkScrollType unused, gdouble value, App * app);
149
150 /* UI state functions *******************************************************/
151
152 /**
153  * Update properties of UI elements that depend on more than one thing.
154  */
155
156 static void
157 update_effect_sensitivity (App * app)
158 {
159   GList *i;
160   gboolean ok = TRUE;
161
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)) {
165       ok = FALSE;
166       break;
167     }
168   }
169
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));
173 }
174
175 static void
176 update_delete_sensitivity (App * app)
177 {
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));
182 }
183
184 static void
185 update_add_transition_sensitivity (App * app)
186 {
187   gtk_action_set_sensitive (app->add_transition,
188       (app->state != GST_STATE_PLAYING) && (app->state != GST_STATE_PAUSED));
189 }
190
191 static void
192 update_move_up_down_sensitivity (App * app)
193 {
194   gboolean can_move;
195
196   can_move = (app->n_selected == 1) &&
197       (app->state != GST_STATE_PLAYING) && (app->state != GST_STATE_PAUSED);
198
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));
201 }
202
203 static void
204 update_play_sensitivity (App * app)
205 {
206   gboolean valid;
207
208   g_object_get (app->layer, "valid", &valid, NULL);
209
210   gtk_action_set_sensitive (app->play, (app->n_objects && valid));
211 }
212
213 /* Backend callbacks ********************************************************/
214
215 static void
216 test_source_notify_volume_changed_cb (GESClip * clip, GParamSpec *
217     unused G_GNUC_UNUSED, App * app)
218 {
219   gdouble volume;
220
221   g_object_get (G_OBJECT (clip), "volume", &volume, NULL);
222
223   gtk_range_set_value (GTK_RANGE (app->volume), volume);
224 }
225
226 static void
227 layer_notify_valid_changed_cb (GObject * object, GParamSpec * unused
228     G_GNUC_UNUSED, App * app)
229 {
230   update_play_sensitivity (app);
231 }
232
233 static gboolean
234 find_row_for_object (GtkListStore * model, GtkTreeIter * ret, GESClip * clip)
235 {
236   gtk_tree_model_get_iter_first ((GtkTreeModel *) model, ret);
237
238   while (gtk_list_store_iter_is_valid (model, ret)) {
239     GESClip *clip2;
240     gtk_tree_model_get ((GtkTreeModel *) model, ret, 2, &clip2, -1);
241     if (clip2 == clip) {
242       gst_object_unref (clip2);
243       return TRUE;
244     }
245     gst_object_unref (clip2);
246     gtk_tree_model_iter_next ((GtkTreeModel *) model, ret);
247   }
248   return FALSE;
249 }
250
251 /* this callback is registered for every clip, and updates the
252  * corresponding duration cell in the model */
253 static void
254 clip_notify_duration_cb (GESClip * clip,
255     GParamSpec * arg G_GNUC_UNUSED, App * app)
256 {
257   GtkTreeIter iter;
258   guint64 duration = 0;
259
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);
263 }
264
265 /* these guys are only connected to filesources that are the target of the
266  * current selection */
267
268 static void
269 filesource_notify_duration_cb (GESClip * clip,
270     GParamSpec * arg G_GNUC_UNUSED, App * app)
271 {
272   guint64 duration, max_inpoint;
273   duration = GES_TIMELINE_ELEMENT_DURATION (clip);
274   max_inpoint = GES_TIMELINE_ELEMENT_MAX_DURATION (clip) - duration;
275
276   gtk_range_set_value (GTK_RANGE (app->duration), duration);
277   gtk_range_set_fill_level (GTK_RANGE (app->in_point), max_inpoint);
278
279   if (max_inpoint < GES_TIMELINE_ELEMENT_INPOINT (clip))
280     g_object_set (clip, "in-point", max_inpoint, NULL);
281
282 }
283
284 static void
285 filesource_notify_max_duration_cb (GESClip * clip,
286     GParamSpec * arg G_GNUC_UNUSED, App * app)
287 {
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));
292 }
293
294 static void
295 filesource_notify_in_point_cb (GESClip * clip,
296     GParamSpec * arg G_GNUC_UNUSED, App * app)
297 {
298   gtk_range_set_value (GTK_RANGE (app->in_point),
299       GES_TIMELINE_ELEMENT_INPOINT (clip));
300 }
301
302 static void
303 app_update_first_last_selected (App * app)
304 {
305   GtkTreePath *path;
306
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);
312
313   path = gtk_tree_path_new_from_indices (app->n_objects - 1, -1);
314   app->last_selected =
315       gtk_tree_selection_path_is_selected (app->selection, path);
316   gtk_tree_path_free (path);
317 }
318
319 static void
320 object_count_changed (App * app)
321 {
322   app_update_first_last_selected (app);
323   update_move_up_down_sensitivity (app);
324   update_play_sensitivity (app);
325 }
326
327 static void
328 title_source_text_changed_cb (GESClip * clip,
329     GParamSpec * arg G_GNUC_UNUSED, App * app)
330 {
331   GtkTreeIter iter;
332   gchar *text;
333
334   g_object_get (clip, "text", &text, NULL);
335   if (text) {
336     find_row_for_object (app->model, &iter, clip);
337     gtk_list_store_set (app->model, &iter, 0, text, -1);
338   }
339 }
340
341 static void
342 layer_object_added_cb (GESLayer * layer, GESClip * clip, App * app)
343 {
344   GtkTreeIter iter;
345   gchar *description;
346
347   GST_INFO ("layer clip added cb %p %p %p", layer, clip, app);
348
349   gtk_list_store_append (app->model, &iter);
350
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);
354   }
355
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);
361   }
362
363   else if (GES_IS_TEST_CLIP (clip)) {
364     gtk_list_store_set (app->model, &iter, 2, clip, 0, "Test Source", -1);
365   }
366
367   else if (GES_IS_BASE_TRANSITION_CLIP (clip)) {
368     gtk_list_store_set (app->model, &iter, 2, clip, 0, "Transition", -1);
369   }
370
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);
374
375   app->n_objects++;
376   object_count_changed (app);
377 }
378
379 static void
380 layer_object_removed_cb (GESLayer * layer, GESClip * clip, App * app)
381 {
382   GtkTreeIter iter;
383
384   GST_INFO ("layer clip removed cb %p %p %p", layer, clip, app);
385
386   if (!find_row_for_object (GTK_LIST_STORE (app->model), &iter, clip)) {
387     g_print ("clip deleted but we don't own it");
388     return;
389   }
390   app->n_objects--;
391   object_count_changed (app);
392
393   gtk_list_store_remove (app->model, &iter);
394 }
395
396 static void
397 layer_object_moved_cb (GESClip * layer, GESClip * clip,
398     gint old, gint new, App * app)
399 {
400   GtkTreeIter a, b;
401   GtkTreePath *path;
402
403   /* we can take the old position as given, but the new position might have to
404    * be adjusted. */
405   new = new < 0 ? (app->n_objects - 1) : new;
406
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);
410
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);
414
415   gtk_list_store_swap (app->model, &a, &b);
416   app_selection_changed_cb (app->selection, app);
417   update_move_up_down_sensitivity (app);
418 }
419
420 static void
421 pipeline_state_changed_cb (App * app)
422 {
423   gboolean playing_or_paused;
424
425   if (app->state == GST_STATE_PLAYING)
426     gtk_action_set_stock_id (app->play, GTK_STOCK_MEDIA_PAUSE);
427   else
428     gtk_action_set_stock_id (app->play, GTK_STOCK_MEDIA_PLAY);
429
430   update_delete_sensitivity (app);
431   update_add_transition_sensitivity (app);
432   update_move_up_down_sensitivity (app);
433
434   playing_or_paused = (app->state == GST_STATE_PLAYING) ||
435       (app->state == GST_STATE_PAUSED);
436
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,
441       !playing_or_paused);
442   gtk_action_set_sensitive ((GtkAction *) app->video_track_action,
443       !playing_or_paused);
444   gtk_widget_set_sensitive (app->properties, !playing_or_paused);
445 }
446
447 static void
448 project_bus_message_cb (GstBus * bus, GstMessage * message,
449     GMainLoop * mainloop)
450 {
451   switch (GST_MESSAGE_TYPE (message)) {
452     case GST_MESSAGE_ERROR:
453       g_printerr ("ERROR\n");
454       g_main_loop_quit (mainloop);
455       break;
456     case GST_MESSAGE_EOS:
457       g_printerr ("Done\n");
458       g_main_loop_quit (mainloop);
459       break;
460     default:
461       break;
462   }
463 }
464
465 static void
466 bus_message_cb (GstBus * bus, GstMessage * message, App * app)
467 {
468   const GstStructure *s;
469   s = gst_message_get_structure (message);
470
471   switch (GST_MESSAGE_TYPE (message)) {
472     case GST_MESSAGE_ERROR:
473       g_print ("ERROR\n");
474       break;
475     case GST_MESSAGE_EOS:
476       gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_READY);
477       break;
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);
482         app->state = new;
483         pipeline_state_changed_cb (app);
484       }
485       break;
486     default:
487       break;
488   }
489 }
490
491 /* Static UI Callbacks ******************************************************/
492
493 static gboolean
494 check_time (const gchar * time)
495 {
496   static GRegex *re = NULL;
497
498   if (!re) {
499     if (NULL == (re =
500             g_regex_new ("^[0-9][0-9]:[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?$",
501                 G_REGEX_EXTENDED, 0, NULL)))
502       return FALSE;
503   }
504
505   if (g_regex_match (re, time, 0, NULL))
506     return TRUE;
507   return FALSE;
508 }
509
510 static guint64
511 str_to_time (const gchar * str)
512 {
513   guint64 ret;
514   guint64 h, m;
515   gdouble s;
516   gchar buf[15];
517
518   buf[0] = str[0];
519   buf[1] = str[1];
520   buf[2] = '\0';
521
522   h = strtoull (buf, NULL, 10);
523
524   buf[0] = str[3];
525   buf[1] = str[4];
526   buf[2] = '\0';
527
528   m = strtoull (buf, NULL, 10);
529
530   strncpy (buf, &str[6], sizeof (buf));
531   s = strtod (buf, NULL);
532
533   ret = (h * 3600 * GST_SECOND) +
534       (m * 60 * GST_SECOND) + ((guint64) (s * GST_SECOND));
535
536   return ret;
537 }
538
539 static void
540 text_notify_text_changed_cb (GtkEntry * widget, GParamSpec * unused, App * app)
541 {
542   GList *tmp;
543   const gchar *text;
544
545   if (app->ignore_input)
546     return;
547
548   text = gtk_entry_get_text (widget);
549
550   for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
551     g_object_set (G_OBJECT (tmp->data), "text", text, NULL);
552   }
553 }
554
555 static void
556 seconds_notify_text_changed_cb (GtkEntry * widget, GParamSpec * unused,
557     App * app)
558 {
559   GList *tmp;
560   const gchar *text;
561
562   if (app->ignore_input)
563     return;
564
565   text = gtk_entry_get_text (app->seconds);
566
567   if (!check_time (text)) {
568     gtk_entry_set_icon_from_stock (app->seconds,
569         GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_DIALOG_WARNING);
570   } else {
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);
576     }
577   }
578 }
579
580 static void
581 duration_cell_func (GtkTreeViewColumn * column, GtkCellRenderer * renderer,
582     GtkTreeModel * model, GtkTreeIter * iter, gpointer user)
583 {
584   gchar buf[30];
585   guint64 duration;
586
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);
590 }
591
592 /* UI Initialization ********************************************************/
593
594 static void
595 connect_to_filesource (GESClip * clip, App * app)
596 {
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);
600
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);
604
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);
608 }
609
610 static void
611 disconnect_from_filesource (GESClip * clip, App * app)
612 {
613   g_signal_handlers_disconnect_by_func (G_OBJECT (clip),
614       filesource_notify_duration_cb, app);
615
616   g_signal_handlers_disconnect_by_func (G_OBJECT (clip),
617       filesource_notify_max_duration_cb, app);
618 }
619
620 static void
621 connect_to_title_source (GESClip * clip, App * app)
622 {
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));
630 }
631
632 static void
633 disconnect_from_title_source (GESClip * clip, App * app)
634 {
635 }
636
637 static void
638 connect_to_test_source (GESClip * clip, App * app)
639 {
640   GObjectClass *klass;
641   GParamSpecDouble *pspec;
642
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));
647
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);
651
652   klass = G_OBJECT_GET_CLASS (G_OBJECT (testclip));
653
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);
656
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)));
661 }
662
663 static void
664 disconnect_from_test_source (GESClip * clip, App * app)
665 {
666   g_signal_handlers_disconnect_by_func (G_OBJECT (clip),
667       test_source_notify_volume_changed_cb, app);
668 }
669
670 static void
671 connect_to_object (GESClip * clip, App * app)
672 {
673   gchar buf[30];
674   guint64 duration;
675
676   app->ignore_input = TRUE;
677
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);
682
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);
689   }
690
691   app->ignore_input = FALSE;
692 }
693
694 static void
695 disconnect_from_object (GESClip * clip, App * app)
696 {
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);
703   }
704 }
705
706 static GtkListStore *
707 get_video_patterns (void)
708 {
709   GEnumClass *enum_class;
710   GESTestClip *tr;
711   GESTestClipClass *klass;
712   GParamSpec *pspec;
713   GEnumValue *v;
714   GtkListStore *m;
715   GtkTreeIter i;
716
717   m = gtk_list_store_new (1, G_TYPE_STRING);
718
719   tr = ges_test_clip_new ();
720   klass = GES_TEST_CLIP_GET_CLASS (tr);
721
722   pspec = g_object_class_find_property (G_OBJECT_CLASS (klass), "vpattern");
723
724   enum_class = G_ENUM_CLASS (g_type_class_ref (pspec->value_type));
725
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);
729   }
730
731   g_type_class_unref (enum_class);
732   gst_object_unref (tr);
733
734   return m;
735 }
736
737 #define GET_WIDGET(dest,name,type) {\
738   if (!(dest =\
739     type(gtk_builder_get_object(builder, name))))\
740         goto fail;\
741 }
742
743
744 static void
745 layer_added_cb (GESTimeline * timeline, GESLayer * layer, App * app)
746 {
747   if (!GES_IS_SIMPLE_LAYER (layer)) {
748     GST_ERROR ("This timeline contains a layer type other than "
749         "GESSimpleLayer. Timeline editing disabled");
750     return;
751   }
752
753   if (!(app->layer)) {
754     app->layer = layer;
755   }
756
757   if (layer != app->layer) {
758     GST_ERROR ("This demo doesn't support editing timelines with multiple"
759         " layers");
760     return;
761   }
762
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);
771 }
772
773 static void
774 update_track_actions (App * app)
775 {
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);
790 }
791
792 static void
793 track_added_cb (GESTimeline * timeline, GESTrack * track, App * app)
794 {
795   if (track->type == GES_TRACK_TYPE_AUDIO) {
796     app->audio_tracks++;
797     if (!app->audio_track)
798       app->audio_track = track;
799   }
800   if (track->type == GES_TRACK_TYPE_VIDEO) {
801     app->video_tracks++;
802     if (!app->video_track)
803       app->video_track = track;
804   }
805
806
807   update_track_actions (app);
808 }
809
810 static void
811 track_removed_cb (GESTimeline * timeline, GESTrack * track, App * app)
812 {
813   if (track->type == GES_TRACK_TYPE_AUDIO)
814     app->audio_tracks--;
815   if (track->type == GES_TRACK_TYPE_VIDEO)
816     app->video_tracks--;
817
818   update_track_actions (app);
819 }
820
821 static gboolean
822 create_ui (App * app)
823 {
824   GtkBuilder *builder;
825   GtkTreeView *timeline;
826   GtkTreeViewColumn *duration_col;
827   GtkCellRenderer *duration_renderer;
828   GtkCellRenderer *background_type_renderer;
829   GtkListStore *backgrounds;
830   GstBus *bus;
831
832   /* construct widget tree */
833
834   builder = gtk_builder_new ();
835   gtk_builder_add_from_file (builder, "ges-ui.glade", NULL);
836
837   /* get a bunch of widgets from the XML tree */
838
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);
872
873   /* get text notifications */
874
875   g_signal_connect (app->text, "notify::text",
876       G_CALLBACK (text_notify_text_changed_cb), app);
877
878   g_signal_connect (app->seconds, "notify::text",
879       G_CALLBACK (seconds_notify_text_changed_cb), app);
880
881   /* we care when the tree selection changes */
882
883   if (!(app->selection = gtk_tree_view_get_selection (timeline)))
884     goto fail;
885
886   gtk_tree_selection_set_mode (app->selection, GTK_SELECTION_MULTIPLE);
887
888   g_signal_connect (app->selection, "changed",
889       G_CALLBACK (app_selection_changed_cb), app);
890
891   /* create the model for the treeview */
892
893   if (!(app->model =
894           gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_OBJECT)))
895     goto fail;
896
897   gtk_tree_view_set_model (timeline, GTK_TREE_MODEL (app->model));
898
899   /* register custom cell data function */
900
901   gtk_tree_view_column_set_cell_data_func (duration_col, duration_renderer,
902       duration_cell_func, NULL, NULL);
903
904   /* initialize combo boxes */
905
906   if (!(backgrounds = get_video_patterns ()))
907     goto fail;
908
909   if (!(background_type_renderer = gtk_cell_renderer_text_new ()))
910     goto fail;
911
912   gtk_combo_box_set_model (app->background_type, (GtkTreeModel *)
913       backgrounds);
914
915   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (app->background_type),
916       background_type_renderer, FALSE);
917
918   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (app->background_type),
919       background_type_renderer, "text", 0);
920
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);
927
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);
932
933   /* success */
934   gtk_builder_connect_signals (builder, app);
935   gst_object_unref (G_OBJECT (builder));
936   return TRUE;
937
938 fail:
939   gst_object_unref (G_OBJECT (builder));
940   return FALSE;
941 }
942
943 #undef GET_WIDGET
944
945 /* application methods ******************************************************/
946
947 static void selection_foreach (GtkTreeModel * model, GtkTreePath * path,
948     GtkTreeIter * iter, gpointer user);
949
950 static void
951 app_toggle_playpause (App * app)
952 {
953   if (app->state != GST_STATE_PLAYING) {
954     gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PLAYING);
955   } else {
956     gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PAUSED);
957   }
958 }
959
960 static void
961 app_stop_playback (App * app)
962 {
963   if ((app->state != GST_STATE_NULL) && (app->state != GST_STATE_READY)) {
964     gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_READY);
965   }
966 }
967
968 typedef struct
969 {
970   GList *objects;
971   guint n;
972 } select_info;
973
974 static void
975 app_update_selection (App * app)
976 {
977   GList *cur;
978   GType type;
979   select_info info = { NULL, 0 };
980
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);
985     cur->data = NULL;
986   }
987   g_list_free (app->selected_objects);
988   app->selected_objects = NULL;
989   app->n_selected = 0;
990
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;
996
997   type = G_TYPE_NONE;
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)) {
1002         type = G_TYPE_NONE;
1003         break;
1004       }
1005     }
1006   }
1007
1008   if (type != G_TYPE_NONE) {
1009     for (cur = app->selected_objects; cur; cur = cur->next) {
1010       connect_to_object (cur->data, app);
1011     }
1012   }
1013
1014   app->selected_type = type;
1015   app_update_first_last_selected (app);
1016 }
1017
1018 static void
1019 selection_foreach (GtkTreeModel * model, GtkTreePath * path, GtkTreeIter
1020     * iter, gpointer user)
1021 {
1022   select_info *info = (select_info *) user;
1023   GESClip *clip;
1024
1025   gtk_tree_model_get (model, iter, 2, &clip, -1);
1026   info->objects = g_list_append (info->objects, clip);
1027
1028   info->n++;
1029   return;
1030 }
1031
1032 static GList *
1033 app_get_selected_objects (App * app)
1034 {
1035   return g_list_copy (app->selected_objects);
1036 }
1037
1038 static void
1039 app_delete_objects (App * app, GList * objects)
1040 {
1041   GList *cur;
1042
1043   for (cur = objects; cur; cur = cur->next) {
1044     ges_layer_remove_clip (app->layer, GES_CLIP (cur->data));
1045     cur->data = NULL;
1046   }
1047
1048   g_list_free (objects);
1049 }
1050
1051 /* the following two methods assume exactly one clip is selected and that the
1052  * requested action is valid */
1053
1054 static void
1055 app_move_selected_up (App * app)
1056 {
1057   GList *objects, *tmp;
1058   gint pos;
1059
1060   objects = ges_layer_get_clips (app->layer);
1061   pos = g_list_index (objects, app->selected_objects->data);
1062
1063   ges_simple_layer_move_object (GES_SIMPLE_LAYER (app->layer),
1064       GES_CLIP (app->selected_objects->data), pos - 1);
1065
1066   for (tmp = objects; tmp; tmp = tmp->next) {
1067     gst_object_unref (tmp->data);
1068   }
1069 }
1070
1071 static void
1072 app_add_effect_on_selected_clips (App * app, const gchar * bin_desc)
1073 {
1074   GList *objects, *tmp;
1075   GESTrackElement *effect = NULL;
1076
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);
1080
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);
1086   }
1087 }
1088
1089 gboolean
1090 add_effect_dlg_delete_event_cb (GtkWidget * widget, GdkEvent * event,
1091     gpointer * app)
1092 {
1093   gtk_widget_hide_all (((App *) app)->add_effect_dlg);
1094   return TRUE;
1095 }
1096
1097 void
1098 on_cancel_add_effect_cb (GtkButton * button, App * app)
1099 {
1100   gtk_widget_hide_all (app->add_effect_dlg);
1101 }
1102
1103 void
1104 on_apply_effect_cb (GtkButton * button, App * app)
1105 {
1106   const gchar *effect;
1107
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);
1111
1112   gtk_entry_set_text (GTK_ENTRY (app->video_effect_entry), "");
1113
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);
1117
1118   gtk_entry_set_text (GTK_ENTRY (app->audio_effect_entry), "");
1119
1120   gtk_widget_hide_all (app->add_effect_dlg);
1121 }
1122
1123 static void
1124 app_move_selected_down (App * app)
1125 {
1126   GList *objects, *tmp;
1127   gint pos;
1128
1129   objects = ges_layer_get_clips (app->layer);
1130   pos = g_list_index (objects, app->selected_objects->data);
1131
1132   ges_simple_layer_move_object (GES_SIMPLE_LAYER (app->layer),
1133       GES_CLIP (app->selected_objects->data), pos - 1);
1134
1135   for (tmp = objects; tmp; tmp = tmp->next) {
1136     gst_object_unref (tmp->data);
1137   }
1138 }
1139
1140 static void
1141 app_add_file (App * app, gchar * uri)
1142 {
1143   GESClip *clip;
1144
1145   GST_DEBUG ("adding file %s", uri);
1146
1147   clip = GES_CLIP (ges_uri_clip_new (uri));
1148
1149   ges_simple_layer_add_object (GES_SIMPLE_LAYER (app->layer), clip, -1);
1150 }
1151
1152 static void
1153 app_launch_project (App * app, gchar * uri)
1154 {
1155   GESTimeline *timeline;
1156   GMainLoop *mainloop;
1157   GESTimelinePipeline *pipeline;
1158   GstBus *bus;
1159   GESFormatter *formatter;
1160
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);
1168
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),
1175       mainloop);
1176   g_main_loop_run (mainloop);
1177 }
1178
1179 static void
1180 app_add_title (App * app)
1181 {
1182   GESClip *clip;
1183
1184   GST_DEBUG ("adding title");
1185
1186   clip = GES_CLIP (ges_title_clip_new ());
1187   g_object_set (G_OBJECT (clip), "duration", GST_SECOND, NULL);
1188
1189   ges_simple_layer_add_object (GES_SIMPLE_LAYER (app->layer), clip, -1);
1190 }
1191
1192 static void
1193 app_add_test (App * app)
1194 {
1195   GESClip *clip;
1196
1197   GST_DEBUG ("adding test");
1198
1199   clip = GES_CLIP (ges_test_clip_new ());
1200   g_object_set (G_OBJECT (clip), "duration", GST_SECOND, NULL);
1201
1202   ges_simple_layer_add_object (GES_SIMPLE_LAYER (app->layer), clip, -1);
1203 }
1204
1205 static void
1206 app_add_transition (App * app)
1207 {
1208   GESClip *clip;
1209
1210   GST_DEBUG ("adding transition");
1211
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);
1215
1216   ges_simple_layer_add_object (GES_SIMPLE_LAYER (app->layer), clip, -1);
1217 }
1218
1219 static void
1220 app_save_to_uri (App * app, gchar * uri)
1221 {
1222   ges_timeline_save_to_uri (app->timeline, uri, NULL, FALSE, NULL);
1223 }
1224
1225 static void
1226 app_add_audio_track (App * app)
1227 {
1228   if (app->audio_tracks)
1229     return;
1230
1231   app->audio_track = GES_TRACK (ges_audio_track_new ());
1232   ges_timeline_add_track (app->timeline, app->audio_track);
1233 }
1234
1235 static void
1236 app_remove_audio_track (App * app)
1237 {
1238   if (!app->audio_tracks)
1239     return;
1240
1241   ges_timeline_remove_track (app->timeline, app->audio_track);
1242   app->audio_track = NULL;
1243 }
1244
1245 static void
1246 app_add_video_track (App * app)
1247 {
1248   if (app->video_tracks)
1249     return;
1250
1251   app->video_track = GES_TRACK (ges_video_track_new ());
1252   ges_timeline_add_track (app->timeline, app->video_track);
1253 }
1254
1255 static void
1256 app_remove_video_track (App * app)
1257 {
1258   if (!app->video_tracks)
1259     return;
1260
1261   ges_timeline_remove_track (app->timeline, app->video_track);
1262   app->video_track = NULL;
1263 }
1264
1265 static void
1266 app_dispose (App * app)
1267 {
1268   if (app) {
1269     if (app->pipeline) {
1270       gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_NULL);
1271       gst_object_unref (app->pipeline);
1272     }
1273
1274     g_free (app);
1275   }
1276
1277   n_instances--;
1278
1279   if (n_instances == 0) {
1280     gtk_main_quit ();
1281   }
1282 }
1283
1284 static App *
1285 app_init (void)
1286 {
1287   App *ret;
1288   ret = g_new0 (App, 1);
1289   n_instances++;
1290
1291   ret->selected_type = G_TYPE_NONE;
1292
1293   if (!ret)
1294     return NULL;
1295
1296   if (!(ret->timeline = ges_timeline_new ()))
1297     goto fail;
1298
1299   if (!(ret->pipeline = ges_timeline_pipeline_new ()))
1300     goto fail;
1301
1302   if (!ges_timeline_pipeline_add_timeline (ret->pipeline, ret->timeline))
1303     goto fail;
1304
1305   if (!(create_ui (ret)))
1306     goto fail;
1307
1308   return ret;
1309
1310 fail:
1311   app_dispose (ret);
1312   return NULL;
1313 }
1314
1315 static App *
1316 app_new (void)
1317 {
1318   App *ret;
1319   GESTrack *a = NULL, *v = NULL;
1320
1321   ret = app_init ();
1322
1323   /* add base audio and video track */
1324
1325   if (!(a = GES_TRACK (ges_audio_track_new ())))
1326     goto fail;
1327
1328   if (!(ges_timeline_add_track (ret->timeline, a)))
1329     goto fail;
1330
1331   if (!(v = GES_TRACK (ges_video_track_new ())))
1332     goto fail;
1333
1334   if (!(ges_timeline_add_track (ret->timeline, v)))
1335     goto fail;
1336
1337   if (!(ret->layer = (GESLayer *) ges_simple_layer_new ()))
1338     goto fail;
1339
1340   if (!(ges_timeline_add_layer (ret->timeline, ret->layer)))
1341     goto fail;
1342
1343   ret->audio_track = a;
1344   ret->video_track = v;
1345   return ret;
1346
1347 fail:
1348
1349   if (a)
1350     gst_object_unref (a);
1351   if (v)
1352     gst_object_unref (v);
1353   app_dispose (ret);
1354   return NULL;
1355 }
1356
1357 static gboolean
1358 load_file_async (App * app)
1359 {
1360   ges_timeline_load_from_uri (app->timeline, app->pending_uri, NULL);
1361
1362   g_free (app->pending_uri);
1363   app->pending_uri = NULL;
1364
1365   return FALSE;
1366 }
1367
1368 static gboolean
1369 app_new_from_uri (gchar * uri)
1370 {
1371   App *ret;
1372
1373   ret = app_init ();
1374   ret->pending_uri = g_strdup (uri);
1375   g_idle_add ((GSourceFunc) load_file_async, ret);
1376
1377   return FALSE;
1378 }
1379
1380 /* UI callbacks  ************************************************************/
1381
1382 gboolean
1383 window_delete_event_cb (GtkObject * window, GdkEvent * event, App * app)
1384 {
1385   app_dispose (app);
1386   return FALSE;
1387 }
1388
1389 void
1390 new_activate_cb (GtkMenuItem * item, App * app)
1391 {
1392   app_new ();
1393 }
1394
1395 void
1396 launch_pitivi_project_activate_cb (GtkMenuItem * item, App * app)
1397 {
1398   GtkFileChooserDialog *dlg;
1399   GtkFileFilter *filter;
1400
1401   GST_DEBUG ("add file signal handler");
1402
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,
1410       GTK_STOCK_CANCEL,
1411       GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1412   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), filter);
1413
1414   g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL);
1415
1416   if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
1417     gchar *uri;
1418     uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg));
1419     gtk_widget_destroy ((GtkWidget *) dlg);
1420     app_launch_project (app, uri);
1421   }
1422 }
1423
1424 void
1425 open_activate_cb (GtkMenuItem * item, App * app)
1426 {
1427   GtkFileChooserDialog *dlg;
1428
1429   GST_DEBUG ("add file signal handler");
1430
1431   dlg = (GtkFileChooserDialog *) gtk_file_chooser_dialog_new ("Open Project...",
1432       GTK_WINDOW (app->main_window),
1433       GTK_FILE_CHOOSER_ACTION_OPEN,
1434       GTK_STOCK_CANCEL,
1435       GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1436
1437   g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL);
1438
1439   if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
1440     gchar *uri;
1441     uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg));
1442     app_new_from_uri (uri);
1443     g_free (uri);
1444   }
1445   gtk_widget_destroy ((GtkWidget *) dlg);
1446 }
1447
1448 void
1449 save_as_activate_cb (GtkMenuItem * item, App * app)
1450 {
1451   GtkFileChooserDialog *dlg;
1452
1453   GST_DEBUG ("save as signal handler");
1454
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,
1459       NULL);
1460
1461   g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL);
1462
1463   if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
1464     gchar *uri;
1465     uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg));
1466     app_save_to_uri (app, uri);
1467     g_free (uri);
1468   }
1469   gtk_widget_destroy ((GtkWidget *) dlg);
1470 }
1471
1472 void
1473 quit_item_activate_cb (GtkMenuItem * item, App * app)
1474 {
1475   gtk_main_quit ();
1476 }
1477
1478 void
1479 delete_activate_cb (GtkAction * item, App * app)
1480 {
1481   /* get a gslist of selected track elements */
1482   GList *objects = NULL;
1483
1484   objects = app_get_selected_objects (app);
1485   app_delete_objects (app, objects);
1486 }
1487
1488 void
1489 add_effect_activate_cb (GtkAction * item, App * app)
1490 {
1491   gtk_widget_show_all (app->add_effect_dlg);
1492 }
1493
1494 void
1495 add_file_activate_cb (GtkAction * item, App * app)
1496 {
1497   GtkFileChooserDialog *dlg;
1498
1499   GST_DEBUG ("add file signal handler");
1500
1501   dlg = (GtkFileChooserDialog *) gtk_file_chooser_dialog_new ("Add File...",
1502       GTK_WINDOW (app->main_window),
1503       GTK_FILE_CHOOSER_ACTION_OPEN,
1504       GTK_STOCK_CANCEL,
1505       GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1506
1507   g_object_set (G_OBJECT (dlg), "select-multiple", TRUE, NULL);
1508
1509   if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
1510     GSList *uris;
1511     GSList *cur;
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);
1516   }
1517   gtk_widget_destroy ((GtkWidget *) dlg);
1518 }
1519
1520 void
1521 add_text_activate_cb (GtkAction * item, App * app)
1522 {
1523   app_add_title (app);
1524 }
1525
1526 void
1527 add_test_activate_cb (GtkAction * item, App * app)
1528 {
1529   app_add_test (app);
1530 }
1531
1532 void
1533 add_transition_activate_cb (GtkAction * item, App * app)
1534 {
1535   app_add_transition (app);
1536 }
1537
1538 void
1539 play_activate_cb (GtkAction * item, App * app)
1540 {
1541   app_toggle_playpause (app);
1542 }
1543
1544 void
1545 stop_activate_cb (GtkAction * item, App * app)
1546 {
1547   app_stop_playback (app);
1548 }
1549
1550 void
1551 move_up_activate_cb (GtkAction * item, App * app)
1552 {
1553   app_move_selected_up (app);
1554 }
1555
1556 void
1557 move_down_activate_cb (GtkAction * item, App * app)
1558 {
1559   app_move_selected_down (app);
1560 }
1561
1562 void
1563 audio_track_activate_cb (GtkToggleAction * item, App * app)
1564 {
1565   if (gtk_toggle_action_get_active (item)) {
1566     app_add_audio_track (app);
1567   } else {
1568     app_remove_audio_track (app);
1569   }
1570 }
1571
1572 void
1573 video_track_activate_cb (GtkToggleAction * item, App * app)
1574 {
1575   if (gtk_toggle_action_get_active (item)) {
1576     app_add_video_track (app);
1577   } else {
1578     app_remove_video_track (app);
1579   }
1580 }
1581
1582 void
1583 app_selection_changed_cb (GtkTreeSelection * selection, App * app)
1584 {
1585   app_update_selection (app);
1586
1587   update_delete_sensitivity (app);
1588   update_effect_sensitivity (app);
1589   update_add_transition_sensitivity (app);
1590   update_move_up_down_sensitivity (app);
1591
1592   gtk_widget_set_visible (app->properties, app->n_selected > 0);
1593
1594   gtk_widget_set_visible (app->filesource_properties,
1595       app->selected_type == GES_TYPE_URI_CLIP);
1596
1597   gtk_widget_set_visible (app->text_properties,
1598       app->selected_type == GES_TYPE_TITLE_CLIP);
1599
1600   gtk_widget_set_visible (app->generic_duration,
1601       app->selected_type != G_TYPE_NONE &&
1602       app->selected_type != G_TYPE_INVALID);
1603
1604   gtk_widget_set_visible (app->background_properties,
1605       app->selected_type == GES_TYPE_TEST_CLIP);
1606 }
1607
1608 gboolean
1609 duration_scale_change_value_cb (GtkRange * range, GtkScrollType unused,
1610     gdouble value, App * app)
1611 {
1612   GList *i;
1613
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);
1619   }
1620   return TRUE;
1621 }
1622
1623 gboolean
1624 in_point_scale_change_value_cb (GtkRange * range, GtkScrollType unused,
1625     gdouble value, App * app)
1626 {
1627   GList *i;
1628
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);
1635   }
1636   return TRUE;
1637 }
1638
1639 void
1640 halign_changed_cb (GtkComboBox * widget, App * app)
1641 {
1642   GList *tmp;
1643   int active;
1644
1645   if (app->ignore_input)
1646     return;
1647
1648   active = gtk_combo_box_get_active (app->halign);
1649
1650   for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1651     g_object_set (G_OBJECT (tmp->data), "halignment", active, NULL);
1652   }
1653 }
1654
1655 void
1656 valign_changed_cb (GtkComboBox * widget, App * app)
1657 {
1658   GList *tmp;
1659   int active;
1660
1661   if (app->ignore_input)
1662     return;
1663
1664   active = gtk_combo_box_get_active (app->valign);
1665
1666   for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1667     g_object_set (G_OBJECT (tmp->data), "valignment", active, NULL);
1668   }
1669 }
1670
1671 void
1672 background_type_changed_cb (GtkComboBox * widget, App * app)
1673 {
1674   GList *tmp;
1675   gint p;
1676
1677   if (app->ignore_input)
1678     return;
1679
1680   p = gtk_combo_box_get_active (widget);
1681
1682   for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1683     g_object_set (G_OBJECT (tmp->data), "vpattern", (gint) p, NULL);
1684   }
1685 }
1686
1687 void
1688 frequency_value_changed_cb (GtkSpinButton * widget, App * app)
1689 {
1690   GList *tmp;
1691   gdouble value;
1692
1693   if (app->ignore_input)
1694     return;
1695
1696   value = gtk_spin_button_get_value (widget);
1697
1698   for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1699     g_object_set (G_OBJECT (tmp->data), "freq", (gdouble) value, NULL);
1700   }
1701 }
1702
1703 gboolean
1704 volume_change_value_cb (GtkRange * widget, GtkScrollType unused, gdouble
1705     value, App * app)
1706 {
1707   GList *tmp;
1708
1709   value = value >= 0 ? (value <= 2.0 ? value : 2.0) : 0;
1710
1711   for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1712     g_object_set (G_OBJECT (tmp->data), "volume", (gdouble) value, NULL);
1713   }
1714   return TRUE;
1715 }
1716
1717 /* main *********************************************************************/
1718
1719 int
1720 main (int argc, char *argv[])
1721 {
1722   App *app;
1723
1724   /* intialize GStreamer and GES */
1725   gst_init (&argc, &argv);
1726   ges_init ();
1727
1728   /* initialize UI */
1729   gtk_init (&argc, &argv);
1730
1731   if ((app = app_new ())) {
1732     gtk_main ();
1733   }
1734
1735   return 0;
1736 }