1 /* Copyright 2009-2010 Yorba Foundation
3 * This software is licensed under the GNU Lesser General Public License
4 * (version 2.1 or later). See the COPYING file in this distribution.
9 extern const string _PROGRAM_NAME;
10 bool do_print_graph = false;
13 const OptionEntry[] options = {
14 { "print-graph", 0, 0, OptionArg.NONE, &do_print_graph,
15 "Show Print Graph in help menu", null },
16 { "debug-level", 0, 0, OptionArg.INT, &debug_level,
17 "Control amount of diagnostic information",
18 "[0 (minimal),5 (maximum)]" },
22 class Recorder : Gtk.Window, TransportDelegate {
23 public Model.AudioProject project;
24 public TimeLine timeline;
25 View.ClickTrack click_track;
26 HeaderArea header_area;
27 ClipLibraryView library;
28 Model.TimeSystem provider;
29 Gtk.Adjustment h_adjustment;
30 Gtk.HPaned timeline_library_pane;
31 Gtk.ScrolledWindow library_scrolled;
32 Gtk.ScrolledWindow timeline_scrolled;
34 int64 center_time = -1;
36 const int scroll_speed = 8;
38 Gtk.ActionGroup main_group;
40 Gtk.ToggleButton play_button;
41 Gtk.ToggleButton record_button;
42 Gtk.Button addnew_button;
43 Gtk.Button rewind_button;
44 Gtk.Button forward_button;
45 Gtk.Button zoomin_button;
46 Gtk.Button zoomout_button;
47 Gtk.Button volume_button;
49 Hildon.PannableArea scrollwin;
50 int scrollwin_pos_y = 0;
52 Gtk.UIManager manager;
53 // TODO: Have a MediaExportConnector that extends MediaConnector rather than concrete type.
54 View.OggVorbisExport audio_export;
55 View.AudioOutput audio_output;
56 Gee.ArrayList<string> load_errors;
58 public const string NAME = "Fillmore";
59 const Gtk.ActionEntry[] entries = {
60 { "Project", null, "_Project", null, null, null },
61 { "Open", Gtk.STOCK_OPEN, "_Open...", null, "Open a project", on_project_open },
62 { "NewProject", Gtk.STOCK_NEW, "_New", null, "Create new project", on_project_new },
63 { "Save", Gtk.STOCK_SAVE, "_Save", "<Control>S", "Save project", on_project_save },
64 { "SaveAs", Gtk.STOCK_SAVE_AS, "Save _As...", "<Control><Shift>S",
65 "Save project with new name", on_project_save_as },
66 { "Export", Gtk.STOCK_JUMP_TO, "_Export...", "<Control>E", null, on_export },
67 { "Settings", Gtk.STOCK_PROPERTIES, "Se_ttings", "<Control><Alt>Return", null, on_properties },
68 { "Quit", Gtk.STOCK_QUIT, null, null, null, on_quit },
70 { "Edit", null, "_Edit", null, null, null },
71 { "Undo", Gtk.STOCK_UNDO, null, "<Control>Z", null, on_undo },
72 { "Cut", Gtk.STOCK_CUT, null, null, null, on_cut },
73 { "Copy", Gtk.STOCK_COPY, null, null, null, on_copy },
74 { "Paste", Gtk.STOCK_PASTE, null, null, null, on_paste },
75 { "Delete", Gtk.STOCK_DELETE, null, "Delete", null, on_delete },
76 { "SelectAll", Gtk.STOCK_SELECT_ALL, null, "<Control>A", null, on_select_all },
77 { "SplitAtPlayhead", null, "_Split at Playhead", "<Control>P", null, on_split_at_playhead },
78 { "TrimToPlayhead", null, "Trim to Play_head", "<Control>H", null, on_trim_to_playhead },
79 { "ClipProperties", Gtk.STOCK_PROPERTIES, "Properti_es", "<Alt>Return",
80 null, on_clip_properties },
82 { "View", null, "_View", null, null, null },
83 { "ZoomIn", Gtk.STOCK_ZOOM_IN, "Zoom _In", "<Control>plus", null, on_zoom_in },
84 { "ZoomOut", Gtk.STOCK_ZOOM_OUT, "Zoom _Out", "<Control>minus", null, on_zoom_out },
85 { "ZoomProject", null, "Fit to _Window", "<Shift>Z", null, on_zoom_to_project },
87 { "Track", null, "_Track", null, null, null },
88 { "NewTrack", Gtk.STOCK_ADD, "_New...", "<Control><Shift>N",
89 "Create new track", on_track_new },
90 { "Rename", null, "_Rename...", null, "Rename Track", on_track_rename },
91 { "DeleteTrack", null, "_Delete", "<Control><Shift>Delete",
92 "Delete track", on_track_remove },
93 { "MoveTrackUp", null, "Move Track Up", "<Control>Up", null, on_track_move_up },
94 { "MoveTrackDown", null, "Move Track Down", "<Control>Down", null, on_track_move_down },
95 { "ScrollTracksUp", null, "Scroll Tracks Up", "<Shift>Up", null, on_tracks_scroll_up },
96 { "ScrollTracksDown", null, "Scroll Tracks Down", "<Shift>Down", null, on_tracks_scroll_down },
99 { "Help", null, "_Help", null, null, null },
100 { "Contents", Gtk.STOCK_HELP, "_Contents", "F1",
101 "More information on Fillmore", on_help_contents},
102 { "About", Gtk.STOCK_ABOUT, null, null, null, on_about },
103 { "SaveGraph", null, "Save _Graph", null, "Save graph", on_save_graph },
105 { "Rewind", Gtk.STOCK_MEDIA_PREVIOUS, "Rewind", "Home", "Go to beginning", on_rewind },
106 { "End", Gtk.STOCK_MEDIA_NEXT, "End", "End", "Go to end", on_end }
109 const Gtk.ToggleActionEntry[] toggle_entries = {
110 { "Play", Gtk.STOCK_MEDIA_PLAY, null, "space", "Play", on_play },
111 { "Record", Gtk.STOCK_MEDIA_RECORD, null, "r", "Record", on_record },
112 { "Library", null, "_Library", "F9", null, on_view_library, false },
113 { "Snap", null, "_Snap to Clip Edges", null, null, on_snap, false }
116 const string ui = """
118 <menubar name="MenuBar">
119 <menu name="ProjectMenu" action="Project">
120 <menuitem name="New" action="NewProject"/>
121 <menuitem name="Open" action="Open"/>
122 <menuitem name="Save" action="Save"/>
123 <menuitem name="SaveAs" action="SaveAs"/>
125 <menuitem name="Export" action="Export"/>
127 <menuitem name="Property" action="Settings"/>
129 <menuitem name="Quit" action="Quit"/>
131 <menu name="EditMenu" action="Edit">
132 <menuitem name="EditUndo" action="Undo"/>
134 <menuitem name="EditCut" action="Cut"/>
135 <menuitem name="EditCopy" action="Copy"/>
136 <menuitem name="EditPaste" action="Paste"/>
137 <menuitem name="EditDelete" action="Delete"/>
139 <menuitem name="EditSelectAll" action="SelectAll"/>
141 <menuitem name="ClipSplitAtPlayhead" action="SplitAtPlayhead"/>
142 <menuitem name="ClipTrimToPlayhead" action="TrimToPlayhead"/>
144 <menuitem name="ClipViewProperties" action="ClipProperties"/>
146 <menu name="ViewMenu" action="View">
147 <menuitem name="ViewLibrary" action="Library"/>
149 <menuitem name="ViewZoomIn" action="ZoomIn"/>
150 <menuitem name="ViewZoomOut" action="ZoomOut"/>
151 <menuitem name="ViewZoomProject" action="ZoomProject"/>
153 <menuitem name="Snap" action="Snap"/>
155 <menu name="TrackMenu" action="Track">
156 <menuitem name="TrackNew" action="NewTrack"/>
157 <menuitem name="TrackRename" action="Rename"/>
158 <menuitem name="TrackDelete" action="DeleteTrack"/>
160 <menuitem name="TrackMoveUp" action="MoveTrackUp"/>
161 <menuitem name="TrackMoveDown" action="MoveTrackDown"/>
162 <menuitem name="TrackScrollUp" action="ScrollTracksUp"/>
163 <menuitem name="TrackScrollDown" action="ScrollTracksDown"/>
165 <menu name="HelpMenu" action="Help">
166 <menuitem name="HelpContents" action="Contents"/>
168 <menuitem name="HelpAbout" action="About"/>
169 <menuitem name="SaveGraph" action="SaveGraph"/>
172 <popup name="ClipContextMenu">
173 <menuitem name="Cut" action="Cut"/>
174 <menuitem name="Copy" action="Copy"/>
176 <menuitem name="ClipContextProperties" action="ClipProperties"/>
178 <popup name="LibraryContextMenu">
179 <menuitem name="ClipContextProperties" action="ClipProperties"/>
181 <toolbar name="Toolbar">
182 <toolitem name="New" action="NewTrack"/>
184 <toolitem name="Rewind" action="Rewind"/>
185 <toolitem name="End" action="End"/>
186 <toolitem name="Play" action="Play"/>
187 <toolitem name="Record" action="Record"/>
189 <accelerator name="Rewind" action="Rewind"/>
190 <accelerator name="End" action="End"/>
191 <accelerator name="Play" action="Play"/>
192 <accelerator name="Record" action="Record"/>
196 const DialogUtils.filter_description_struct[] filters = {
197 { "Fillmore Project Files", Model.Project.FILLMORE_FILE_EXTENSION },
198 { "Lombard Project Files", Model.Project.LOMBARD_FILE_EXTENSION }
201 const DialogUtils.filter_description_struct[] export_filters = {
202 { "Ogg Files", "ogg" }
205 public signal void finished_closing(bool project_did_close);
207 public Recorder(string? project_file) throws Error {
208 ClassFactory.set_transport_delegate(this);
209 GLib.DirUtils.create(get_fillmore_directory(), 0777);
210 load_errors = new Gee.ArrayList<string>();
213 AppDirs.get_resources_dir().get_child("fillmore_icon.png").get_path());
214 } catch (GLib.Error e) {
215 warning("Could not load application icon: %s", e.message);
219 project = new Model.AudioProject(project_file);
220 project.snap_to_clip = false;
221 provider = new Model.BarBeatTimeSystem(project);
223 project.media_engine.callback_pulse.connect(on_callback_pulse);
224 project.media_engine.post_export.connect(on_post_export);
225 project.media_engine.position_changed.connect(on_position_changed);
227 project.load_error.connect(on_load_error);
228 project.name_changed.connect(on_name_changed);
229 project.undo_manager.dirty_changed.connect(on_dirty_changed);
230 project.undo_manager.undo_changed.connect(on_undo_changed);
231 project.error_occurred.connect(on_error_occurred);
232 project.playstate_changed.connect(on_playstate_changed);
233 project.track_added.connect(on_track_added);
234 project.track_removed.connect(on_track_removed);
235 project.load_complete.connect(on_load_complete);
236 project.closed.connect(on_project_close);
238 audio_output = new View.AudioOutput(project.media_engine.get_project_audio_caps());
239 project.media_engine.connect_output(audio_output);
240 click_track = new View.ClickTrack(project.media_engine, project);
241 set_position(Gtk.WindowPosition.CENTER);
243 set_default_size(800, 1200);
245 main_group = new Gtk.ActionGroup("main");
246 main_group.add_actions(entries, this);
247 main_group.add_toggle_actions(toggle_entries, this);
249 manager = new Gtk.UIManager();
250 manager.insert_action_group(main_group, 0);
252 manager.add_ui_from_string(ui, -1);
253 } catch (Error e) { error("%s", e.message); }
255 Gtk.MenuBar menubar = (Gtk.MenuBar) get_widget(manager, "/MenuBar");
256 Gtk.Toolbar toolbar = (Gtk.Toolbar) get_widget(manager, "/Toolbar");
257 //play_button = (Gtk.ToggleToolButton) get_widget(manager, "/Toolbar/Play");
258 //record_button = (Gtk.ToggleToolButton) get_widget(manager, "/Toolbar/Record");
259 //rewind_button = (Gtk.ToolButton) get_widget(manager, "/Toolbar/Rewind");
260 //forward_button = (Gtk.ToolButton) get_widget(manager, "/Toolbar/End");
261 //addnew_button = (Gtk.ToolButton) get_widget(manager, "/Toolbar/New");
262 on_undo_changed(false);
264 library = new ClipLibraryView(project, provider, null, Gdk.DragAction.COPY);
265 library.selection_changed.connect(on_library_selection_changed);
266 library.drag_data_received.connect(on_drag_data_received);
268 timeline = new TimeLine(project, provider, Gdk.DragAction.COPY);
269 timeline.track_changed.connect(on_track_changed);
270 timeline.drag_data_received.connect(on_drag_data_received);
271 timeline.size_allocate.connect(on_timeline_size_allocate);
272 timeline.selection_changed.connect(on_timeline_selection_changed);
274 ClipView.context_menu = (Gtk.Menu) manager.get_widget("/ClipContextMenu");
275 ClipLibraryView.context_menu = (Gtk.Menu) manager.get_widget("/LibraryContextMenu");
278 library_scrolled = new Gtk.ScrolledWindow(null, null);
279 library_scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
280 library_scrolled.add_with_viewport(library);
282 Gtk.HBox hbox = new Gtk.HBox(false, 0);
283 header_area = new HeaderArea(this, provider, TimeLine.RULER_HEIGHT);
284 hbox.pack_start(header_area, false, false, 0);
287 timeline_scrolled = new Gtk.ScrolledWindow(null, null);
288 timeline_scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.NEVER);
289 timeline_scrolled.add_with_viewport(timeline);
290 hbox.pack_start(timeline_scrolled, true, true, 0);
291 h_adjustment = timeline_scrolled.get_hadjustment();
295 Gtk.HButtonBox buttons = new Gtk.HButtonBox();
296 addnew_button = new Gtk.Button(); //.with_label("Pl");
297 addnew_button.set_image(new Gtk.Image.from_icon_name("general_add", Gtk.IconSize.BUTTON));
298 addnew_button.clicked.connect(on_track_new);
299 buttons.add(addnew_button);
300 play_button = new Gtk.ToggleButton(); //.with_label("Pl");
301 play_button.set_image(new Gtk.Image.from_icon_name("camera_playback", Gtk.IconSize.BUTTON));
302 play_button.toggled.connect(on_play);
303 buttons.add(play_button);
304 record_button = new Gtk.ToggleButton(); //.with_label("Re");
305 record_button.set_image(new Gtk.Image.from_icon_name("camera_video_recording", Gtk.IconSize.BUTTON));
306 record_button.toggled.connect(on_record);
307 buttons.add(record_button);
308 rewind_button = new Gtk.Button(); //.with_label("Re");
309 rewind_button.set_image(new Gtk.Image.from_icon_name("pdf_viewer_first_page", Gtk.IconSize.BUTTON));
310 rewind_button.clicked.connect(on_rewind);
311 buttons.add(rewind_button);
312 forward_button = new Gtk.Button(); //.with_label("Re");
313 forward_button.set_image(new Gtk.Image.from_icon_name("pdf_viewer_last_page", Gtk.IconSize.BUTTON));
314 forward_button.clicked.connect(on_end);
315 buttons.add(forward_button);
316 zoomin_button = new Gtk.Button(); //.with_label("Re");
317 zoomin_button.set_image(new Gtk.Image.from_icon_name("pdf_zoomin", Gtk.IconSize.BUTTON));
318 zoomin_button.clicked.connect(on_zoom_in);
319 buttons.add(zoomin_button);
320 zoomout_button = new Gtk.Button(); //.with_label("Re");
321 zoomout_button.set_image(new Gtk.Image.from_icon_name("pdf_zoomout", Gtk.IconSize.BUTTON));
322 zoomout_button.clicked.connect(on_zoom_out);
323 buttons.add(zoomout_button);
324 volume_button = new Gtk.Button(); //.with_label("Re");
325 volume_button.set_image(new Gtk.Image.from_icon_name("statusarea_volumelevel4", Gtk.IconSize.BUTTON));
326 volume_button.clicked.connect(on_volume);
327 buttons.add(volume_button);
330 Gtk.VBox vbox = new Gtk.VBox(false, 0);
331 vbox.pack_start(menubar, false, false, 0);
332 //vbox.pack_start(toolbar, false, false, 0);
333 vbox.pack_start(buttons, false, false, 0);
335 Gtk.VBox main_view = new Gtk.VBox(false, 0);
337 timeline_library_pane = new Gtk.HPaned();
338 timeline_library_pane.set_position(project.library_width);
339 timeline_library_pane.add1(hbox);
340 timeline_library_pane.child1_resize = 1;
341 timeline_library_pane.add2(library_scrolled);
342 timeline_library_pane.child2_resize = 0;
343 timeline_library_pane.child1.size_allocate.connect(on_library_size_allocate);
346 main_view.pack_start(timeline_library_pane, true, true, 0);
349 scrollwin = new Hildon.PannableArea();
350 scrollwin.add_with_viewport(main_view);
354 // toggle the library pane off.
357 Gtk.MenuItem? save_graph = (Gtk.MenuItem?)
358 get_widget(manager, "/MenuBar/HelpMenu/SaveGraph");
360 if (!do_print_graph && save_graph != null) {
361 save_graph.destroy();
364 add_accel_group(manager.get_accel_group());
365 timeline.grab_focus();
366 delete_event.connect(on_delete_event);
368 project.load(project_file);
369 if (project_file == null) {
373 project.media_engine.pipeline.set_state(Gst.State.PAUSED);
376 void default_track_set() {
377 project.add_track(new Model.AudioTrack(project, get_default_track_name()));
378 project.tracks[0].set_selected(true);
381 static int default_track_number_compare(void *a, void *b) {
382 string* s1 = (string *) a;
383 string* s2 = (string *) b;
386 s1->scanf("track %d", &i);
387 s2->scanf("track %d", &j);
399 public string get_default_track_name() {
400 List<string> default_track_names = new List<string>();
401 foreach(Model.Track track in project.tracks) {
402 if (track.display_name.has_prefix("track ")) {
403 default_track_names.append(track.display_name);
406 default_track_names.sort(default_track_number_compare);
409 foreach(string s in default_track_names) {
410 string track_name = "track %d".printf(i);
411 if (s != track_name) {
416 return "track %d".printf(i);
419 Gtk.Widget get_widget(Gtk.UIManager manager, string name) {
420 Gtk.Widget widget = manager.get_widget(name);
422 error("can't find widget");
426 void on_track_move_up() {
429 int track_ct = project.tracks.size;
431 foreach(Model.Track track in project.tracks) {
433 if (selected_track() == track) {
438 Model.AudioTrack track = selected_track() as Model.AudioTrack;
439 project.tracks.remove(track);
440 project.tracks.insert(position - 1, track);
444 void on_track_move_down() {
445 // if current track < position last
448 int track_ct = project.tracks.size;
450 foreach(Model.Track track in project.tracks) {
452 if (selected_track() == track) {
456 if (track_ct - 1 > position) {
457 Model.AudioTrack audio_track = selected_track() as Model.AudioTrack;
458 project.tracks.remove(audio_track);
459 project.tracks.insert(position + 1, audio_track);
463 void on_tracks_scroll_up() {
464 // if track count > 4 = Visible tracks
465 /*int track_ct = project.tracks.size;
467 // take bottom track and move to start
468 Model.AudioTrack track = project.tracks.get(track_ct - 1) as Model.AudioTrack;
469 project.tracks.remove(track);
470 project.tracks.insert(0, track);
472 scrollwin_pos_y -= scrollwin_pos_y < 4 ? 0 : 64;
473 scrollwin.scroll_to(0, scrollwin_pos_y);
475 void on_tracks_scroll_down() {
476 // if track count > 4 = visible tracks
477 /*int track_ct = project.tracks.size;
479 // take top track and move to end
480 Model.AudioTrack track = project.tracks.get(0) as Model.AudioTrack;
481 project.tracks.remove(track);
482 project.tracks.add(track);
484 scrollwin_pos_y += 64;
485 scrollwin.scroll_to(0, scrollwin_pos_y);
488 void set_sensitive_group(Gtk.ActionGroup group, string group_path, bool sensitive) {
489 Gtk.Action action = group.get_action(group_path);
490 action.set_sensitive(sensitive);
493 void on_track_changed() {
494 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_track_changed");
498 void on_position_changed() {
499 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_position_changed");
503 void on_track_added(Model.Track track) {
504 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_track_added");
506 track.clip_added.connect(on_clip_added);
507 track.clip_removed.connect(on_clip_removed);
508 track.track_selection_changed.connect(on_track_selection_changed);
511 void on_track_removed(Model.Track unused) {
512 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_track_removed");
516 void on_clip_added(Model.Clip clip) {
517 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_added");
518 clip.moved.connect(on_clip_moved);
522 void on_clip_removed(Model.Clip clip) {
523 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_removed");
524 clip.moved.disconnect(on_clip_moved);
528 void on_track_selection_changed(Model.Track track) {
529 if (track.get_is_selected()) {
530 foreach (Model.Track t in project.tracks) {
532 t.set_selected(false);
538 void on_clip_moved(Model.Clip clip) {
539 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_moved");
543 void on_drag_data_received(Gtk.Widget w, Gdk.DragContext context, int x, int y,
544 Gtk.SelectionData selection_data, uint drag_info, uint time) {
549 bool library_selected = library.has_selection();
550 bool selected = timeline.is_clip_selected();
551 bool playhead_on_clip = project.playhead_on_clip();
552 int number_of_tracks = project.tracks.size;
553 bool is_stopped = is_stopped();
554 bool one_selected = false;
555 if (library_selected) {
556 one_selected = library.get_selected_files().size == 1;
557 } else if (selected) {
558 one_selected = timeline.selected_clips.size == 1;
562 set_sensitive_group(main_group, "NewProject", is_stopped);
563 set_sensitive_group(main_group, "Open", is_stopped);
564 set_sensitive_group(main_group, "Save", is_stopped);
565 set_sensitive_group(main_group, "SaveAs", is_stopped);
566 set_sensitive_group(main_group, "Settings", is_stopped);
567 set_sensitive_group(main_group, "Export", project.can_export());
568 set_sensitive_group(main_group, "Quit", !project.transport_is_recording());
571 set_sensitive_group(main_group, "Undo", is_stopped && project.undo_manager.can_undo);
572 set_sensitive_group(main_group, "Copy", is_stopped && selected);
573 set_sensitive_group(main_group, "Cut", is_stopped && selected);
574 set_sensitive_group(main_group, "Paste", timeline.clipboard.clips.size != 0 && is_stopped);
575 set_sensitive_group(main_group, "Delete", (selected || library_selected) && is_stopped);
576 set_sensitive_group(main_group, "SplitAtPlayhead",
577 selected && playhead_on_clip && is_stopped);
578 set_sensitive_group(main_group, "TrimToPlayhead",
579 selected && playhead_on_clip && is_stopped);
580 set_sensitive_group(main_group, "ClipProperties", one_selected);
583 set_sensitive_group(main_group, "ZoomProject", project.get_length() != 0);
586 set_sensitive_group(main_group, "Rename", number_of_tracks > 0 && is_stopped);
587 set_sensitive_group(main_group, "DeleteTrack", number_of_tracks > 0 && is_stopped);
588 set_sensitive_group(main_group, "NewTrack", is_stopped);
591 set_sensitive_group(main_group, "Play", true);
592 set_sensitive_group(main_group, "Record", number_of_tracks > 0 && is_stopped);
595 public Model.Track? selected_track() {
596 foreach (Model.Track track in project.tracks) {
597 if (track.get_is_selected()) {
601 error("can't find selected track");
605 public void scroll_to_beginning() {
606 h_adjustment.set_value(0.0);
609 public void page_to_time(int64 time) {
610 double location_in_window = timeline.provider.time_to_xpos(time) - h_adjustment.get_value();
611 int window_width = timeline.parent.allocation.width;
612 if (location_in_window > 0.9 * window_width ||
613 location_in_window < 0.1 * window_width) {
614 scroll_to_time(time);
618 public void scroll_to_time(int64 time) {
619 int new_adjustment = timeline.provider.time_to_xpos(time);
620 int window_width = timeline.parent.allocation.width;
621 if (new_adjustment < timeline.parent.allocation.width) {
624 new_adjustment = new_adjustment - window_width / 2;
627 int max_value = (int)(h_adjustment.upper - timeline_scrolled.allocation.width);
628 if (new_adjustment > max_value) {
629 new_adjustment = max_value;
632 h_adjustment.set_value(new_adjustment);
635 public void scroll_to_end() {
636 scroll_to_time(project.get_length());
639 static int sgn(int x) {
642 return x < 0 ? -1 : 1;
645 public void scroll_toward_center(int xpos) {
646 if (cursor_pos == -1) {
647 cursor_pos = xpos - (int) h_adjustment.value;
649 // Move the cursor position toward the center of the window. We compute
650 // the remaining distance and move by its square root; this results in
651 // a smooth decelerating motion.
652 int page_size = (int) h_adjustment.page_size;
653 int diff = page_size / 2 - cursor_pos;
654 int d = sgn(diff) * (int) Math.sqrt(diff.abs());
656 int x = int.max(0, xpos - cursor_pos);
657 int max_value = (int)(h_adjustment.upper - timeline_scrolled.allocation.width);
661 h_adjustment.set_value(x);
664 public override bool key_press_event(Gdk.EventKey event) {
665 switch (event.keyval) {
666 case KeySyms.KP_Enter:
668 if (project.transport_is_recording()) {
671 if ((event.state & GDK_SHIFT_ALT_CONTROL_MASK) != 0)
672 return base.key_press_event(event);
676 if (project.transport_is_recording()) {
679 if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0) {
680 project.go_previous();
682 project.media_engine.go(project.transport_get_position() - Gst.SECOND);
684 page_to_time(project.transport_get_position());
687 if (project.transport_is_recording()) {
690 if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0) {
693 project.media_engine.go(project.transport_get_position() + Gst.SECOND);
695 page_to_time(project.transport_get_position());
702 case KeySyms.KP_Subtract:
704 case KeySyms.underscore:
708 return base.key_press_event(event);
715 string filename = null;
716 if (DialogUtils.save(this, "Export", false, export_filters, ref filename)) {
718 new MultiFileProgress(this, 1, "Export", project.media_engine);
719 project.media_engine.disconnect_output(audio_output);
720 audio_export = new View.OggVorbisExport(View.MediaConnector.MediaTypes.Audio,
721 filename, project.media_engine.get_project_audio_export_caps());
722 project.media_engine.connect_output(audio_export);
723 project.media_engine.start_export(filename);
725 do_error_dialog("Could not export file", e.message);
730 void on_post_export(bool canceled) {
731 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_post_export");
732 project.media_engine.disconnect_output(audio_export);
733 project.media_engine.connect_output(audio_output);
736 GLib.FileUtils.remove(audio_export.get_filename());
742 void on_project_new_finished_closing(bool project_did_close) {
743 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_project_new_finished_closing");
744 project.closed.disconnect(on_project_close);
745 finished_closing.disconnect(on_project_new_finished_closing);
746 if (project_did_close) {
747 project.media_engine.set_play_state(PlayState.LOADING);
750 project.media_engine.pipeline.set_state(Gst.State.PAUSED);
751 project.undo_manager.reset();
755 void on_project_new() {
757 project.closed.connect(on_project_close);
758 finished_closing.connect(on_project_new_finished_closing);
762 void on_project_open_finished_closing(bool project_did_close) {
763 project.closed.disconnect(on_project_close);
764 finished_closing.disconnect(on_project_open_finished_closing);
765 if (project_did_close) {
766 GLib.SList<string> filenames;
767 if (DialogUtils.open(this, filters, false, false, out filenames)) {
769 project.load(filenames.data);
774 void on_project_open() {
776 project.closed.connect(on_project_close);
777 finished_closing.connect(on_project_open_finished_closing);
781 void on_project_save_as() {
785 void on_project_save() {
789 void on_save_new_file_finished_closing(bool did_close) {
790 project.closed.disconnect(on_project_close);
791 finished_closing.disconnect(on_save_new_file_finished_closing);
792 project.load(project.get_project_file());
796 if (project.get_project_file() != null) {
801 return save_dialog();
806 bool saving_new_file = project.get_project_file() == null;
808 string filename = project.get_project_file();
809 bool create_directory = project.get_project_file() == null;
810 if (DialogUtils.save(this, "Save Project", create_directory, filters, ref filename)) {
811 project.save(filename);
812 if (saving_new_file && project.get_project_file() != null) {
813 project.closed.connect(on_project_close);
814 finished_closing.connect(on_save_new_file_finished_closing);
822 void on_properties() {
823 Gtk.Builder builder = new Gtk.Builder();
825 builder.add_from_file(AppDirs.get_exec_dir().get_path() + "/resources/fillmore.glade");
826 //"/home/developer/Fillmore/resources/fillmore.glade");
827 //AppDirs.get_resources_dir().get_child("fillmore.glade").get_path());
828 } catch(GLib.Error e) {
829 stdout.printf("%s\n", e.message);
832 builder.connect_signals(null);
833 ProjectProperties properties = (ProjectProperties)builder.get_object("projectproperties1");
834 properties.setup(project, builder);
836 int response = properties.run();
837 if (response == Gtk.ResponseType.APPLY) {
838 string description = "Set Project Properties";
839 project.undo_manager.start_transaction(description);
840 project.set_bpm(properties.get_tempo());
841 project.set_time_signature(properties.get_time_signature());
842 project.click_during_record = properties.during_record();
843 project.click_during_play = properties.during_play();
844 project.click_volume = properties.get_click_volume();
845 project.undo_manager.end_transaction(description);
847 properties.destroy();
850 void on_quit_finished_closing(bool project_did_close) {
851 project.closed.disconnect(on_project_close);
852 finished_closing.disconnect(on_quit_finished_closing);
853 if (project_did_close) {
859 if (!project.transport_is_recording()) {
860 project.closed.connect(on_project_close);
861 finished_closing.connect(on_quit_finished_closing);
866 bool on_delete_event() {
867 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_delete_event");
872 void on_project_close() {
873 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_project_close");
874 if (project.undo_manager.is_dirty) {
875 switch(DialogUtils.save_close_cancel(this, null, "Save changes before closing?")) {
876 case Gtk.ResponseType.ACCEPT:
878 finished_closing(false);
882 case Gtk.ResponseType.NO:
883 // if the user has never saved the file but quits anyway, save in .fillmore
884 if (project.get_project_file() == null) {
888 case Gtk.ResponseType.DELETE_EVENT: // when user presses escape.
889 case Gtk.ResponseType.CANCEL:
890 finished_closing(false);
897 finished_closing(true);
918 if (library.has_selection()) {
919 library.delete_selection();
921 timeline.delete_selection();
925 void on_select_all() {
926 if (library.has_selection()) {
927 library.select_all();
929 timeline.select_all();
933 public void on_split_at_playhead() {
934 project.split_at_playhead();
937 public void on_trim_to_playhead() {
938 project.trim_to_playhead();
941 public void on_clip_properties() {
942 if (library.has_selection()) {
943 Gee.ArrayList<string> files = library.get_selected_files();
944 if (files.size == 1) {
945 string file_name = files.get(0);
946 Model.ClipFile? clip_file = project.find_clipfile(file_name);
947 DialogUtils.show_clip_properties(this, null, clip_file, null);
950 Gee.ArrayList<ClipView> clips = timeline.selected_clips;
951 if (clips.size == 1) {
952 ClipView clip_view = clips.get(0);
953 DialogUtils.show_clip_properties(this, clip_view, null, null);
960 void on_track_new() {
961 UI.TrackInformation dialog = new UI.TrackInformation();
962 dialog.set_track_name(get_default_track_name());
963 if (track_name_dialog(dialog, null)) {
964 project.add_track(new Model.AudioTrack(project, dialog.get_track_name()));
969 void on_track_rename() {
970 UI.TrackInformation dialog = new UI.TrackInformation();
971 Model.Track track = selected_track();
972 dialog.set_title("Rename Track");
973 dialog.set_track_name(selected_track().display_name);
974 if (track_name_dialog(dialog, track)) {
975 track.set_display_name(dialog.get_track_name());
980 bool track_name_dialog(UI.TrackInformation dialog, Model.Track? track) {
981 Gtk.ResponseType result = Gtk.ResponseType.OK;
984 result = (Gtk.ResponseType) dialog.run();
985 string new_name = dialog.get_track_name();
987 if (result == Gtk.ResponseType.OK) {
988 if (new_name == "") {
990 DialogUtils.error("Invalid track name.", "The track name cannot be empty.");
992 is_ok = !project.is_duplicate_track_name(track, new_name);
994 DialogUtils.error("Duplicate track name.",
995 "A track with this name already exists.");
999 } while (result == Gtk.ResponseType.OK && !is_ok);
1000 return result == Gtk.ResponseType.OK && is_ok;
1003 void on_track_remove() {
1004 project.remove_track(selected_track());
1012 void on_zoom_out() {
1016 void on_zoom_to_project() {
1017 timeline.zoom_to_project(h_adjustment.page_size);
1021 project.snap_to_clip = !project.snap_to_clip;
1024 void on_view_library() {
1025 if (timeline_library_pane.child2 == library_scrolled) {
1026 timeline_library_pane.remove(library_scrolled);
1027 project.library_visible = false;
1029 timeline_library_pane.add2(library_scrolled);
1030 timeline_library_pane.show_all();
1031 project.library_visible = true;
1035 void on_library_size_allocate(Gdk.Rectangle rectangle) {
1036 if (!loading && timeline_library_pane.child2 == library_scrolled) {
1037 project.library_width = rectangle.width;
1043 void on_help_contents() {
1045 Gtk.show_uri(null, "http://trac.yorba.org/wiki/UsingFillmore0.1", 0);
1046 } catch (GLib.Error e) {
1051 Gtk.show_about_dialog(this,
1052 "version", project.get_version(),
1053 "comments", "An audio editor and recorder",
1054 "copyright", "Copyright 2009-2010 Yorba Foundation",
1055 "website", "http://www.yorba.org",
1056 "license", project.get_license(),
1057 "website-label", "Visit the Yorba web site",
1058 "authors", project.authors
1062 void on_save_graph() {
1063 project.print_graph(project.media_engine.pipeline, "save_graph");
1073 project.media_engine.go(0);
1074 scroll_to_beginning();
1083 if (project.transport_is_recording()) {
1084 set_sensitive_group(main_group, "Record", true);
1085 record_button.set_active(false);
1086 play_button.set_active(false);
1087 project.media_engine.pause();
1088 } else if (play_button.get_active())
1089 project.media_engine.do_play(PlayState.PLAYING);
1091 project.media_engine.pause();
1095 if (record_button.get_active()) {
1096 Model.AudioTrack audio_track = selected_track() as Model.AudioTrack;
1097 int number_of_channels;
1098 if (audio_track.get_num_channels(out number_of_channels)) {
1099 if (number_of_channels > CHANNELS_PER_TRACK_RECORD) {
1100 record_button.set_active(false);
1101 on_error_occurred("Can not record onto a stereo track", null);
1105 set_sensitive_group(main_group, "Record", false);
1106 set_sensitive_group(main_group, "Play", false);
1107 project.record(audio_track);
1114 void on_callback_pulse() {
1115 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_callback_pulse");
1116 if (project.transport_is_playing()) {
1117 scroll_toward_center(provider.time_to_xpos(project.media_engine.position));
1119 timeline.queue_draw();
1122 int64 get_zoom_center_time() {
1123 return project.transport_get_position();
1126 void do_zoom(float increment) {
1127 center_time = get_zoom_center_time();
1128 timeline.zoom(increment);
1131 void on_timeline_size_allocate(Gdk.Rectangle rectangle) {
1132 if (center_time != -1) {
1133 int new_center_pixel = provider.time_to_xpos(center_time);
1134 int page_size = (int)(h_adjustment.get_page_size() / 2);
1135 h_adjustment.clamp_page(new_center_pixel - page_size, new_center_pixel + page_size);
1140 void on_timeline_selection_changed(bool selected) {
1141 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_timeline_selection_changed");
1143 library.unselect_all();
1148 public void on_library_selection_changed(bool selected) {
1149 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_library_selection_changed");
1151 timeline.deselect_all_clips();
1152 timeline.queue_draw();
1159 /*static void main(string[] args) {
1160 Hildon.gtk_init(ref args);
1162 OptionContext context = new OptionContext(
1163 " [project file] - Record and edit multitrack audio");
1164 context.add_main_entries(options, null);
1165 context.add_group(Gst.init_get_option_group());
1168 context.parse(ref args);
1169 } catch (GLib.Error arg_error) {
1170 stderr.printf("%s\nRun 'fillmore --help' for a full list of available command line options.",
1176 GLib.Environment.set_application_name("Fillmore");
1177 if (debug_level > -1) {
1178 set_logging_level((Logging.Level)debug_level);
1181 AppDirs.init(args[0], _PROGRAM_NAME);
1182 string rc_file = AppDirs.get_resources_dir().get_child("fillmore.rc").get_path();
1184 Gtk.rc_parse(rc_file);
1187 string? project_file = null;
1188 if (args.length > 1) {
1189 project_file = args[1];
1191 project_file = GLib.Filename.from_uri(project_file);
1192 } catch (GLib.Error e) { }
1195 ClassFactory.set_class_factory(new FillmoreClassFactory());
1196 View.MediaEngine.can_run();
1198 Recorder recorder = new Recorder(project_file);
1199 recorder.show_all();
1202 do_error_dialog("Could not start application.",e.message);
1206 public static void do_error_dialog(string major_message, string? minor_message) {
1207 DialogUtils.error(major_message, minor_message);
1210 public void on_load_error(string message) {
1211 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_load_error");
1212 load_errors.add(message);
1215 public void on_load_complete() {
1216 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_load_complete");
1217 project.media_engine.pipeline.set_state(Gst.State.PAUSED);
1218 timeline_library_pane.set_position(project.library_width);
1220 Gtk.ToggleAction action = main_group.get_action("Library") as Gtk.ToggleAction;
1221 if (action.get_active() != project.library_visible) {
1222 action.set_active(project.library_visible);
1225 action = main_group.get_action("Snap") as Gtk.ToggleAction;
1226 if (action.get_active() != project.snap_to_clip) {
1227 action.set_active(project.snap_to_clip);
1230 if (project.library_visible) {
1231 if (timeline_library_pane.child2 != library_scrolled) {
1232 timeline_library_pane.add2(library_scrolled);
1235 if (timeline_library_pane.child2 == library_scrolled) {
1236 timeline_library_pane.remove(library_scrolled);
1240 if (load_errors.size > 0) {
1241 string message = "";
1242 foreach (string s in load_errors) {
1243 message = message + s + "\n";
1245 do_error_dialog("An error occurred loading the project.", message);
1251 void on_name_changed() {
1252 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_name_changed");
1253 set_title(project.get_file_display_name());
1256 void on_dirty_changed(bool isDirty) {
1257 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_dirty_changed");
1258 Gtk.MenuItem? file_save = (Gtk.MenuItem?) get_widget(manager, "/MenuBar/ProjectMenu/Save");
1259 assert(file_save != null);
1260 file_save.set_sensitive(isDirty);
1263 void on_undo_changed(bool can_undo) {
1264 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_undo_changed");
1265 Gtk.MenuItem? undo = (Gtk.MenuItem?) get_widget(manager, "/MenuBar/EditMenu/EditUndo");
1266 assert(undo != null);
1267 //undo.set_label("_Undo " + project.undo_manager.get_undo_title());
1268 set_sensitive_group(main_group, "Undo", is_stopped() && project.undo_manager.can_undo);
1271 void on_playstate_changed(PlayState playstate) {
1272 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_playstate_changed");
1273 if (playstate == PlayState.STOPPED) {
1275 play_button.set_active(false);
1276 set_sensitive_group(main_group, "Export", project.can_export());
1281 void on_error_occurred(string major_message, string? minor_message) {
1282 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_error_occurred");
1283 DialogUtils.error(major_message, minor_message);
1286 string get_fillmore_directory() {
1287 return Path.build_filename(GLib.Environment.get_home_dir(), ".fillmore");
1290 // TransportDelegate methods
1292 return project.transport_is_playing();
1295 bool is_recording() {
1296 return project.transport_is_recording();
1300 return !(is_playing() || is_recording());
1304 public class Fillmore : Hildon.Program {
1307 public void run(string[] args) {
1309 OptionContext context = new OptionContext(
1310 " [project file] - Record and edit multitrack audio");
1311 context.add_main_entries(options, null);
1312 context.add_group(Gst.init_get_option_group());
1315 context.parse(ref args);
1316 } catch (GLib.Error arg_error) {
1317 stderr.printf("%s\nRun 'fillmore --help' for a full list of available command line options.",
1321 //Gtk.init(ref args);
1323 GLib.Environment.set_application_name("Fillmore");
1324 if (debug_level > -1) {
1325 set_logging_level((Logging.Level)debug_level);
1328 AppDirs.init(args[0], _PROGRAM_NAME);
1329 string rc_file = AppDirs.get_resources_dir().get_child("fillmore.rc").get_path();
1331 Gtk.rc_parse(rc_file);
1332 //Gst.init(ref args);
1334 string? project_file = null;
1335 if (args.length > 1) {
1336 project_file = args[1];
1338 project_file = GLib.Filename.from_uri(project_file);
1339 } catch (GLib.Error e) { }
1342 ClassFactory.set_class_factory(new FillmoreClassFactory());
1343 View.MediaEngine.can_run();
1345 Recorder recorder = new Recorder(project_file);
1346 recorder.show_all();
1349 Recorder.do_error_dialog("Could not start application.",e.message);
1354 public static int main(string[] args) {
1357 Fillmore fillmore = new Fillmore();
1362 static void NO_main(string[] args) {
1363 Hildon.gtk_init(ref args);
1365 OptionContext context = new OptionContext(
1366 " [project file] - Record and edit multitrack audio");
1367 context.add_main_entries(options, null);
1368 context.add_group(Gst.init_get_option_group());
1371 context.parse(ref args);
1372 } catch (GLib.Error arg_error) {
1373 stderr.printf("%s\nRun 'fillmore --help' for a full list of available command line options.",
1379 GLib.Environment.set_application_name("Fillmore");
1380 if (debug_level > -1) {
1381 set_logging_level((Logging.Level)debug_level);
1384 AppDirs.init(args[0], _PROGRAM_NAME);
1385 string rc_file = AppDirs.get_resources_dir().get_child("fillmore.rc").get_path();
1387 Gtk.rc_parse(rc_file);
1390 string? project_file = null;
1391 if (args.length > 1) {
1392 project_file = args[1];
1394 project_file = GLib.Filename.from_uri(project_file);
1395 } catch (GLib.Error e) { }
1398 ClassFactory.set_class_factory(new FillmoreClassFactory());
1399 View.MediaEngine.can_run();
1401 Recorder recorder = new Recorder(project_file);
1402 recorder.show_all();
1405 Recorder.do_error_dialog("Could not start application.",e.message);