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