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   GESPipeline *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 (GtkWidget * 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_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
update_effect_sensitivity(App * app)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
update_delete_sensitivity(App * app)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
update_add_transition_sensitivity(App * app)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
update_move_up_down_sensitivity(App * app)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
update_play_sensitivity(App * app)204 update_play_sensitivity (App * app)
205 {
206   gtk_action_set_sensitive (app->play, app->n_objects);
207 }
208 
209 /* Backend callbacks ********************************************************/
210 
211 static void
test_source_notify_volume_changed_cb(GESClip * clip,GParamSpec * unused G_GNUC_UNUSED,App * app)212 test_source_notify_volume_changed_cb (GESClip * clip, GParamSpec *
213     unused G_GNUC_UNUSED, App * app)
214 {
215   gdouble volume;
216 
217   g_object_get (G_OBJECT (clip), "volume", &volume, NULL);
218 
219   gtk_range_set_value (GTK_RANGE (app->volume), volume);
220 }
221 
222 static void
layer_notify_valid_changed_cb(GObject * object,GParamSpec * unused G_GNUC_UNUSED,App * app)223 layer_notify_valid_changed_cb (GObject * object, GParamSpec * unused
224     G_GNUC_UNUSED, App * app)
225 {
226   update_play_sensitivity (app);
227 }
228 
229 static gboolean
find_row_for_object(GtkListStore * model,GtkTreeIter * ret,GESClip * clip)230 find_row_for_object (GtkListStore * model, GtkTreeIter * ret, GESClip * clip)
231 {
232   gtk_tree_model_get_iter_first ((GtkTreeModel *) model, ret);
233 
234   while (gtk_list_store_iter_is_valid (model, ret)) {
235     GESClip *clip2;
236     gtk_tree_model_get ((GtkTreeModel *) model, ret, 2, &clip2, -1);
237     if (clip2 == clip) {
238       gst_object_unref (clip2);
239       return TRUE;
240     }
241     gst_object_unref (clip2);
242     gtk_tree_model_iter_next ((GtkTreeModel *) model, ret);
243   }
244   return FALSE;
245 }
246 
247 /* this callback is registered for every clip, and updates the
248  * corresponding duration cell in the model */
249 static void
clip_notify_duration_cb(GESClip * clip,GParamSpec * arg G_GNUC_UNUSED,App * app)250 clip_notify_duration_cb (GESClip * clip,
251     GParamSpec * arg G_GNUC_UNUSED, App * app)
252 {
253   GtkTreeIter iter;
254   guint64 duration = 0;
255 
256   g_object_get (clip, "duration", &duration, NULL);
257   find_row_for_object (app->model, &iter, clip);
258   gtk_list_store_set (app->model, &iter, 1, duration, -1);
259 }
260 
261 /* these guys are only connected to filesources that are the target of the
262  * current selection */
263 
264 static void
filesource_notify_duration_cb(GESClip * clip,GParamSpec * arg G_GNUC_UNUSED,App * app)265 filesource_notify_duration_cb (GESClip * clip,
266     GParamSpec * arg G_GNUC_UNUSED, App * app)
267 {
268   guint64 duration, max_inpoint;
269   duration = GES_TIMELINE_ELEMENT_DURATION (clip);
270   max_inpoint = GES_TIMELINE_ELEMENT_MAX_DURATION (clip) - duration;
271 
272   gtk_range_set_value (GTK_RANGE (app->duration), duration);
273   gtk_range_set_fill_level (GTK_RANGE (app->in_point), max_inpoint);
274 
275   if (max_inpoint < GES_TIMELINE_ELEMENT_INPOINT (clip))
276     g_object_set (clip, "in-point", max_inpoint, NULL);
277 
278 }
279 
280 static void
filesource_notify_max_duration_cb(GESClip * clip,GParamSpec * arg G_GNUC_UNUSED,App * app)281 filesource_notify_max_duration_cb (GESClip * clip,
282     GParamSpec * arg G_GNUC_UNUSED, App * app)
283 {
284   gtk_range_set_range (GTK_RANGE (app->duration), 0, (gdouble)
285       GES_TIMELINE_ELEMENT_MAX_DURATION (clip));
286   gtk_range_set_range (GTK_RANGE (app->in_point), 0, (gdouble)
287       GES_TIMELINE_ELEMENT_MAX_DURATION (clip));
288 }
289 
290 static void
filesource_notify_in_point_cb(GESClip * clip,GParamSpec * arg G_GNUC_UNUSED,App * app)291 filesource_notify_in_point_cb (GESClip * clip,
292     GParamSpec * arg G_GNUC_UNUSED, App * app)
293 {
294   gtk_range_set_value (GTK_RANGE (app->in_point),
295       GES_TIMELINE_ELEMENT_INPOINT (clip));
296 }
297 
298 static void
app_update_first_last_selected(App * app)299 app_update_first_last_selected (App * app)
300 {
301   GtkTreePath *path;
302 
303   /* keep track of whether the first or last items are selected */
304   path = gtk_tree_path_new_from_indices (0, -1);
305   app->first_selected =
306       gtk_tree_selection_path_is_selected (app->selection, path);
307   gtk_tree_path_free (path);
308 
309   path = gtk_tree_path_new_from_indices (app->n_objects - 1, -1);
310   app->last_selected =
311       gtk_tree_selection_path_is_selected (app->selection, path);
312   gtk_tree_path_free (path);
313 }
314 
315 static void
object_count_changed(App * app)316 object_count_changed (App * app)
317 {
318   app_update_first_last_selected (app);
319   update_move_up_down_sensitivity (app);
320   update_play_sensitivity (app);
321 }
322 
323 static void
title_source_text_changed_cb(GESClip * clip,GParamSpec * arg G_GNUC_UNUSED,App * app)324 title_source_text_changed_cb (GESClip * clip,
325     GParamSpec * arg G_GNUC_UNUSED, App * app)
326 {
327   GtkTreeIter iter;
328   gchar *text;
329 
330   g_object_get (clip, "text", &text, NULL);
331   if (text) {
332     find_row_for_object (app->model, &iter, clip);
333     gtk_list_store_set (app->model, &iter, 0, text, -1);
334   }
335 }
336 
337 static void
layer_object_added_cb(GESLayer * layer,GESClip * clip,App * app)338 layer_object_added_cb (GESLayer * layer, GESClip * clip, App * app)
339 {
340   GtkTreeIter iter;
341   gchar *description;
342 
343   GST_INFO ("layer clip added cb %p %p %p", layer, clip, app);
344 
345   gtk_list_store_append (app->model, &iter);
346 
347   if (GES_IS_URI_CLIP (clip)) {
348     g_object_get (G_OBJECT (clip), "uri", &description, NULL);
349     gtk_list_store_set (app->model, &iter, 0, description, 2, clip, -1);
350   }
351 
352   else if (GES_IS_TITLE_CLIP (clip)) {
353     gtk_list_store_set (app->model, &iter, 2, clip, -1);
354     g_signal_connect (G_OBJECT (clip), "notify::text",
355         G_CALLBACK (title_source_text_changed_cb), app);
356     title_source_text_changed_cb (clip, NULL, app);
357   }
358 
359   else if (GES_IS_TEST_CLIP (clip)) {
360     gtk_list_store_set (app->model, &iter, 2, clip, 0, "Test Source", -1);
361   }
362 
363   else if (GES_IS_BASE_TRANSITION_CLIP (clip)) {
364     gtk_list_store_set (app->model, &iter, 2, clip, 0, "Transition", -1);
365   }
366 
367   g_signal_connect (G_OBJECT (clip), "notify::duration",
368       G_CALLBACK (clip_notify_duration_cb), app);
369   clip_notify_duration_cb (clip, NULL, app);
370 
371   app->n_objects++;
372   object_count_changed (app);
373 }
374 
375 static void
layer_object_removed_cb(GESLayer * layer,GESClip * clip,App * app)376 layer_object_removed_cb (GESLayer * layer, GESClip * clip, App * app)
377 {
378   GtkTreeIter iter;
379 
380   GST_INFO ("layer clip removed cb %p %p %p", layer, clip, app);
381 
382   if (!find_row_for_object (GTK_LIST_STORE (app->model), &iter, clip)) {
383     g_print ("clip deleted but we don't own it");
384     return;
385   }
386   app->n_objects--;
387   object_count_changed (app);
388 
389   gtk_list_store_remove (app->model, &iter);
390 }
391 
392 static void
layer_object_moved_cb(GESClip * layer,GESClip * clip,gint old,gint new,App * app)393 layer_object_moved_cb (GESClip * layer, GESClip * clip,
394     gint old, gint new, App * app)
395 {
396   GtkTreeIter a, b;
397   GtkTreePath *path;
398 
399   /* we can take the old position as given, but the new position might have to
400    * be adjusted. */
401   new = new < 0 ? (app->n_objects - 1) : new;
402 
403   path = gtk_tree_path_new_from_indices (old, -1);
404   gtk_tree_model_get_iter (GTK_TREE_MODEL (app->model), &a, path);
405   gtk_tree_path_free (path);
406 
407   path = gtk_tree_path_new_from_indices (new, -1);
408   gtk_tree_model_get_iter (GTK_TREE_MODEL (app->model), &b, path);
409   gtk_tree_path_free (path);
410 
411   gtk_list_store_swap (app->model, &a, &b);
412   app_selection_changed_cb (app->selection, app);
413   update_move_up_down_sensitivity (app);
414 }
415 
416 static void
pipeline_state_changed_cb(App * app)417 pipeline_state_changed_cb (App * app)
418 {
419   gboolean playing_or_paused;
420 
421   if (app->state == GST_STATE_PLAYING)
422     gtk_action_set_stock_id (app->play, GTK_STOCK_MEDIA_PAUSE);
423   else
424     gtk_action_set_stock_id (app->play, GTK_STOCK_MEDIA_PLAY);
425 
426   update_delete_sensitivity (app);
427   update_add_transition_sensitivity (app);
428   update_move_up_down_sensitivity (app);
429 
430   playing_or_paused = (app->state == GST_STATE_PLAYING) ||
431       (app->state == GST_STATE_PAUSED);
432 
433   gtk_action_set_sensitive (app->add_file, !playing_or_paused);
434   gtk_action_set_sensitive (app->add_title, !playing_or_paused);
435   gtk_action_set_sensitive (app->add_test, !playing_or_paused);
436   gtk_action_set_sensitive ((GtkAction *) app->audio_track_action,
437       !playing_or_paused);
438   gtk_action_set_sensitive ((GtkAction *) app->video_track_action,
439       !playing_or_paused);
440   gtk_widget_set_sensitive (app->properties, !playing_or_paused);
441 }
442 
443 static void
project_bus_message_cb(GstBus * bus,GstMessage * message,GMainLoop * mainloop)444 project_bus_message_cb (GstBus * bus, GstMessage * message,
445     GMainLoop * mainloop)
446 {
447   switch (GST_MESSAGE_TYPE (message)) {
448     case GST_MESSAGE_ERROR:
449       g_printerr ("ERROR\n");
450       g_main_loop_quit (mainloop);
451       break;
452     case GST_MESSAGE_EOS:
453       g_printerr ("Done\n");
454       g_main_loop_quit (mainloop);
455       break;
456     default:
457       break;
458   }
459 }
460 
461 static void
bus_message_cb(GstBus * bus,GstMessage * message,App * app)462 bus_message_cb (GstBus * bus, GstMessage * message, App * app)
463 {
464   const GstStructure *s;
465   s = gst_message_get_structure (message);
466 
467   switch (GST_MESSAGE_TYPE (message)) {
468     case GST_MESSAGE_ERROR:
469       g_print ("ERROR\n");
470       break;
471     case GST_MESSAGE_EOS:
472       gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_READY);
473       break;
474     case GST_MESSAGE_STATE_CHANGED:
475       if (s && GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (app->pipeline)) {
476         GstState old, new, pending;
477         gst_message_parse_state_changed (message, &old, &new, &pending);
478         app->state = new;
479         pipeline_state_changed_cb (app);
480       }
481       break;
482     default:
483       break;
484   }
485 }
486 
487 /* Static UI Callbacks ******************************************************/
488 
489 static gboolean
check_time(const gchar * time)490 check_time (const gchar * time)
491 {
492   static GRegex *re = NULL;
493 
494   if (!re) {
495     if (NULL == (re =
496             g_regex_new ("^[0-9][0-9]:[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?$",
497                 G_REGEX_EXTENDED, 0, NULL)))
498       return FALSE;
499   }
500 
501   if (g_regex_match (re, time, 0, NULL))
502     return TRUE;
503   return FALSE;
504 }
505 
506 static guint64
str_to_time(const gchar * str)507 str_to_time (const gchar * str)
508 {
509   guint64 ret;
510   guint64 h, m;
511   gdouble s;
512   gchar buf[15];
513 
514   buf[0] = str[0];
515   buf[1] = str[1];
516   buf[2] = '\0';
517 
518   h = strtoull (buf, NULL, 10);
519 
520   buf[0] = str[3];
521   buf[1] = str[4];
522   buf[2] = '\0';
523 
524   m = strtoull (buf, NULL, 10);
525 
526   strncpy (buf, &str[6], sizeof (buf) - 1);
527   buf[sizeof (buf) - 1] = '\0';
528   s = strtod (buf, NULL);
529 
530   ret = (h * 3600 * GST_SECOND) +
531       (m * 60 * GST_SECOND) + ((guint64) (s * GST_SECOND));
532 
533   return ret;
534 }
535 
536 static void
text_notify_text_changed_cb(GtkEntry * widget,GParamSpec * unused,App * app)537 text_notify_text_changed_cb (GtkEntry * widget, GParamSpec * unused, App * app)
538 {
539   GList *tmp;
540   const gchar *text;
541 
542   if (app->ignore_input)
543     return;
544 
545   text = gtk_entry_get_text (widget);
546 
547   for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
548     g_object_set (G_OBJECT (tmp->data), "text", text, NULL);
549   }
550 }
551 
552 static void
seconds_notify_text_changed_cb(GtkEntry * widget,GParamSpec * unused,App * app)553 seconds_notify_text_changed_cb (GtkEntry * widget, GParamSpec * unused,
554     App * app)
555 {
556   GList *tmp;
557   const gchar *text;
558 
559   if (app->ignore_input)
560     return;
561 
562   text = gtk_entry_get_text (app->seconds);
563 
564   if (!check_time (text)) {
565     gtk_entry_set_icon_from_stock (app->seconds,
566         GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_DIALOG_WARNING);
567   } else {
568     gtk_entry_set_icon_from_stock (app->seconds,
569         GTK_ENTRY_ICON_SECONDARY, NULL);
570     for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
571       g_object_set (GES_CLIP (tmp->data), "duration",
572           (guint64) str_to_time (text), NULL);
573     }
574   }
575 }
576 
577 static void
duration_cell_func(GtkTreeViewColumn * column,GtkCellRenderer * renderer,GtkTreeModel * model,GtkTreeIter * iter,gpointer user)578 duration_cell_func (GtkTreeViewColumn * column, GtkCellRenderer * renderer,
579     GtkTreeModel * model, GtkTreeIter * iter, gpointer user)
580 {
581   gchar buf[30];
582   guint64 duration;
583 
584   gtk_tree_model_get (model, iter, 1, &duration, -1);
585   g_snprintf (buf, sizeof (buf), "%u:%02u:%02u.%09u", GST_TIME_ARGS (duration));
586   g_object_set (renderer, "text", &buf, NULL);
587 }
588 
589 /* UI Initialization ********************************************************/
590 
591 static void
connect_to_filesource(GESClip * clip,App * app)592 connect_to_filesource (GESClip * clip, App * app)
593 {
594   g_signal_connect (G_OBJECT (clip), "notify::max-duration",
595       G_CALLBACK (filesource_notify_max_duration_cb), app);
596   filesource_notify_max_duration_cb (clip, NULL, app);
597 
598   g_signal_connect (G_OBJECT (clip), "notify::duration",
599       G_CALLBACK (filesource_notify_duration_cb), app);
600   filesource_notify_duration_cb (clip, NULL, app);
601 
602   g_signal_connect (G_OBJECT (clip), "notify::in-point",
603       G_CALLBACK (filesource_notify_in_point_cb), app);
604   filesource_notify_in_point_cb (clip, NULL, app);
605 }
606 
607 static void
disconnect_from_filesource(GESClip * clip,App * app)608 disconnect_from_filesource (GESClip * clip, App * app)
609 {
610   g_signal_handlers_disconnect_by_func (G_OBJECT (clip),
611       filesource_notify_duration_cb, app);
612 
613   g_signal_handlers_disconnect_by_func (G_OBJECT (clip),
614       filesource_notify_max_duration_cb, app);
615 }
616 
617 static void
connect_to_title_source(GESClip * clip,App * app)618 connect_to_title_source (GESClip * clip, App * app)
619 {
620   GESTitleClip *titleclip;
621   titleclip = GES_TITLE_CLIP (clip);
622   gtk_combo_box_set_active (app->halign,
623       ges_title_clip_get_halignment (titleclip));
624   gtk_combo_box_set_active (app->valign,
625       ges_title_clip_get_valignment (titleclip));
626   gtk_entry_set_text (app->text, ges_title_clip_get_text (titleclip));
627 }
628 
629 static void
disconnect_from_title_source(GESClip * clip,App * app)630 disconnect_from_title_source (GESClip * clip, App * app)
631 {
632 }
633 
634 static void
connect_to_test_source(GESClip * clip,App * app)635 connect_to_test_source (GESClip * clip, App * app)
636 {
637   GObjectClass *klass;
638   GParamSpecDouble *pspec;
639 
640   GESTestClip *testclip;
641   testclip = GES_TEST_CLIP (clip);
642   gtk_combo_box_set_active (app->background_type,
643       ges_test_clip_get_vpattern (testclip));
644 
645   g_signal_connect (G_OBJECT (testclip), "notify::volume",
646       G_CALLBACK (test_source_notify_volume_changed_cb), app);
647   test_source_notify_volume_changed_cb (clip, NULL, app);
648 
649   klass = G_OBJECT_GET_CLASS (G_OBJECT (testclip));
650 
651   pspec = G_PARAM_SPEC_DOUBLE (g_object_class_find_property (klass, "volume"));
652   gtk_range_set_range (GTK_RANGE (app->volume), pspec->minimum, pspec->maximum);
653 
654   pspec = G_PARAM_SPEC_DOUBLE (g_object_class_find_property (klass, "freq"));
655   gtk_spin_button_set_range (app->frequency, pspec->minimum, pspec->maximum);
656   gtk_spin_button_set_value (app->frequency,
657       ges_test_clip_get_frequency (GES_TEST_CLIP (clip)));
658 }
659 
660 static void
disconnect_from_test_source(GESClip * clip,App * app)661 disconnect_from_test_source (GESClip * clip, App * app)
662 {
663   g_signal_handlers_disconnect_by_func (G_OBJECT (clip),
664       test_source_notify_volume_changed_cb, app);
665 }
666 
667 static void
connect_to_object(GESClip * clip,App * app)668 connect_to_object (GESClip * clip, App * app)
669 {
670   gchar buf[30];
671   guint64 duration;
672 
673   app->ignore_input = TRUE;
674 
675   duration = GES_TIMELINE_ELEMENT_DURATION (clip);
676   g_snprintf (buf, sizeof (buf), "%02u:%02u:%02u.%09u",
677       GST_TIME_ARGS (duration));
678   gtk_entry_set_text (app->seconds, buf);
679 
680   if (GES_IS_URI_CLIP (clip)) {
681     connect_to_filesource (clip, app);
682   } else if (GES_IS_TITLE_CLIP (clip)) {
683     connect_to_title_source (clip, app);
684   } else if (GES_IS_TEST_CLIP (clip)) {
685     connect_to_test_source (clip, app);
686   }
687 
688   app->ignore_input = FALSE;
689 }
690 
691 static void
disconnect_from_object(GESClip * clip,App * app)692 disconnect_from_object (GESClip * clip, App * app)
693 {
694   if (GES_IS_URI_CLIP (clip)) {
695     disconnect_from_filesource (clip, app);
696   } else if (GES_IS_TITLE_CLIP (clip)) {
697     disconnect_from_title_source (clip, app);
698   } else if (GES_IS_TEST_CLIP (clip)) {
699     disconnect_from_test_source (clip, app);
700   }
701 }
702 
703 static GtkListStore *
get_video_patterns(void)704 get_video_patterns (void)
705 {
706   GEnumClass *enum_class;
707   GESTestClip *tr;
708   GESTestClipClass *klass;
709   GParamSpec *pspec;
710   GEnumValue *v;
711   GtkListStore *m;
712   GtkTreeIter i;
713 
714   m = gtk_list_store_new (1, G_TYPE_STRING);
715 
716   tr = ges_test_clip_new ();
717   klass = GES_TEST_CLIP_GET_CLASS (tr);
718 
719   pspec = g_object_class_find_property (G_OBJECT_CLASS (klass), "vpattern");
720 
721   enum_class = G_ENUM_CLASS (g_type_class_ref (pspec->value_type));
722 
723   for (v = enum_class->values; v->value_nick != NULL; v++) {
724     gtk_list_store_append (m, &i);
725     gtk_list_store_set (m, &i, 0, v->value_name, -1);
726   }
727 
728   g_type_class_unref (enum_class);
729   gst_object_unref (tr);
730 
731   return m;
732 }
733 
734 #define GET_WIDGET(dest,name,type) {\
735   if (!(dest =\
736     type(gtk_builder_get_object(builder, name))))\
737         goto fail;\
738 }
739 
740 
741 static void
layer_added_cb(GESTimeline * timeline,GESLayer * layer,App * app)742 layer_added_cb (GESTimeline * timeline, GESLayer * layer, App * app)
743 {
744   if (!GES_IS_LAYER (layer)) {
745     GST_ERROR ("This timeline contains a layer type other than "
746         "GESLayer. Timeline editing disabled");
747     return;
748   }
749 
750   if (!(app->layer)) {
751     app->layer = layer;
752   }
753 
754   if (layer != app->layer) {
755     GST_ERROR ("This demo doesn't support editing timelines with multiple"
756         " layers");
757     return;
758   }
759 
760   g_signal_connect (app->layer, "clip-added",
761       G_CALLBACK (layer_object_added_cb), app);
762   g_signal_connect (app->layer, "clip-removed",
763       G_CALLBACK (layer_object_removed_cb), app);
764   g_signal_connect (app->layer, "object-moved",
765       G_CALLBACK (layer_object_moved_cb), app);
766   g_signal_connect (app->layer, "notify::valid",
767       G_CALLBACK (layer_notify_valid_changed_cb), app);
768 }
769 
770 static void
update_track_actions(App * app)771 update_track_actions (App * app)
772 {
773   g_signal_handlers_disconnect_by_func (app->audio_track_action,
774       audio_track_activate_cb, app);
775   g_signal_handlers_disconnect_by_func (app->video_track_action,
776       video_track_activate_cb, app);
777   gtk_toggle_action_set_active (app->audio_track_action, app->audio_tracks);
778   gtk_toggle_action_set_active (app->video_track_action, app->video_tracks);
779   gtk_action_set_sensitive ((GtkAction *) app->audio_track_action,
780       app->audio_tracks <= 1);
781   gtk_action_set_sensitive ((GtkAction *) app->video_track_action,
782       app->video_tracks <= 1);
783   g_signal_connect (G_OBJECT (app->audio_track_action), "activate",
784       G_CALLBACK (audio_track_activate_cb), app);
785   g_signal_connect (G_OBJECT (app->video_track_action), "activate",
786       G_CALLBACK (video_track_activate_cb), app);
787 }
788 
789 static void
track_added_cb(GESTimeline * timeline,GESTrack * track,App * app)790 track_added_cb (GESTimeline * timeline, GESTrack * track, App * app)
791 {
792   if (track->type == GES_TRACK_TYPE_AUDIO) {
793     app->audio_tracks++;
794     if (!app->audio_track)
795       app->audio_track = track;
796   }
797   if (track->type == GES_TRACK_TYPE_VIDEO) {
798     app->video_tracks++;
799     if (!app->video_track)
800       app->video_track = track;
801   }
802 
803 
804   update_track_actions (app);
805 }
806 
807 static void
track_removed_cb(GESTimeline * timeline,GESTrack * track,App * app)808 track_removed_cb (GESTimeline * timeline, GESTrack * track, App * app)
809 {
810   if (track->type == GES_TRACK_TYPE_AUDIO)
811     app->audio_tracks--;
812   if (track->type == GES_TRACK_TYPE_VIDEO)
813     app->video_tracks--;
814 
815   update_track_actions (app);
816 }
817 
818 static gboolean
create_ui(App * app)819 create_ui (App * app)
820 {
821   GtkBuilder *builder;
822   GtkTreeView *timeline;
823   GtkTreeViewColumn *duration_col;
824   GtkCellRenderer *duration_renderer;
825   GtkCellRenderer *background_type_renderer;
826   GtkListStore *backgrounds;
827   GstBus *bus;
828 
829   /* construct widget tree */
830 
831   builder = gtk_builder_new ();
832   gtk_builder_add_from_file (builder, "ges-ui.glade", NULL);
833 
834   /* get a bunch of widgets from the XML tree */
835 
836   GET_WIDGET (timeline, "timeline_treeview", GTK_TREE_VIEW);
837   GET_WIDGET (app->properties, "properties", GTK_WIDGET);
838   GET_WIDGET (app->filesource_properties, "filesource_properties", GTK_WIDGET);
839   GET_WIDGET (app->text_properties, "text_properties", GTK_WIDGET);
840   GET_WIDGET (app->main_window, "window", GTK_WIDGET);
841   GET_WIDGET (app->add_effect_dlg, "add_effect_dlg", GTK_WIDGET);
842   GET_WIDGET (app->audio_effect_entry, "entry1", GTK_WIDGET);
843   GET_WIDGET (app->video_effect_entry, "entry2", GTK_WIDGET);
844   GET_WIDGET (app->duration, "duration_scale", GTK_HSCALE);
845   GET_WIDGET (app->in_point, "in_point_scale", GTK_HSCALE);
846   GET_WIDGET (app->halign, "halign", GTK_COMBO_BOX);
847   GET_WIDGET (app->valign, "valign", GTK_COMBO_BOX);
848   GET_WIDGET (app->text, "text", GTK_ENTRY);
849   GET_WIDGET (duration_col, "duration_column", GTK_TREE_VIEW_COLUMN);
850   GET_WIDGET (duration_renderer, "duration_renderer", GTK_CELL_RENDERER);
851   GET_WIDGET (app->add_file, "add_file", GTK_ACTION);
852   GET_WIDGET (app->add_effect, "add_effect", GTK_ACTION);
853   GET_WIDGET (app->add_title, "add_text", GTK_ACTION);
854   GET_WIDGET (app->add_test, "add_test", GTK_ACTION);
855   GET_WIDGET (app->add_transition, "add_transition", GTK_ACTION);
856   GET_WIDGET (app->delete, "delete", GTK_ACTION);
857   GET_WIDGET (app->play, "play", GTK_ACTION);
858   GET_WIDGET (app->stop, "stop", GTK_ACTION);
859   GET_WIDGET (app->move_up, "move_up", GTK_ACTION);
860   GET_WIDGET (app->move_down, "move_down", GTK_ACTION);
861   GET_WIDGET (app->seconds, "seconds", GTK_ENTRY);
862   GET_WIDGET (app->generic_duration, "generic_duration", GTK_WIDGET);
863   GET_WIDGET (app->background_type, "background_type", GTK_COMBO_BOX);
864   GET_WIDGET (app->background_properties, "background_properties", GTK_WIDGET);
865   GET_WIDGET (app->frequency, "frequency", GTK_SPIN_BUTTON);
866   GET_WIDGET (app->volume, "volume", GTK_HSCALE);
867   GET_WIDGET (app->audio_track_action, "audio_track", GTK_TOGGLE_ACTION);
868   GET_WIDGET (app->video_track_action, "video_track", GTK_TOGGLE_ACTION);
869 
870   /* get text notifications */
871 
872   g_signal_connect (app->text, "notify::text",
873       G_CALLBACK (text_notify_text_changed_cb), app);
874 
875   g_signal_connect (app->seconds, "notify::text",
876       G_CALLBACK (seconds_notify_text_changed_cb), app);
877 
878   /* we care when the tree selection changes */
879 
880   if (!(app->selection = gtk_tree_view_get_selection (timeline)))
881     goto fail;
882 
883   gtk_tree_selection_set_mode (app->selection, GTK_SELECTION_MULTIPLE);
884 
885   g_signal_connect (app->selection, "changed",
886       G_CALLBACK (app_selection_changed_cb), app);
887 
888   /* create the model for the treeview */
889 
890   if (!(app->model =
891           gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_OBJECT)))
892     goto fail;
893 
894   gtk_tree_view_set_model (timeline, GTK_TREE_MODEL (app->model));
895 
896   /* register custom cell data function */
897 
898   gtk_tree_view_column_set_cell_data_func (duration_col, duration_renderer,
899       duration_cell_func, NULL, NULL);
900 
901   /* initialize combo boxes */
902 
903   if (!(backgrounds = get_video_patterns ()))
904     goto fail;
905 
906   if (!(background_type_renderer = gtk_cell_renderer_text_new ()))
907     goto fail;
908 
909   gtk_combo_box_set_model (app->background_type, (GtkTreeModel *)
910       backgrounds);
911 
912   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (app->background_type),
913       background_type_renderer, FALSE);
914 
915   gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (app->background_type),
916       background_type_renderer, "text", 0);
917 
918   g_signal_connect (app->timeline, "layer-added", G_CALLBACK
919       (layer_added_cb), app);
920   g_signal_connect (app->timeline, "track-added", G_CALLBACK
921       (track_added_cb), app);
922   g_signal_connect (app->timeline, "track-removed", G_CALLBACK
923       (track_removed_cb), app);
924 
925   /* register callbacks on GES objects */
926   bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
927   gst_bus_add_signal_watch (bus);
928   g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), app);
929 
930   /* success */
931   gtk_builder_connect_signals (builder, app);
932   gst_object_unref (G_OBJECT (builder));
933   return TRUE;
934 
935 fail:
936   gst_object_unref (G_OBJECT (builder));
937   return FALSE;
938 }
939 
940 #undef GET_WIDGET
941 
942 /* application methods ******************************************************/
943 
944 static void selection_foreach (GtkTreeModel * model, GtkTreePath * path,
945     GtkTreeIter * iter, gpointer user);
946 
947 static void
app_toggle_playpause(App * app)948 app_toggle_playpause (App * app)
949 {
950   if (app->state != GST_STATE_PLAYING) {
951     gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PLAYING);
952   } else {
953     gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PAUSED);
954   }
955 }
956 
957 static void
app_stop_playback(App * app)958 app_stop_playback (App * app)
959 {
960   if ((app->state != GST_STATE_NULL) && (app->state != GST_STATE_READY)) {
961     gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_READY);
962   }
963 }
964 
965 typedef struct
966 {
967   GList *objects;
968   guint n;
969 } select_info;
970 
971 static void
app_update_selection(App * app)972 app_update_selection (App * app)
973 {
974   GList *cur;
975   GType type;
976   select_info info = { NULL, 0 };
977 
978   /* clear old selection */
979   for (cur = app->selected_objects; cur; cur = cur->next) {
980     disconnect_from_object (cur->data, app);
981     gst_object_unref (cur->data);
982     cur->data = NULL;
983   }
984   g_list_free (app->selected_objects);
985   app->selected_objects = NULL;
986   app->n_selected = 0;
987 
988   /* get new selection */
989   gtk_tree_selection_selected_foreach (GTK_TREE_SELECTION (app->selection),
990       selection_foreach, &info);
991   app->selected_objects = info.objects;
992   app->n_selected = info.n;
993 
994   type = G_TYPE_NONE;
995   if (app->selected_objects) {
996     type = G_TYPE_FROM_INSTANCE (app->selected_objects->data);
997     for (cur = app->selected_objects; cur; cur = cur->next) {
998       if (type != G_TYPE_FROM_INSTANCE (cur->data)) {
999         type = G_TYPE_NONE;
1000         break;
1001       }
1002     }
1003   }
1004 
1005   if (type != G_TYPE_NONE) {
1006     for (cur = app->selected_objects; cur; cur = cur->next) {
1007       connect_to_object (cur->data, app);
1008     }
1009   }
1010 
1011   app->selected_type = type;
1012   app_update_first_last_selected (app);
1013 }
1014 
1015 static void
selection_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer user)1016 selection_foreach (GtkTreeModel * model, GtkTreePath * path, GtkTreeIter
1017     * iter, gpointer user)
1018 {
1019   select_info *info = (select_info *) user;
1020   GESClip *clip;
1021 
1022   gtk_tree_model_get (model, iter, 2, &clip, -1);
1023   info->objects = g_list_append (info->objects, clip);
1024 
1025   info->n++;
1026   return;
1027 }
1028 
1029 static GList *
app_get_selected_objects(App * app)1030 app_get_selected_objects (App * app)
1031 {
1032   return g_list_copy (app->selected_objects);
1033 }
1034 
1035 static void
app_delete_objects(App * app,GList * objects)1036 app_delete_objects (App * app, GList * objects)
1037 {
1038   GList *cur;
1039 
1040   for (cur = objects; cur; cur = cur->next) {
1041     ges_layer_remove_clip (app->layer, GES_CLIP (cur->data));
1042     cur->data = NULL;
1043   }
1044 
1045   g_list_free (objects);
1046 }
1047 
1048 /* the following two methods assume exactly one clip is selected and that the
1049  * requested action is valid */
1050 
1051 static void
app_move_selected_up(App * app)1052 app_move_selected_up (App * app)
1053 {
1054   GST_FIXME ("This function is not implement, please implement it :)");
1055 }
1056 
1057 static void
app_add_effect_on_selected_clips(App * app,const gchar * bin_desc)1058 app_add_effect_on_selected_clips (App * app, const gchar * bin_desc)
1059 {
1060   GList *objects, *tmp;
1061   GESTrackElement *effect = NULL;
1062 
1063   /* No crash if the video is playing */
1064   gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PAUSED);
1065   objects = ges_layer_get_clips (app->layer);
1066 
1067   for (tmp = objects; tmp; tmp = tmp->next) {
1068     effect = GES_TRACK_ELEMENT (ges_effect_new (bin_desc));
1069     ges_container_add (GES_CONTAINER (tmp->data),
1070         GES_TIMELINE_ELEMENT (effect));
1071     gst_object_unref (tmp->data);
1072   }
1073 }
1074 
1075 gboolean
add_effect_dlg_delete_event_cb(GtkWidget * widget,GdkEvent * event,gpointer * app)1076 add_effect_dlg_delete_event_cb (GtkWidget * widget, GdkEvent * event,
1077     gpointer * app)
1078 {
1079   gtk_widget_hide (((App *) app)->add_effect_dlg);
1080   return TRUE;
1081 }
1082 
1083 void
on_cancel_add_effect_cb(GtkButton * button,App * app)1084 on_cancel_add_effect_cb (GtkButton * button, App * app)
1085 {
1086   gtk_widget_hide (app->add_effect_dlg);
1087 }
1088 
1089 void
on_apply_effect_cb(GtkButton * button,App * app)1090 on_apply_effect_cb (GtkButton * button, App * app)
1091 {
1092   const gchar *effect;
1093 
1094   effect = gtk_entry_get_text (GTK_ENTRY (app->video_effect_entry));
1095   if (g_strcmp0 (effect, ""))
1096     app_add_effect_on_selected_clips (app, effect);
1097 
1098   gtk_entry_set_text (GTK_ENTRY (app->video_effect_entry), "");
1099 
1100   effect = gtk_entry_get_text (GTK_ENTRY (app->audio_effect_entry));
1101   if (g_strcmp0 (effect, ""))
1102     app_add_effect_on_selected_clips (app, effect);
1103 
1104   gtk_entry_set_text (GTK_ENTRY (app->audio_effect_entry), "");
1105 
1106   gtk_widget_hide (app->add_effect_dlg);
1107 }
1108 
1109 static void
app_move_selected_down(App * app)1110 app_move_selected_down (App * app)
1111 {
1112   GST_FIXME ("This function is not implement, please implement it :)");
1113 }
1114 
1115 static void
app_add_file(App * app,gchar * uri)1116 app_add_file (App * app, gchar * uri)
1117 {
1118   GESClip *clip;
1119 
1120   GST_DEBUG ("adding file %s", uri);
1121 
1122   clip = GES_CLIP (ges_uri_clip_new (uri));
1123   ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip),
1124       ges_layer_get_duration (app->layer));
1125 
1126   ges_layer_add_clip (app->layer, clip);
1127 }
1128 
1129 static void
app_launch_project(App * app,gchar * uri)1130 app_launch_project (App * app, gchar * uri)
1131 {
1132   GESTimeline *timeline;
1133   GMainLoop *mainloop;
1134   GESPipeline *pipeline;
1135   GstBus *bus;
1136   GESProject *project;
1137 
1138   uri = g_strsplit (uri, "//", 2)[1];
1139   printf ("we will launch this uri : %s\n", uri);
1140   project = ges_project_new (uri);
1141   timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL));
1142   pipeline = ges_pipeline_new ();
1143   bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
1144   mainloop = g_main_loop_new (NULL, FALSE);
1145 
1146   ges_pipeline_set_timeline (pipeline, timeline);
1147   ges_pipeline_set_mode (pipeline, GES_PIPELINE_MODE_PREVIEW_VIDEO);
1148   gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
1149   gst_bus_add_signal_watch (bus);
1150   g_signal_connect (bus, "message", G_CALLBACK (project_bus_message_cb),
1151       mainloop);
1152   g_main_loop_run (mainloop);
1153   g_object_unref (project);
1154 }
1155 
1156 static void
app_add_title(App * app)1157 app_add_title (App * app)
1158 {
1159   GESClip *clip;
1160 
1161   GST_DEBUG ("adding title");
1162 
1163   clip = GES_CLIP (ges_title_clip_new ());
1164   g_object_set (G_OBJECT (clip), "duration", GST_SECOND,
1165       "start", ges_layer_get_duration (app->layer), NULL);
1166   ges_layer_add_clip (app->layer, clip);
1167 }
1168 
1169 static void
app_add_test(App * app)1170 app_add_test (App * app)
1171 {
1172   GESClip *clip;
1173 
1174   GST_DEBUG ("adding test");
1175 
1176   clip = GES_CLIP (ges_test_clip_new ());
1177   g_object_set (G_OBJECT (clip), "duration", GST_SECOND,
1178       "start", ges_layer_get_duration (app->layer), NULL);
1179 
1180   ges_layer_add_clip (app->layer, clip);
1181 }
1182 
1183 static void
app_add_transition(App * app)1184 app_add_transition (App * app)
1185 {
1186   GST_FIXME ("This function is not implement, please implement it :)");
1187 }
1188 
1189 static void
app_save_to_uri(App * app,gchar * uri)1190 app_save_to_uri (App * app, gchar * uri)
1191 {
1192   ges_timeline_save_to_uri (app->timeline, uri, NULL, FALSE, NULL);
1193 }
1194 
1195 static void
app_add_audio_track(App * app)1196 app_add_audio_track (App * app)
1197 {
1198   if (app->audio_tracks)
1199     return;
1200 
1201   app->audio_track = GES_TRACK (ges_audio_track_new ());
1202   ges_timeline_add_track (app->timeline, app->audio_track);
1203 }
1204 
1205 static void
app_remove_audio_track(App * app)1206 app_remove_audio_track (App * app)
1207 {
1208   if (!app->audio_tracks)
1209     return;
1210 
1211   ges_timeline_remove_track (app->timeline, app->audio_track);
1212   app->audio_track = NULL;
1213 }
1214 
1215 static void
app_add_video_track(App * app)1216 app_add_video_track (App * app)
1217 {
1218   if (app->video_tracks)
1219     return;
1220 
1221   app->video_track = GES_TRACK (ges_video_track_new ());
1222   ges_timeline_add_track (app->timeline, app->video_track);
1223 }
1224 
1225 static void
app_remove_video_track(App * app)1226 app_remove_video_track (App * app)
1227 {
1228   if (!app->video_tracks)
1229     return;
1230 
1231   ges_timeline_remove_track (app->timeline, app->video_track);
1232   app->video_track = NULL;
1233 }
1234 
1235 static void
app_dispose(App * app)1236 app_dispose (App * app)
1237 {
1238   if (app) {
1239     if (app->pipeline) {
1240       gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_NULL);
1241       gst_object_unref (app->pipeline);
1242     }
1243 
1244     g_free (app);
1245   }
1246 
1247   n_instances--;
1248 
1249   if (n_instances == 0) {
1250     gtk_main_quit ();
1251   }
1252 }
1253 
1254 static App *
app_init(void)1255 app_init (void)
1256 {
1257   App *ret;
1258   ret = g_new0 (App, 1);
1259   n_instances++;
1260 
1261   ret->selected_type = G_TYPE_NONE;
1262 
1263   if (!(ret->timeline = ges_timeline_new ()))
1264     goto fail;
1265 
1266   if (!(ret->pipeline = ges_pipeline_new ()))
1267     goto fail;
1268 
1269   if (!ges_pipeline_set_timeline (ret->pipeline, ret->timeline))
1270     goto fail;
1271 
1272   if (!(create_ui (ret)))
1273     goto fail;
1274 
1275   return ret;
1276 
1277 fail:
1278   app_dispose (ret);
1279   return NULL;
1280 }
1281 
1282 static App *
app_new(void)1283 app_new (void)
1284 {
1285   App *ret;
1286   GESTrack *a = NULL, *v = NULL;
1287 
1288   ret = app_init ();
1289 
1290   /* add base audio and video track */
1291 
1292   if (!(a = GES_TRACK (ges_audio_track_new ())))
1293     goto fail;
1294 
1295   if (!(ges_timeline_add_track (ret->timeline, a)))
1296     goto fail;
1297 
1298   if (!(v = GES_TRACK (ges_video_track_new ())))
1299     goto fail;
1300 
1301   if (!(ges_timeline_add_track (ret->timeline, v)))
1302     goto fail;
1303 
1304   if (!(ret->layer = ges_layer_new ()))
1305     goto fail;
1306 
1307   if (!(ges_timeline_add_layer (ret->timeline, ret->layer)))
1308     goto fail;
1309 
1310   ret->audio_track = a;
1311   ret->video_track = v;
1312   return ret;
1313 
1314 fail:
1315 
1316   if (a)
1317     gst_object_unref (a);
1318   if (v)
1319     gst_object_unref (v);
1320   app_dispose (ret);
1321   return NULL;
1322 }
1323 
1324 static gboolean
load_file_async(App * app)1325 load_file_async (App * app)
1326 {
1327   ges_timeline_load_from_uri (app->timeline, app->pending_uri, NULL);
1328 
1329   g_free (app->pending_uri);
1330   app->pending_uri = NULL;
1331 
1332   return FALSE;
1333 }
1334 
1335 static gboolean
app_new_from_uri(gchar * uri)1336 app_new_from_uri (gchar * uri)
1337 {
1338   App *ret;
1339 
1340   ret = app_init ();
1341   ret->pending_uri = g_strdup (uri);
1342   g_idle_add ((GSourceFunc) load_file_async, ret);
1343 
1344   return FALSE;
1345 }
1346 
1347 /* UI callbacks  ************************************************************/
1348 
1349 gboolean
window_delete_event_cb(GtkWidget * window,GdkEvent * event,App * app)1350 window_delete_event_cb (GtkWidget * window, GdkEvent * event, App * app)
1351 {
1352   app_dispose (app);
1353   return FALSE;
1354 }
1355 
1356 void
new_activate_cb(GtkMenuItem * item,App * app)1357 new_activate_cb (GtkMenuItem * item, App * app)
1358 {
1359   app_new ();
1360 }
1361 
1362 void
launch_project_activate_cb(GtkMenuItem * item,App * app)1363 launch_project_activate_cb (GtkMenuItem * item, App * app)
1364 {
1365   GtkFileChooserDialog *dlg;
1366   GtkFileFilter *filter;
1367 
1368   GST_DEBUG ("add file signal handler");
1369 
1370   filter = gtk_file_filter_new ();
1371   gtk_file_filter_set_name (filter, "pitivi projects");
1372   gtk_file_filter_add_pattern (filter, "*.xptv");
1373   dlg = (GtkFileChooserDialog *)
1374       gtk_file_chooser_dialog_new ("Preview Project...",
1375       GTK_WINDOW (app->main_window),
1376       GTK_FILE_CHOOSER_ACTION_OPEN,
1377       GTK_STOCK_CANCEL,
1378       GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1379   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), filter);
1380 
1381   g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL);
1382 
1383   if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
1384     gchar *uri;
1385     uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg));
1386     gtk_widget_destroy ((GtkWidget *) dlg);
1387     app_launch_project (app, uri);
1388   }
1389 }
1390 
1391 void
open_activate_cb(GtkMenuItem * item,App * app)1392 open_activate_cb (GtkMenuItem * item, App * app)
1393 {
1394   GtkFileChooserDialog *dlg;
1395 
1396   GST_DEBUG ("add file signal handler");
1397 
1398   dlg = (GtkFileChooserDialog *) gtk_file_chooser_dialog_new ("Open Project...",
1399       GTK_WINDOW (app->main_window),
1400       GTK_FILE_CHOOSER_ACTION_OPEN,
1401       GTK_STOCK_CANCEL,
1402       GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1403 
1404   g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL);
1405 
1406   if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
1407     gchar *uri;
1408     uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg));
1409     app_new_from_uri (uri);
1410     g_free (uri);
1411   }
1412   gtk_widget_destroy ((GtkWidget *) dlg);
1413 }
1414 
1415 void
save_as_activate_cb(GtkMenuItem * item,App * app)1416 save_as_activate_cb (GtkMenuItem * item, App * app)
1417 {
1418   GtkFileChooserDialog *dlg;
1419 
1420   GST_DEBUG ("save as signal handler");
1421 
1422   dlg = (GtkFileChooserDialog *)
1423       gtk_file_chooser_dialog_new ("Save project as...",
1424       GTK_WINDOW (app->main_window), GTK_FILE_CHOOSER_ACTION_SAVE,
1425       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_OK,
1426       NULL);
1427 
1428   g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL);
1429 
1430   if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
1431     gchar *uri;
1432     uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg));
1433     app_save_to_uri (app, uri);
1434     g_free (uri);
1435   }
1436   gtk_widget_destroy ((GtkWidget *) dlg);
1437 }
1438 
1439 void
quit_item_activate_cb(GtkMenuItem * item,App * app)1440 quit_item_activate_cb (GtkMenuItem * item, App * app)
1441 {
1442   gtk_main_quit ();
1443 }
1444 
1445 void
delete_activate_cb(GtkAction * item,App * app)1446 delete_activate_cb (GtkAction * item, App * app)
1447 {
1448   /* get a gslist of selected track elements */
1449   GList *objects = NULL;
1450 
1451   objects = app_get_selected_objects (app);
1452   app_delete_objects (app, objects);
1453 }
1454 
1455 void
add_effect_activate_cb(GtkAction * item,App * app)1456 add_effect_activate_cb (GtkAction * item, App * app)
1457 {
1458   gtk_widget_show_all (app->add_effect_dlg);
1459 }
1460 
1461 void
add_file_activate_cb(GtkAction * item,App * app)1462 add_file_activate_cb (GtkAction * item, App * app)
1463 {
1464   GtkFileChooserDialog *dlg;
1465 
1466   GST_DEBUG ("add file signal handler");
1467 
1468   dlg = (GtkFileChooserDialog *) gtk_file_chooser_dialog_new ("Add File...",
1469       GTK_WINDOW (app->main_window),
1470       GTK_FILE_CHOOSER_ACTION_OPEN,
1471       GTK_STOCK_CANCEL,
1472       GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1473 
1474   g_object_set (G_OBJECT (dlg), "select-multiple", TRUE, NULL);
1475 
1476   if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
1477     GSList *uris;
1478     GSList *cur;
1479     uris = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (dlg));
1480     for (cur = uris; cur; cur = cur->next)
1481       app_add_file (app, cur->data);
1482     g_slist_free (uris);
1483   }
1484   gtk_widget_destroy ((GtkWidget *) dlg);
1485 }
1486 
1487 void
add_text_activate_cb(GtkAction * item,App * app)1488 add_text_activate_cb (GtkAction * item, App * app)
1489 {
1490   app_add_title (app);
1491 }
1492 
1493 void
add_test_activate_cb(GtkAction * item,App * app)1494 add_test_activate_cb (GtkAction * item, App * app)
1495 {
1496   app_add_test (app);
1497 }
1498 
1499 void
add_transition_activate_cb(GtkAction * item,App * app)1500 add_transition_activate_cb (GtkAction * item, App * app)
1501 {
1502   app_add_transition (app);
1503 }
1504 
1505 void
play_activate_cb(GtkAction * item,App * app)1506 play_activate_cb (GtkAction * item, App * app)
1507 {
1508   app_toggle_playpause (app);
1509 }
1510 
1511 void
stop_activate_cb(GtkAction * item,App * app)1512 stop_activate_cb (GtkAction * item, App * app)
1513 {
1514   app_stop_playback (app);
1515 }
1516 
1517 void
move_up_activate_cb(GtkAction * item,App * app)1518 move_up_activate_cb (GtkAction * item, App * app)
1519 {
1520   app_move_selected_up (app);
1521 }
1522 
1523 void
move_down_activate_cb(GtkAction * item,App * app)1524 move_down_activate_cb (GtkAction * item, App * app)
1525 {
1526   app_move_selected_down (app);
1527 }
1528 
1529 void
audio_track_activate_cb(GtkToggleAction * item,App * app)1530 audio_track_activate_cb (GtkToggleAction * item, App * app)
1531 {
1532   if (gtk_toggle_action_get_active (item)) {
1533     app_add_audio_track (app);
1534   } else {
1535     app_remove_audio_track (app);
1536   }
1537 }
1538 
1539 void
video_track_activate_cb(GtkToggleAction * item,App * app)1540 video_track_activate_cb (GtkToggleAction * item, App * app)
1541 {
1542   if (gtk_toggle_action_get_active (item)) {
1543     app_add_video_track (app);
1544   } else {
1545     app_remove_video_track (app);
1546   }
1547 }
1548 
1549 void
app_selection_changed_cb(GtkTreeSelection * selection,App * app)1550 app_selection_changed_cb (GtkTreeSelection * selection, App * app)
1551 {
1552   app_update_selection (app);
1553 
1554   update_delete_sensitivity (app);
1555   update_effect_sensitivity (app);
1556   update_add_transition_sensitivity (app);
1557   update_move_up_down_sensitivity (app);
1558 
1559   gtk_widget_set_visible (app->properties, app->n_selected > 0);
1560 
1561   gtk_widget_set_visible (app->filesource_properties,
1562       app->selected_type == GES_TYPE_URI_CLIP);
1563 
1564   gtk_widget_set_visible (app->text_properties,
1565       app->selected_type == GES_TYPE_TITLE_CLIP);
1566 
1567   gtk_widget_set_visible (app->generic_duration,
1568       app->selected_type != G_TYPE_NONE &&
1569       app->selected_type != G_TYPE_INVALID);
1570 
1571   gtk_widget_set_visible (app->background_properties,
1572       app->selected_type == GES_TYPE_TEST_CLIP);
1573 }
1574 
1575 gboolean
duration_scale_change_value_cb(GtkRange * range,GtkScrollType unused,gdouble value,App * app)1576 duration_scale_change_value_cb (GtkRange * range, GtkScrollType unused,
1577     gdouble value, App * app)
1578 {
1579   GList *i;
1580 
1581   for (i = app->selected_objects; i; i = i->next) {
1582     guint64 duration, maxduration;
1583     maxduration = GES_TIMELINE_ELEMENT_MAX_DURATION (i->data);
1584     duration = (value < maxduration ? (value > 0 ? value : 0) : maxduration);
1585     g_object_set (G_OBJECT (i->data), "duration", (guint64) duration, NULL);
1586   }
1587   return TRUE;
1588 }
1589 
1590 gboolean
in_point_scale_change_value_cb(GtkRange * range,GtkScrollType unused,gdouble value,App * app)1591 in_point_scale_change_value_cb (GtkRange * range, GtkScrollType unused,
1592     gdouble value, App * app)
1593 {
1594   GList *i;
1595 
1596   for (i = app->selected_objects; i; i = i->next) {
1597     guint64 in_point, maxduration;
1598     maxduration = GES_TIMELINE_ELEMENT_MAX_DURATION (i->data) -
1599         GES_TIMELINE_ELEMENT_DURATION (i->data);
1600     in_point = (value < maxduration ? (value > 0 ? value : 0) : maxduration);
1601     g_object_set (G_OBJECT (i->data), "in-point", (guint64) in_point, NULL);
1602   }
1603   return TRUE;
1604 }
1605 
1606 void
halign_changed_cb(GtkComboBox * widget,App * app)1607 halign_changed_cb (GtkComboBox * widget, App * app)
1608 {
1609   GList *tmp;
1610   int active;
1611 
1612   if (app->ignore_input)
1613     return;
1614 
1615   active = gtk_combo_box_get_active (app->halign);
1616 
1617   for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1618     g_object_set (G_OBJECT (tmp->data), "halignment", active, NULL);
1619   }
1620 }
1621 
1622 void
valign_changed_cb(GtkComboBox * widget,App * app)1623 valign_changed_cb (GtkComboBox * widget, App * app)
1624 {
1625   GList *tmp;
1626   int active;
1627 
1628   if (app->ignore_input)
1629     return;
1630 
1631   active = gtk_combo_box_get_active (app->valign);
1632 
1633   for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1634     g_object_set (G_OBJECT (tmp->data), "valignment", active, NULL);
1635   }
1636 }
1637 
1638 void
background_type_changed_cb(GtkComboBox * widget,App * app)1639 background_type_changed_cb (GtkComboBox * widget, App * app)
1640 {
1641   GList *tmp;
1642   gint p;
1643 
1644   if (app->ignore_input)
1645     return;
1646 
1647   p = gtk_combo_box_get_active (widget);
1648 
1649   for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1650     g_object_set (G_OBJECT (tmp->data), "vpattern", (gint) p, NULL);
1651   }
1652 }
1653 
1654 void
frequency_value_changed_cb(GtkSpinButton * widget,App * app)1655 frequency_value_changed_cb (GtkSpinButton * widget, App * app)
1656 {
1657   GList *tmp;
1658   gdouble value;
1659 
1660   if (app->ignore_input)
1661     return;
1662 
1663   value = gtk_spin_button_get_value (widget);
1664 
1665   for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1666     g_object_set (G_OBJECT (tmp->data), "freq", (gdouble) value, NULL);
1667   }
1668 }
1669 
1670 gboolean
volume_change_value_cb(GtkRange * widget,GtkScrollType unused,gdouble value,App * app)1671 volume_change_value_cb (GtkRange * widget, GtkScrollType unused, gdouble
1672     value, App * app)
1673 {
1674   GList *tmp;
1675 
1676   value = value >= 0 ? (value <= 2.0 ? value : 2.0) : 0;
1677 
1678   for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
1679     g_object_set (G_OBJECT (tmp->data), "volume", (gdouble) value, NULL);
1680   }
1681   return TRUE;
1682 }
1683 
1684 /* main *********************************************************************/
1685 
1686 int
main(int argc,char * argv[])1687 main (int argc, char *argv[])
1688 {
1689   App *app;
1690 
1691   /* intialize GStreamer and GES */
1692   gst_init (&argc, &argv);
1693   ges_init ();
1694 
1695   /* initialize UI */
1696   gtk_init (&argc, &argv);
1697 
1698   if ((app = app_new ())) {
1699     gtk_main ();
1700   }
1701 
1702   return 0;
1703 }
1704