Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:12.2
fillmore-lombard
fillmore-lombard-git.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File fillmore-lombard-git.patch of Package fillmore-lombard
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a63b29 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.a +*.c +*.o +*.so +*~ +.stamp +/fillmore +/lombard +/src/marina/marina.h +/src/marina/marina/marina.vapi +/media_test diff --git a/Makefile b/Makefile index ff366b5..9d57188 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ default: all BUILD_ROOT = 1 -VERSION = 0.1.0 +VERSION = 0.1.0+trunk FILLMORE = fillmore LOMBARD = lombard MEDIA_TEST = media_test diff --git a/THANKS b/THANKS index ca61fe8..8f5433e 100644 --- a/THANKS +++ b/THANKS @@ -1,4 +1,5 @@ We'd like to thank the following contributors: Matt Jones <mattjones@workhorsy.org> +Vincent Untz <vuntz@gnome.org> diff --git a/configure b/configure index e440cfd..0cbbcbb 100755 --- a/configure +++ b/configure @@ -17,7 +17,13 @@ configure_help() { printf "\t--build=DIR\t\tBuild secondary files in DIR.\n" printf "\t--debug | --release\tBuild executable for debugging or release.\n" printf "\t\t\t\t[--release]\n" + printf "\t--prefix=PREFIX\t\tPrepend PREFIX to program installation paths.\n" + printf "\t\t\t\t[/usr/local]\n" printf "\t--define=SYMBOL\t\tDefine a symbol for the Vala compiler.\n" + printf "\t--disable-desktop-update\n" + printf "\t\t\t\tDisable desktop database update.\n" + printf "\t--disable-icon-update\n" + printf "\t\t\t\tDisable icon cache update.\n" printf "\n" } @@ -39,7 +45,15 @@ do -h | --help) configure_help exit 0 ;; - + + --prefix) if [ ! $value ] + then + abort $1 + fi + + variables="${variables}PREFIX=$value\n" + ;; + --assume-pkgs) variables="${variables}ASSUME_PKGS=1\n" ;; @@ -61,6 +75,11 @@ do --define) variables="${variables}USER_VALAFLAGS+=--define=$value\n" ;; + --disable-desktop-update) variables="${variables}DISABLE_DESKTOP_UPDATE=1\n" + ;; + + --disable-icon-update) variables="${variables}DISABLE_ICON_UPDATE=1\n" + ;; *) if [ ! $value ] then diff --git a/fillmore-glade b/fillmore-glade new file mode 100755 index 0000000..9ff0fb8 --- /dev/null +++ b/fillmore-glade @@ -0,0 +1,2 @@ +#!/bin/bash +GLADE_CATALOG_PATH=. GLADE_MODULE_PATH=. glade resources/fillmore.glade > /dev/null 2>&1 & diff --git a/marina.mk b/marina.mk index a37ab2d..79c630b 100644 --- a/marina.mk +++ b/marina.mk @@ -1,7 +1,9 @@ VALAC = valac -MIN_VALAC_VERSION = 0.9.1 +MIN_VALAC_VERSION = 0.9.3 # defaults that may be overridden by configure.mk +ifndef PREFIX PREFIX=/usr/local +endif INSTALL_PROGRAM = install INSTALL_DATA = install -m 644 @@ -98,7 +100,9 @@ ifndef DISABLE_DESKTOP_UPDATE endif mkdir -p $(DESTDIR)$(PREFIX)/share/mime/packages $(INSTALL_DATA) ../../misc/$(PROGRAM_NAME).xml $(DESTDIR)$(PREFIX)/share/mime/packages +ifndef DISABLE_MIME_UPDATE -update-mime-database $(DESTDIR)$(PREFIX)/share/mime +endif uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/$(PROGRAM_NAME) @@ -106,7 +110,9 @@ uninstall: rm -fr $(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/$(PROGRAM_NAME).svg rm -f $(DESTDIR)$(PREFIX)/share/applications/$(PROGRAM_NAME).desktop rm -f $(DESTDIR)$(PREFIX)/share/mime/packages/$(PROGRAM_NAME).xml +ifndef DISABLE_MIME_UPDATE -update-mime-database $(DESTDIR)$(PREFIX)/share/mime +endif $(VALA_STAMP): $(EXPANDED_SRC_FILES) $(EXPANDED_VAPI_FILES) $(EXPANDED_SRC_HEADER_FILES) Makefile \ $(CONFIG_IN) $(TEMP_MARINA_VAPI) diff --git a/misc/fillmore.desktop b/misc/fillmore.desktop index dfa8db8..5961495 100644 --- a/misc/fillmore.desktop +++ b/misc/fillmore.desktop @@ -6,8 +6,8 @@ GenericName=Audio Editor Comment=Record and edit multitrack audio Exec=fillmore %U Icon=fillmore -MimeType=application/fillmore;text/yorba-media +MimeType=application/fillmore;text/yorba-media; Terminal=false Type=Application -Categories=AudioVideo;GNOME;GTK; +Categories=AudioVideo;Audio;AudioVideoEditing;GNOME;GTK; X-GIO-NoFuse=true diff --git a/misc/lombard.desktop b/misc/lombard.desktop index 68e0525..04fdf71 100644 --- a/misc/lombard.desktop +++ b/misc/lombard.desktop @@ -6,9 +6,9 @@ GenericName=Video Editor Comment=Create and edit movies Exec=lombard %U Icon=lombard -MimeType=application/lombard;text/yorba-media +MimeType=application/lombard;text/yorba-media; Terminal=false Type=Application -Categories=AudioVideo;GNOME;GTK; +Categories=AudioVideo;Video;AudioVideoEditing;GNOME;GTK; X-GIO-NoFuse=true diff --git a/resources/fillmore.glade b/resources/fillmore.glade index 80f8054..85296db 100644 --- a/resources/fillmore.glade +++ b/resources/fillmore.glade @@ -15,12 +15,10 @@ <child internal-child="vbox"> <object class="GtkVBox" id="dialog-vbox1"> <property name="visible">True</property> - <property name="orientation">vertical</property> <property name="spacing">2</property> <child> <object class="GtkVBox" id="vbox1"> <property name="visible">True</property> - <property name="orientation">vertical</property> <property name="spacing">6</property> <child> <object class="GtkLabel" id="label2"> @@ -276,7 +274,7 @@ <object class="GtkAdjustment" id="tempo_adjustment"> <property name="value">40</property> <property name="lower">30</property> - <property name="upper">130</property> + <property name="upper">240</property> <property name="step_increment">1</property> <property name="page_increment">10</property> <property name="page_size">10</property> @@ -307,4 +305,250 @@ <property name="page_increment">0.10000000000000001</property> <property name="page_size">0.10000000000000001</property> </object> + <object class="GtkAdjustment" id="trackvolume_adjustment"> + <property name="value">0.80000000000000004</property> + <property name="upper">1.5</property> + <property name="step_increment">0.01</property> + <property name="page_increment">0.10000000000000001</property> + </object> + <object class="GtkAdjustment" id="pan_adjustment"> + <property name="lower">-1</property> + <property name="upper">1</property> + <property name="step_increment">0.10000000000000001</property> + <property name="page_increment">0.10000000000000001</property> + </object> + <object class="AudioTrackHeader" id="HeaderArea"> + <property name="visible">True</property> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="top_padding">5</property> + <property name="bottom_padding">5</property> + <property name="left_padding">5</property> + <property name="right_padding">5</property> + <child> + <object class="GtkHBox" id="hea"> + <property name="width_request">200</property> + <property name="visible">True</property> + <child> + <object class="GtkVBox" id="vbox2"> + <property name="visible">True</property> + <property name="spacing">2</property> + <child> + <object class="GtkLabel" id="track_label"> + <property name="width_request">100</property> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="xpad">2</property> + <property name="label" translatable="yes">Track 1</property> + <attributes> + <attribute name="foreground" value="#eeeeeeeeeeee"/> + </attributes> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox2"> + <property name="visible">True</property> + <child> + <object class="GtkToggleButton" id="mute"> + <property name="label" translatable="yes">M</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="focus_on_click">False</property> + <signal name="toggled" handler="audio_track_header_on_mute_toggled" object="HeaderArea"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="solo"> + <property name="label" translatable="yes">S</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="focus_on_click">False</property> + <signal name="toggled" handler="audio_track_header_on_solo_toggled" object="HeaderArea"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkToggleButton" id="record_enable"> + <property name="label" translatable="yes">R</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="focus_on_click">False</property> + <signal name="toggled" handler="audio_track_header_on_record_enable_toggled" object="HeaderArea"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="input"> + <property name="label" translatable="yes">I</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="focus_on_click">False</property> + <signal name="clicked" handler="audio_track_header_on_input_clicked" object="HeaderArea"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox3"> + <property name="visible">True</property> + <child> + <object class="GtkAlignment" id="alignment2"> + <property name="visible">True</property> + <property name="bottom_padding">4</property> + <property name="left_padding">8</property> + <property name="right_padding">10</property> + <child> + <object class="ViewAudioMeter" id="audiometer1"> + <property name="width_request">100</property> + <property name="visible">True</property> + </object> + </child> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox4"> + <property name="visible">True</property> + <child> + <object class="GtkImage" id="image4"> + <property name="visible">True</property> + <property name="pixbuf">min_speaker.png</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="VolumeSlider" id="track_volume"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">trackvolume_adjustment</property> + <signal name="value_changed" handler="audio_track_header_on_volume_value_changed" object="HeaderArea"/> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkImage" id="image5"> + <property name="visible">True</property> + <property name="pixbuf">max_speaker.png</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox3"> + <property name="visible">True</property> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="label" translatable="yes">L</property> + <attributes> + <attribute name="foreground" value="#eeeeeeeeeeee"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="padding">3</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="PanSlider" id="track_pan"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="adjustment">pan_adjustment</property> + <signal name="value_changed" handler="audio_track_header_on_pan_value_changed" object="HeaderArea"/> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="label" translatable="yes">R</property> + <attributes> + <attribute name="foreground" value="#eeeeeeeeeeee"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="padding">2</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> + <object class="GtkSizeGroup" id="header_button_size"> + <property name="mode">both</property> + <widgets> + <widget name="mute"/> + <widget name="solo"/> + <widget name="record_enable"/> + <widget name="input"/> + </widgets> + </object> </interface> diff --git a/resources/fillmore.rc b/resources/fillmore.rc index 85d92d9..fba3d52 100644 --- a/resources/fillmore.rc +++ b/resources/fillmore.rc @@ -14,9 +14,56 @@ style "pan" { } style "hseparator" { + bg[NORMAL] = "#999" +} + +style "togglebutton" { + GtkButton::child-displacement-x = 0 + GtkButton::child-displacement-y = 0 + bg[SELECTED] = "#79b" + bg[NORMAL] = "#777" + bg[INSENSITIVE] = "#555" + bg[PRELIGHT] = "#888" +} + +style "mutetogglebutton" = "togglebutton" { + bg[ACTIVE] = "#ED773B" + bg[NORMAL] = "#A68574" + bg[PRELIGHT] = "#FF6F26" + bg[INSENSITIVE] = "#cab3a6" +} + +style "mutetext" { + fg[INSENSITIVE] = "#d5cac3" +} + +style "solotogglebutton" = "togglebutton" { + bg[ACTIVE] = "#EDDB3B" + bg[NORMAL] = "#A6A174" + bg[PRELIGHT] = "#FFE926" +} + +style "recordenabletogglebutton" = "togglebutton" { + bg[ACTIVE] = "#db2222" + bg[NORMAL] = "#854444" + bg[PRELIGHT] = "#ed1010" +} + +style "audiotrackheader" { + bg[SELECTED] = "#68a" bg[NORMAL] = "#666" } +style "clipview" { + text[NORMAL] = "black" +} + class "PanSlider" style "pan" class "VolumeSlider" style "pan" class "TrackSeparator" style "hseparator" +widget "*.mute" style "mutetogglebutton" +widget "*.solo" style "solotogglebutton" +widget "*.record_enable" style "recordenabletogglebutton" +class "AudioTrackHeader" style "audiotrackheader" +class "ClipView" style "clipview" + diff --git a/resources/lombard.rc b/resources/lombard.rc new file mode 100644 index 0000000..bf2ec31 --- /dev/null +++ b/resources/lombard.rc @@ -0,0 +1,12 @@ +/* Copyright 2010 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +style "clipview" { + text[NORMAL] = "black" +} + +class "ClipView" style "clipview" + diff --git a/resources/max_speaker.png b/resources/max_speaker.png index 7c294bc..02566d6 100644 Binary files a/resources/max_speaker.png and b/resources/max_speaker.png differ diff --git a/resources/min_speaker.png b/resources/min_speaker.png index 70c4446..77888cc 100644 Binary files a/resources/min_speaker.png and b/resources/min_speaker.png differ diff --git a/src/fillmore/FillmoreClassFactory.vala b/src/fillmore/FillmoreClassFactory.vala deleted file mode 100644 index 9fd2d07..0000000 --- a/src/fillmore/FillmoreClassFactory.vala +++ /dev/null @@ -1,56 +0,0 @@ -/* Copyright 2009-2010 Yorba Foundation - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -class TrackSeparator : Gtk.HSeparator { -//this class is referenced in the resource file -} - -class FillmoreTrackView : Gtk.VBox, TrackView { - TrackView track_view; - public FillmoreTrackView(TrackView track_view) { - this.track_view = track_view; - track_view.clip_view_added.connect(on_clip_view_added); - - pack_start(track_view, true, true, 0); - pack_start(new TrackSeparator(), false, false, 0); - can_focus = false; - } - - public void move_to_top(ClipView clip_view) { - track_view.move_to_top(clip_view); - } - - public void resize() { - track_view.resize(); - } - - public Model.Track get_track() { - return track_view.get_track(); - } - - public int get_track_height() { - return track_view.get_track_height(); - } - - void on_clip_view_added(ClipView clip_view) { - clip_view_added(clip_view); - } - - Gtk.Widget? find_child(double x, double y) { - return track_view.find_child(x, y); - } - - void select_all() { - track_view.select_all(); - } -} - -public class FillmoreClassFactory : ClassFactory { - public override TrackView get_track_view(Model.Track track, TimeLine timeline) { - TrackView track_view = base.get_track_view(track, timeline); - return new FillmoreTrackView(track_view); - } -} diff --git a/src/fillmore/audio_project.vala b/src/fillmore/audio_project.vala index ac567ca..4c8bc78 100644 --- a/src/fillmore/audio_project.vala +++ b/src/fillmore/audio_project.vala @@ -21,15 +21,15 @@ class RecordFetcherCompletion : FetcherCompletion { public override void complete(Fetcher fetch) { base.complete(fetch); - Clip the_clip = new Clip(fetch.clipfile, MediaType.AUDIO, - isolate_filename(fetch.clipfile.filename), 0, 0, fetch.clipfile.length, false); + Clip the_clip = new Clip(fetch.mediafile, MediaType.AUDIO, + isolate_filename(fetch.mediafile.filename), 0, 0, fetch.mediafile.length, false); project.undo_manager.start_transaction("Record"); track.append_at_time(the_clip, position, true); project.undo_manager.end_transaction("Record"); } } -class AudioProject : Project { +public class AudioProject : Project { bool has_been_saved; public AudioProject(string? filename) throws Error { @@ -45,7 +45,7 @@ class AudioProject : Project { } } - public override TimeCode get_clip_time(ClipFile f) { + public override TimeCode get_clip_time(MediaFile f) { TimeCode t = {}; t.get_from_length(f.length); @@ -81,19 +81,32 @@ class AudioProject : Project { inactive_tracks.add(track); return; } - + Model.AudioTrack audio_track = track as Model.AudioTrack; + audio_track.record_enable_changed.connect(on_record_enable_changed); base.add_track(track); } - + public void record(AudioTrack track) { media_engine.record(track); } + void on_record_enable_changed(Model.AudioTrack changed_track) { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_record_enable_changed"); + if (changed_track.record_enable) { + foreach (Track track in tracks) { + if (track != changed_track) { + Model.AudioTrack audio_track = track as Model.AudioTrack; + audio_track.record_enable = false; + } + } + } + } + public void on_record_completed() { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_record_completed"); try { create_clip_fetcher(new Model.RecordFetcherCompletion(this, media_engine.record_track, - media_engine.record_region.start), media_engine.record_region.clipfile.filename); + media_engine.record_region.start), media_engine.record_region.mediafile.filename); } catch (Error e) { error_occurred("Could not complete recording", e.message); } @@ -138,11 +151,11 @@ class AudioProject : Project { } while (base_name != null); // Next, update the model so that the project file is saved properly - foreach (ClipFile clip_file in clipfiles) { - if (Path.get_dirname(clip_file.filename) == audio_path) { - string file_name = Path.get_basename(clip_file.filename); + foreach (MediaFile media_file in mediafiles) { + if (Path.get_dirname(media_file.filename) == audio_path) { + string file_name = Path.get_basename(media_file.filename); string destination = Path.build_filename(destination_path, file_name); - clip_file.filename = destination; + media_file.filename = destination; } } diff --git a/src/fillmore/fillmore.vala b/src/fillmore/fillmore.vala index dcce241..02510c5 100644 --- a/src/fillmore/fillmore.vala +++ b/src/fillmore/fillmore.vala @@ -10,16 +10,29 @@ extern const string _PROGRAM_NAME; bool do_print_graph = false; int debug_level; -const OptionEntry[] options = { - { "print-graph", 0, 0, OptionArg.NONE, &do_print_graph, - "Show Print Graph in help menu", null }, - { "debug-level", 0, 0, OptionArg.INT, &debug_level, +private OptionEntry[]? entries = null; + +public OptionEntry[] get_options() { + if (entries != null) + return entries; + + OptionEntry print_graph = { "print-graph", 0, 0, OptionArg.NONE, &do_print_graph, + "Show Save Graph in help menu. Must set environment variable GST_DEBUG_DUMP_DOT_DIR", + null }; + entries += print_graph; + + OptionEntry debug_level = { "debug-level", 0, 0, OptionArg.INT, &debug_level, "Control amount of diagnostic information", - "[0 (minimal),5 (maximum)]" }, - { null } -}; + "[0 (minimal),5 (maximum)]" }; + entries += debug_level; + + OptionEntry terminator = { null }; + entries += terminator; + + return entries; +} -class Recorder : Gtk.Window, TransportDelegate { +public class Recorder : Gtk.Window, TransportDelegate { public Model.AudioProject project; public TimeLine timeline; View.ClickTrack click_track; @@ -34,6 +47,7 @@ class Recorder : Gtk.Window, TransportDelegate { int64 center_time = -1; bool loading; const int scroll_speed = 8; + bool track_record_enabled = false; Gtk.ActionGroup main_group; @@ -44,58 +58,60 @@ class Recorder : Gtk.Window, TransportDelegate { View.OggVorbisExport audio_export; View.AudioOutput audio_output; Gee.ArrayList<string> load_errors; + bool invalid_project; public const string NAME = "Fillmore"; const Gtk.ActionEntry[] entries = { { "Project", null, "_Project", null, null, null }, - { "Open", Gtk.STOCK_OPEN, "_Open...", null, "Open a project", on_project_open }, - { "NewProject", Gtk.STOCK_NEW, "_New", null, "Create new project", on_project_new }, - { "Save", Gtk.STOCK_SAVE, "_Save", "<Control>S", "Save project", on_project_save }, - { "SaveAs", Gtk.STOCK_SAVE_AS, "Save _As...", "<Control><Shift>S", + { "Open", Gtk.Stock.OPEN, "_Open...", null, "Open a project", on_project_open }, + { "NewProject", Gtk.Stock.NEW, "_New", null, "Create new project", on_project_new }, + { "Save", Gtk.Stock.SAVE, "_Save", "<Control>S", "Save project", on_project_save }, + { "SaveAs", Gtk.Stock.SAVE_AS, "Save _As...", "<Control><Shift>S", "Save project with new name", on_project_save_as }, - { "Export", Gtk.STOCK_JUMP_TO, "_Export...", "<Control>E", null, on_export }, - { "Settings", Gtk.STOCK_PROPERTIES, "Se_ttings", "<Control><Alt>Return", null, on_properties }, - { "Quit", Gtk.STOCK_QUIT, null, null, null, on_quit }, + { "Export", Gtk.Stock.JUMP_TO, "_Export...", "<Control>E", null, on_export }, + { "Settings", Gtk.Stock.PROPERTIES, "Se_ttings", "<Control><Alt>Return", null, on_properties }, + { "Quit", Gtk.Stock.QUIT, null, null, null, on_quit }, { "Edit", null, "_Edit", null, null, null }, - { "Undo", Gtk.STOCK_UNDO, null, "<Control>Z", null, on_undo }, - { "Cut", Gtk.STOCK_CUT, null, null, null, on_cut }, - { "Copy", Gtk.STOCK_COPY, null, null, null, on_copy }, - { "Paste", Gtk.STOCK_PASTE, null, null, null, on_paste }, - { "Delete", Gtk.STOCK_DELETE, null, "Delete", null, on_delete }, - { "SelectAll", Gtk.STOCK_SELECT_ALL, null, "<Control>A", null, on_select_all }, + { "Undo", Gtk.Stock.UNDO, null, "<Control>Z", null, on_undo }, + { "Cut", Gtk.Stock.CUT, null, null, null, on_cut }, + { "Copy", Gtk.Stock.COPY, null, null, null, on_copy }, + { "Paste", Gtk.Stock.PASTE, null, null, null, on_paste }, + { "Delete", Gtk.Stock.DELETE, null, "Delete", null, on_delete }, + { "SelectAll", Gtk.Stock.SELECT_ALL, null, "<Control>A", null, on_select_all }, { "SplitAtPlayhead", null, "_Split at Playhead", "<Control>P", null, on_split_at_playhead }, { "TrimToPlayhead", null, "Trim to Play_head", "<Control>H", null, on_trim_to_playhead }, - { "ClipProperties", Gtk.STOCK_PROPERTIES, "Properti_es", "<Alt>Return", + { "ClipProperties", Gtk.Stock.PROPERTIES, "Properti_es", "<Alt>Return", null, on_clip_properties }, { "View", null, "_View", null, null, null }, - { "ZoomIn", Gtk.STOCK_ZOOM_IN, "Zoom _In", "<Control>plus", null, on_zoom_in }, - { "ZoomOut", Gtk.STOCK_ZOOM_OUT, "Zoom _Out", "<Control>minus", null, on_zoom_out }, + { "ZoomIn", Gtk.Stock.ZOOM_IN, "Zoom _In", "<Control>plus", null, on_zoom_in }, + { "ZoomOut", Gtk.Stock.ZOOM_OUT, "Zoom _Out", "<Control>minus", null, on_zoom_out }, { "ZoomProject", null, "Fit to _Window", "<Shift>Z", null, on_zoom_to_project }, { "Track", null, "_Track", null, null, null }, - { "NewTrack", Gtk.STOCK_ADD, "_New...", "<Control><Shift>N", + { "NewTrack", Gtk.Stock.ADD, "_New...", "<Control><Shift>N", "Create new track", on_track_new }, { "Rename", null, "_Rename...", null, "Rename Track", on_track_rename }, { "DeleteTrack", null, "_Delete", "<Control><Shift>Delete", "Delete track", on_track_remove }, { "Help", null, "_Help", null, null, null }, - { "Contents", Gtk.STOCK_HELP, "_Contents", "F1", + { "Contents", Gtk.Stock.HELP, "_Contents", "F1", "More information on Fillmore", on_help_contents}, - { "About", Gtk.STOCK_ABOUT, null, null, null, on_about }, + { "About", Gtk.Stock.ABOUT, null, null, null, on_about }, { "SaveGraph", null, "Save _Graph", null, "Save graph", on_save_graph }, - { "Rewind", Gtk.STOCK_MEDIA_PREVIOUS, "Rewind", "Home", "Go to beginning", on_rewind }, - { "End", Gtk.STOCK_MEDIA_NEXT, "End", "End", "Go to end", on_end } + { "Rewind", Gtk.Stock.MEDIA_PREVIOUS, "Rewind", "Home", "Go to beginning", on_rewind }, + { "End", Gtk.Stock.MEDIA_NEXT, "End", "End", "Go to end", on_end } }; const Gtk.ToggleActionEntry[] toggle_entries = { - { "Play", Gtk.STOCK_MEDIA_PLAY, null, "space", "Play", on_play }, - { "Record", Gtk.STOCK_MEDIA_RECORD, null, "r", "Record", on_record }, - { "Library", null, "_Library", "F9", null, on_view_library, true }, - { "Snap", null, "_Snap to Clip Edges", null, null, on_snap, false } + { "Play", Gtk.Stock.MEDIA_PLAY, null, "space", "Play", on_play }, + { "Record", Gtk.Stock.MEDIA_RECORD, null, "r", "Record", on_record }, + { "Library", null, "_Library", "F9", null, on_view_library, false }, + { "Snap", null, "_Snap to Clip Edges", null, null, on_snap, false }, + { "SnapGrid", null, "Snap to _Grid", null, null, on_snap_to_grid, false } }; const string ui = """ @@ -136,6 +152,7 @@ class Recorder : Gtk.Window, TransportDelegate { <menuitem name="ViewZoomProject" action="ZoomProject"/> <separator/> <menuitem name="Snap" action="Snap"/> + <menuitem name="SnapGrid" action="SnapGrid" /> </menu> <menu name="TrackMenu" action="Track"> <menuitem name="TrackNew" action="NewTrack"/> @@ -182,20 +199,19 @@ class Recorder : Gtk.Window, TransportDelegate { { "Ogg Files", "ogg" } }; - public signal void finished_closing(bool project_did_close); - public Recorder(string? project_file) throws Error { ClassFactory.set_transport_delegate(this); GLib.DirUtils.create(get_fillmore_directory(), 0777); load_errors = new Gee.ArrayList<string>(); try { - set_icon_from_file( + set_default_icon_from_file( AppDirs.get_resources_dir().get_child("fillmore_icon.png").get_path()); } catch (GLib.Error e) { warning("Could not load application icon: %s", e.message); } project = new Model.AudioProject(project_file); project.snap_to_clip = false; + project.snap_to_grid = false; provider = new Model.BarBeatTimeSystem(project); project.media_engine.callback_pulse.connect(on_callback_pulse); @@ -211,7 +227,7 @@ class Recorder : Gtk.Window, TransportDelegate { project.track_added.connect(on_track_added); project.track_removed.connect(on_track_removed); project.load_complete.connect(on_load_complete); - project.closed.connect(on_project_close); + project.query_closed.connect(on_project_query_close); audio_output = new View.AudioOutput(project.media_engine.get_project_audio_caps()); project.media_engine.connect_output(audio_output); @@ -271,9 +287,12 @@ class Recorder : Gtk.Window, TransportDelegate { timeline_library_pane.set_position(project.library_width); timeline_library_pane.add1(hbox); timeline_library_pane.child1_resize = 1; - timeline_library_pane.add2(library_scrolled); + timeline_library_pane.child1_shrink = 0; timeline_library_pane.child2_resize = 0; + timeline_library_pane.child2_shrink = 0; timeline_library_pane.child1.size_allocate.connect(on_library_size_allocate); + hbox.set_size_request(TrackHeader.width + 50, -1); + library_scrolled.set_size_request(50, -1); vbox.pack_start(timeline_library_pane, true, true, 0); add(vbox); @@ -288,8 +307,7 @@ class Recorder : Gtk.Window, TransportDelegate { add_accel_group(manager.get_accel_group()); timeline.grab_focus(); delete_event.connect(on_delete_event); - loading = true; - project.load(project_file); + start_load(project_file); if (project_file == null) { default_track_set(); loading = false; @@ -297,14 +315,20 @@ class Recorder : Gtk.Window, TransportDelegate { project.media_engine.pipeline.set_state(Gst.State.PAUSED); } + void start_load(string? project_file) { + loading = true; + invalid_project = false; + project.load(project_file); + } + void default_track_set() { project.add_track(new Model.AudioTrack(project, get_default_track_name())); project.tracks[0].set_selected(true); + project.library_visible = false; + show_library(); } - static int default_track_number_compare(void *a, void *b) { - string* s1 = (string *) a; - string* s2 = (string *) b; + static int default_track_number_compare(string* s1, string* s2) { int i = -1; int j = -1; s1->scanf("track %d", &i); @@ -327,7 +351,7 @@ class Recorder : Gtk.Window, TransportDelegate { default_track_names.append(track.display_name); } } - default_track_names.sort(default_track_number_compare); + default_track_names.sort((CompareFunc) default_track_number_compare); int i = 1; foreach(string s in default_track_names) { @@ -368,6 +392,10 @@ class Recorder : Gtk.Window, TransportDelegate { track.clip_added.connect(on_clip_added); track.clip_removed.connect(on_clip_removed); track.track_selection_changed.connect(on_track_selection_changed); + Model.AudioTrack audio_track = track as Model.AudioTrack; + if (audio_track != null) { + audio_track.record_enable_changed.connect(on_record_enable_changed); + } } void on_track_removed(Model.Track unused) { @@ -389,6 +417,11 @@ class Recorder : Gtk.Window, TransportDelegate { void on_track_selection_changed(Model.Track track) { if (track.get_is_selected()) { + Model.AudioTrack audio_track = track as Model.AudioTrack; + if (audio_track != null) { + audio_track.record_enable = true; + } + foreach (Model.Track t in project.tracks) { if (t != track) { t.set_selected(false); @@ -397,6 +430,22 @@ class Recorder : Gtk.Window, TransportDelegate { } } + void on_record_enable_changed(Model.AudioTrack audio_track) { + track_record_enabled = audio_track.record_enable; + if (!track_record_enabled) { + foreach (Model.Track track in project.tracks) { + Model.AudioTrack another_track = track as Model.AudioTrack; + if (another_track != null) { + if (another_track.record_enable) { + track_record_enabled = true; + break; + } + } + } + } + set_sensitive_group(main_group, "Record", track_record_enabled); + } + void on_clip_moved(Model.Clip clip) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_moved"); update_menu(); @@ -431,10 +480,12 @@ class Recorder : Gtk.Window, TransportDelegate { // Edit menu set_sensitive_group(main_group, "Undo", is_stopped && project.undo_manager.can_undo); - set_sensitive_group(main_group, "Copy", is_stopped && selected); - set_sensitive_group(main_group, "Cut", is_stopped && selected); - set_sensitive_group(main_group, "Paste", timeline.clipboard.clips.size != 0 && is_stopped); - set_sensitive_group(main_group, "Delete", (selected || library_selected) && is_stopped); + set_sensitive_group(main_group, "Copy", is_stopped && selected && number_of_tracks > 0); + set_sensitive_group(main_group, "Cut", is_stopped && selected && number_of_tracks > 0); + set_sensitive_group(main_group, "Paste", timeline.clipboard.clips.size != 0 && is_stopped && + number_of_tracks > 0); + set_sensitive_group(main_group, "Delete", (selected || library_selected) && is_stopped && + number_of_tracks > 0); set_sensitive_group(main_group, "SplitAtPlayhead", selected && playhead_on_clip && is_stopped); set_sensitive_group(main_group, "TrimToPlayhead", @@ -450,8 +501,9 @@ class Recorder : Gtk.Window, TransportDelegate { set_sensitive_group(main_group, "NewTrack", is_stopped); // toolbar - set_sensitive_group(main_group, "Play", true); - set_sensitive_group(main_group, "Record", number_of_tracks > 0 && is_stopped); + set_sensitive_group(main_group, "Play", !project.transport_is_recording()); + set_sensitive_group(main_group, "Record", number_of_tracks > 0 && track_record_enabled && + (!project.transport_is_playing() || project.transport_is_recording())); } public Model.Track? selected_track() { @@ -460,7 +512,7 @@ class Recorder : Gtk.Window, TransportDelegate { return track; } } - error("can't find selected track"); + warning("can't find selected track"); return null; } @@ -541,7 +593,9 @@ class Recorder : Gtk.Window, TransportDelegate { if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0) { project.go_previous(); } else { - project.media_engine.go(project.transport_get_position() - Gst.SECOND); + int64 position = project.transport_get_position(); + int64 previous_time = provider.previous_tick(position); + project.media_engine.go(previous_time); } page_to_time(project.transport_get_position()); break; @@ -552,7 +606,9 @@ class Recorder : Gtk.Window, TransportDelegate { if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0) { project.go_next(); } else { - project.media_engine.go(project.transport_get_position() + Gst.SECOND); + int64 position = project.transport_get_position(); + int64 next_time = provider.next_tick(position); + project.media_engine.go(next_time); } page_to_time(project.transport_get_position()); break; @@ -603,9 +659,8 @@ class Recorder : Gtk.Window, TransportDelegate { void on_project_new_finished_closing(bool project_did_close) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_project_new_finished_closing"); - project.closed.disconnect(on_project_close); - finished_closing.disconnect(on_project_new_finished_closing); if (project_did_close) { + project.closed.disconnect(on_project_new_finished_closing); project.media_engine.set_play_state(PlayState.LOADING); project.load(null); default_track_set(); @@ -616,14 +671,12 @@ class Recorder : Gtk.Window, TransportDelegate { void on_project_new() { load_errors.clear(); - project.closed.connect(on_project_close); - finished_closing.connect(on_project_new_finished_closing); + project.closed.connect(on_project_new_finished_closing); project.close(); } void on_project_open_finished_closing(bool project_did_close) { - project.closed.disconnect(on_project_close); - finished_closing.disconnect(on_project_open_finished_closing); + project.closed.disconnect(on_project_open_finished_closing); if (project_did_close) { GLib.SList<string> filenames; if (DialogUtils.open(this, filters, false, false, out filenames)) { @@ -635,8 +688,7 @@ class Recorder : Gtk.Window, TransportDelegate { void on_project_open() { load_errors.clear(); - project.closed.connect(on_project_close); - finished_closing.connect(on_project_open_finished_closing); + project.closed.connect(on_project_open_finished_closing); project.close(); } @@ -649,8 +701,7 @@ class Recorder : Gtk.Window, TransportDelegate { } void on_save_new_file_finished_closing(bool did_close) { - project.closed.disconnect(on_project_close); - finished_closing.disconnect(on_save_new_file_finished_closing); + project.closed.disconnect(on_save_new_file_finished_closing); project.load(project.get_project_file()); } @@ -672,8 +723,7 @@ class Recorder : Gtk.Window, TransportDelegate { if (DialogUtils.save(this, "Save Project", create_directory, filters, ref filename)) { project.save(filename); if (saving_new_file && project.get_project_file() != null) { - project.closed.connect(on_project_close); - finished_closing.connect(on_save_new_file_finished_closing); + project.closed.connect(on_save_new_file_finished_closing); project.close(); } return true; @@ -707,8 +757,7 @@ class Recorder : Gtk.Window, TransportDelegate { } void on_quit_finished_closing(bool project_did_close) { - project.closed.disconnect(on_project_close); - finished_closing.disconnect(on_quit_finished_closing); + project.closed.disconnect(on_quit_finished_closing); if (project_did_close) { Gtk.main_quit(); } @@ -716,8 +765,7 @@ class Recorder : Gtk.Window, TransportDelegate { void on_quit() { if (!project.transport_is_recording()) { - project.closed.connect(on_project_close); - finished_closing.connect(on_quit_finished_closing); + project.closed.connect(on_quit_finished_closing); project.close(); } } @@ -728,13 +776,18 @@ class Recorder : Gtk.Window, TransportDelegate { return true; } - void on_project_close() { - emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_project_close"); + void on_project_query_close(ref bool should_close) { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_project_query_close"); + + if (!should_close) { + return; + } + if (project.undo_manager.is_dirty) { switch(DialogUtils.save_close_cancel(this, null, "Save changes before closing?")) { case Gtk.ResponseType.ACCEPT: if (!do_save()) { - finished_closing(false); + should_close = false; return; } break; @@ -746,14 +799,14 @@ class Recorder : Gtk.Window, TransportDelegate { break; case Gtk.ResponseType.DELETE_EVENT: // when user presses escape. case Gtk.ResponseType.CANCEL: - finished_closing(false); + should_close = false; return; default: assert(false); break; } } - finished_closing(true); + should_close = true; } // Edit menu @@ -802,8 +855,8 @@ class Recorder : Gtk.Window, TransportDelegate { Gee.ArrayList<string> files = library.get_selected_files(); if (files.size == 1) { string file_name = files.get(0); - Model.ClipFile? clip_file = project.find_clipfile(file_name); - DialogUtils.show_clip_properties(this, null, clip_file, null); + Model.MediaFile? media_file = project.find_mediafile(file_name); + DialogUtils.show_clip_properties(this, null, media_file, null); } } else { Gee.ArrayList<ClipView> clips = timeline.selected_clips; @@ -880,15 +933,27 @@ class Recorder : Gtk.Window, TransportDelegate { project.snap_to_clip = !project.snap_to_clip; } + void on_snap_to_grid() { + project.snap_to_grid = !project.snap_to_grid; + } + + void show_library() { + if (!project.library_visible && timeline_library_pane.child2 == library_scrolled) { + timeline_library_pane.remove(library_scrolled); + } + + if (project.library_visible && timeline_library_pane.child2 != library_scrolled) { + timeline_library_pane.add2(library_scrolled); + timeline_library_pane.show_all(); + } + } void on_view_library() { if (timeline_library_pane.child2 == library_scrolled) { - timeline_library_pane.remove(library_scrolled); project.library_visible = false; } else { - timeline_library_pane.add2(library_scrolled); - timeline_library_pane.show_all(); project.library_visible = true; } + show_library(); } void on_library_size_allocate(Gdk.Rectangle rectangle) { @@ -935,31 +1000,53 @@ class Recorder : Gtk.Window, TransportDelegate { } void on_play() { + if (play_button.get_active()) { + project.media_engine.do_play(PlayState.PLAYING); + } else { + project.media_engine.pause(); + } + } + + void on_record() { if (project.transport_is_recording()) { set_sensitive_group(main_group, "Record", true); record_button.set_active(false); play_button.set_active(false); project.media_engine.pause(); - } else if (play_button.get_active()) - project.media_engine.do_play(PlayState.PLAYING); - else - project.media_engine.pause(); - } + } else { + Model.AudioTrack audio_track = null; + if (record_button.get_active()) { + foreach (Model.Track track in project.tracks) { + audio_track = track as Model.AudioTrack; + if (audio_track.record_enable) { + break; + } + } - void on_record() { - if (record_button.get_active()) { - Model.AudioTrack audio_track = selected_track() as Model.AudioTrack; - int number_of_channels; - if (audio_track.get_num_channels(out number_of_channels)) { - if (number_of_channels == 2) { + Gee.ArrayList<View.InputSource> names = + View.InputSources.get_input_selections("alsasrc"); + if (names.size == 0) { + on_error_occurred("Cannot Record", "No input devices are available. " + + "Please connect an input device"); record_button.set_active(false); - on_error_occurred("Can not record onto a stereo track", null); return; } + + int number_of_channels; + if (audio_track.get_num_channels(out number_of_channels)) { + int source_channels = + View.InputSources.get_number_of_channels("alsasrc", audio_track.device); + if (source_channels != -1 && source_channels != number_of_channels) { + on_error_occurred("Unable to record", "Please select an input source"); + record_button.set_active(false); + return; + } + } + + set_sensitive_group(main_group, "Record", false); + set_sensitive_group(main_group, "Play", false); + project.record(audio_track); } - set_sensitive_group(main_group, "Record", false); - set_sensitive_group(main_group, "Play", false); - project.record(audio_track); } } @@ -1012,7 +1099,7 @@ class Recorder : Gtk.Window, TransportDelegate { debug_level = -1; OptionContext context = new OptionContext( " [project file] - Record and edit multitrack audio"); - context.add_main_entries(options, null); + context.add_main_entries(get_options(), null); context.add_group(Gst.init_get_option_group()); try { @@ -1043,7 +1130,7 @@ class Recorder : Gtk.Window, TransportDelegate { } catch (GLib.Error e) { } } - ClassFactory.set_class_factory(new FillmoreClassFactory()); + ClassFactory.set_class_factory(new ClassFactory()); View.MediaEngine.can_run(); Recorder recorder = new Recorder(project_file); @@ -1058,45 +1145,61 @@ class Recorder : Gtk.Window, TransportDelegate { DialogUtils.error(major_message, minor_message); } - public void on_load_error(string message) { + public void on_load_error(Model.ErrorClass error_class, string message) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_load_error"); + if (error_class == Model.ErrorClass.LoadFailure) { + invalid_project = true; + } load_errors.add(message); } + void display_errors() { + if (load_errors.size > 0) { + string message = ""; + foreach (string s in load_errors) { + message = message + s + "\n"; + } + do_error_dialog("An error occurred loading the project.", message); + } + } + public void on_load_complete() { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_load_complete"); - project.media_engine.pipeline.set_state(Gst.State.PAUSED); - timeline_library_pane.set_position(project.library_width); - - Gtk.ToggleAction action = main_group.get_action("Library") as Gtk.ToggleAction; - if (action.get_active() != project.library_visible) { - action.set_active(project.library_visible); - } + if (!invalid_project) { + project.media_engine.pipeline.set_state(Gst.State.PAUSED); + timeline_library_pane.set_position(project.library_width); - action = main_group.get_action("Snap") as Gtk.ToggleAction; - if (action.get_active() != project.snap_to_clip) { - action.set_active(project.snap_to_clip); - } + Gtk.ToggleAction action = main_group.get_action("Library") as Gtk.ToggleAction; + if (action.get_active() != project.library_visible) { + action.set_active(project.library_visible); + } - if (project.library_visible) { - if (timeline_library_pane.child2 != library_scrolled) { - timeline_library_pane.add2(library_scrolled); + action = main_group.get_action("Snap") as Gtk.ToggleAction; + if (action.get_active() != project.snap_to_clip) { + action.set_active(project.snap_to_clip); } - } else { - if (timeline_library_pane.child2 == library_scrolled) { - timeline_library_pane.remove(library_scrolled); + + action = main_group.get_action("SnapGrid") as Gtk.ToggleAction; + if (action.get_active() != project.snap_to_grid) { + action.set_active(project.snap_to_grid); } - } - if (load_errors.size > 0) { - string message = ""; - foreach (string s in load_errors) { - message = message + s + "\n"; + if (project.library_visible) { + if (timeline_library_pane.child2 != library_scrolled) { + timeline_library_pane.add2(library_scrolled); + } + } else { + if (timeline_library_pane.child2 == library_scrolled) { + timeline_library_pane.remove(library_scrolled); + } } - do_error_dialog("An error occurred loading the project.", message); + display_errors(); + loading = false; + } else { + display_errors(); + start_load(null); + default_track_set(); } - - loading = false; } void on_name_changed() { diff --git a/src/fillmore/header_area.vala b/src/fillmore/header_area.vala index 3e20519..f1c34f1 100644 --- a/src/fillmore/header_area.vala +++ b/src/fillmore/header_area.vala @@ -6,48 +6,57 @@ using Logging; -class TrackHeader : Gtk.EventBox { +class TrackSeparator : Gtk.HSeparator { +//this class is referenced in the resource file +} + +public class TrackHeader : Gtk.EventBox { protected weak Model.Track track; protected weak HeaderArea header_area; protected Gtk.Label track_label; - - public const int width = 100; - - public TrackHeader(Model.Track track, HeaderArea area, int height) { + + public const int width = 250; + + public virtual void setup(Gtk.Builder builder, Model.Track track, HeaderArea area, int height) { this.track = track; this.header_area = area; - + track.track_renamed.connect(on_track_renamed); track.track_selection_changed.connect(on_track_selection_changed); set_size_request(width, height); - modify_bg(Gtk.StateType.NORMAL, header_area.background_color); - modify_bg(Gtk.StateType.SELECTED, parse_color("#68a")); - - track_label = new Gtk.Label(track.display_name); + + track_label = (Gtk.Label) builder.get_object("track_label"); + track_label.set_text(track.display_name); track_label.modify_fg(Gtk.StateType.NORMAL, parse_color("#fff")); } - + void on_track_renamed() { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_track_renamed"); track_label.set_text(track.display_name); } - + void on_track_selection_changed(Model.Track track) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_track_selection_changed"); - set_state(track.get_is_selected() ? Gtk.StateType.SELECTED : Gtk.StateType.NORMAL); + if (track.get_is_selected()) { + modify_bg(Gtk.StateType.NORMAL, parse_color("#68A")); + track_label.modify_fg(Gtk.StateType.NORMAL, parse_color("#FFF")); + } else { + modify_bg(Gtk.StateType.NORMAL, parse_color("#666")); + track_label.modify_fg(Gtk.StateType.NORMAL, parse_color("#222")); + } } - + public override bool button_press_event(Gdk.EventButton event) { header_area.select(track); return true; } - + public Model.Track get_track() { return track; } } -public class SliderBase : Gtk.HScrollbar { +public abstract class SliderBase : Gtk.HScrollbar { Gdk.Pixbuf slider_image; construct { can_focus = true; @@ -58,7 +67,9 @@ public class SliderBase : Gtk.HScrollbar { warning("Could not load resource for slider: %s", e.message); } } - + + protected abstract void control_click(); + public override bool expose_event (Gdk.EventExpose event) { Gdk.GC gc = style.fg_gc[(int) Gtk.StateType.NORMAL]; int radius = (slider_end - slider_start) / 2; @@ -72,57 +83,202 @@ public class SliderBase : Gtk.HScrollbar { slider_image.get_width(), slider_image.get_height(), Gdk.RgbDither.NORMAL, 0, 0); return true; } + + public override bool button_press_event (Gdk.EventButton event) { + if (event.button == 1 && (event.state & Gdk.ModifierType.CONTROL_MASK) != 0) { + control_click(); + return true; + } else { + return base.button_press_event(event); + } + } } -class PanSlider : SliderBase { +public class PanSlider : SliderBase { construct { } + + protected override void control_click() { + // control click centers panorama + set_value(0); + } } public class VolumeSlider : SliderBase { construct { } + + protected override void control_click() { + // control click sets to unity gain + set_value(1); + } } -class AudioTrackHeader : TrackHeader { +class InputMenuItem : Gtk.RadioMenuItem { + public string device_name; + + public InputMenuItem(GLib.SList<Gtk.RadioMenuItem> group, + string nice_name, string? device_name) { + if (device_name != null) { + set_label("%s (%s)".printf(nice_name, device_name)); + } else { + set_label(nice_name); + } + this.device_name = device_name; + } +} + +public class AudioTrackHeader : TrackHeader { public PanSlider pan; public VolumeSlider volume; - - public AudioTrackHeader(Model.AudioTrack track, HeaderArea header, int height) { - base(track, header, height); - Gtk.HBox pan_box = new Gtk.HBox(false, 0); - pan_box.pack_start(new Gtk.Label(" L"), false, false, 0); - pan = new PanSlider(); - pan.set_adjustment(new Gtk.Adjustment(track.get_pan(), -1, 1, 0.1, 0.1, 0.0)); - pan.value_changed.connect(on_pan_value_changed); - pan_box.pack_start(pan, true, true, 1); - pan_box.pack_start(new Gtk.Label("R "), false, false, 0); - - Gtk.HBox volume_box = new Gtk.HBox(false, 0); - Gtk.Image min_speaker = new Gtk.Image.from_file( - AppDirs.get_resources_dir().get_child("min_speaker.png").get_path()); - volume_box.pack_start(min_speaker, false, false, 0); - volume = new VolumeSlider(); - volume.set_adjustment(new Gtk.Adjustment(track.get_volume(), 0, 1.5, 0.01, 1, 0)); - volume.value_changed.connect(on_volume_value_changed); - volume_box.pack_start(volume, true, true, 0); - Gtk.Image max_speaker = new Gtk.Image.from_file( - AppDirs.get_resources_dir().get_child("max_speaker.png").get_path()); - volume_box.pack_start(max_speaker, false, false, 0); - - track.parameter_changed.connect(on_parameter_changed); - - Gtk.VBox vbox = new Gtk.VBox(false, 0); - vbox.pack_start(track_label, true, true, 0); - View.AudioMeter meter = new View.AudioMeter(track); - vbox.add(meter); - - vbox.add(volume_box); - vbox.add(pan_box); - add(vbox); + Gtk.ToggleButton mute; + Gtk.ToggleButton solo; + Gtk.ToggleButton record_enable; + Gtk.Button input_select; + + public override void setup(Gtk.Builder builder, Model.Track track, + HeaderArea header, int height) { + base.setup(builder, track, header, height); + Model.AudioTrack audio_track = track as Model.AudioTrack; + View.AudioMeter audio_meter = (View.AudioMeter) builder.get_object("audiometer1"); + + input_select = (Gtk.Button) builder.get_object("input"); + + // We set the property name so the style can be applied. You can't do this in + // glade. There is a bug against glade/gtkbuilder already + // https://bugzilla.gnome.org/show_bug.cgi?id=591076 + mute = (Gtk.ToggleButton) builder.get_object("mute"); + mute.set("name", "mute"); + + solo = (Gtk.ToggleButton) builder.get_object("solo"); + solo.set("name", "solo"); + + record_enable = (Gtk.ToggleButton) builder.get_object("record_enable"); + record_enable.set("name", "record_enable"); + + pan = (PanSlider) builder.get_object("track_pan"); + + volume = (VolumeSlider) builder.get_object("track_volume"); + volume.get_adjustment().set_value(audio_track.get_volume()); + pan.get_adjustment().set_value(audio_track.get_pan()); + audio_meter.setup(audio_track); + audio_track.parameter_changed.connect(on_parameter_changed); + audio_track.indirect_mute_changed.connect(on_indirect_mute_changed); + audio_track.mute_changed.connect(on_mute_changed); + audio_track.solo_changed.connect(on_solo_changed); + audio_track.record_enable_changed.connect(on_record_enable_changed); + } + + public void on_mute_toggled(Gtk.ToggleButton button) { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_mute_toggled"); + Model.AudioTrack audio_track = track as Model.AudioTrack; + audio_track.mute = button.active; + if (audio_track.mute) { + audio_track.solo = false; + } } - void on_pan_value_changed() { + public void on_solo_toggled(Gtk.ToggleButton button) { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_solo_toggled"); + Model.AudioTrack audio_track = track as Model.AudioTrack; + audio_track.solo = button.active; + } + + public void on_record_enable_toggled(Gtk.ToggleButton button) { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_record_enable_toggled"); + Model.AudioTrack audio_track = track as Model.AudioTrack; + audio_track.record_enable = button.active; + } + + public void on_input_clicked() { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_input_clicked"); + Gee.ArrayList<View.InputSource> names = View.InputSources.get_input_selections("alsasrc"); + Model.AudioTrack audio_track = track as Model.AudioTrack; + int number_of_channels = -1; + audio_track.get_num_channels(out number_of_channels); + + int names_length = names.size; + if (names_length > 0) { + Gtk.Menu menu = new Gtk.Menu(); + unowned GLib.SList<Gtk.RadioMenuItem> group = null; + + InputMenuItem item = new InputMenuItem(group, "Default", null); + group = item.get_group(); + item.activate.connect(on_input_selected); + item.set_active(audio_track.device == null); + menu.append(item); + + for (int i = 0; i < names_length; ++i) { + View.InputSource input_source = names.get(i); + item = new InputMenuItem(group, + input_source.friendly_name, input_source.device); + item.set_sensitive(number_of_channels == -1 || + input_source.number_of_channels == number_of_channels); + + item.set_active(audio_track.device == input_source.device); + menu.append(item); + item.activate.connect(on_input_selected); + } + menu.attach_to_widget(input_select, null); + menu.show_all(); + menu.popup(null, null, menu_position_function, 0, 0); + } + } + + void menu_position_function(Gtk.Menu menu, out int x, out int y, out bool push_in) { + menu.attach_widget.window.get_origin(out x, out y); + x += menu.attach_widget.allocation.x; + y += menu.attach_widget.allocation.y + menu.attach_widget.allocation.height; + push_in = true; + } + + void on_input_selected(Gtk.MenuItem item) { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_input_selected"); + InputMenuItem input_item = item as InputMenuItem; + + Model.AudioTrack audio_track = track as Model.AudioTrack; + audio_track.device = input_item.device_name; + } + + void on_indirect_mute_changed() { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_indirect_mute_changed"); + Model.AudioTrack audio_track = track as Model.AudioTrack; + if (audio_track != null) { + mute.set_sensitive(!audio_track.indirect_mute); + } + } + + void on_mute_changed() { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_indirect_mute_changed"); + Model.AudioTrack audio_track = track as Model.AudioTrack; + if (audio_track != null) { + if (audio_track.mute != mute.active) { + mute.set_active(audio_track.mute); + } + } + } + + void on_solo_changed() { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_indirect_mute_changed"); + Model.AudioTrack audio_track = track as Model.AudioTrack; + if (audio_track != null) { + if (audio_track.solo != solo.active) { + solo.set_active(audio_track.solo); + } + } + } + + void on_record_enable_changed() { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_record_enable_changed"); + Model.AudioTrack audio_track = track as Model.AudioTrack; + if (audio_track != null) { + if (audio_track.record_enable != record_enable.active) { + record_enable.set_active(audio_track.record_enable); + } + } + } + + public void on_pan_value_changed() { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_pan_value_changed"); Model.AudioTrack audio_track = track as Model.AudioTrack; if (audio_track != null) { @@ -130,8 +286,8 @@ class AudioTrackHeader : TrackHeader { audio_track.set_pan(adjustment.get_value()); } } - - void on_volume_value_changed() { + + public void on_volume_value_changed() { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_volume_value_changed"); Model.AudioTrack audio_track = track as Model.AudioTrack; if (audio_track != null) { @@ -155,7 +311,7 @@ class AudioTrackHeader : TrackHeader { } } -class HeaderArea : Gtk.EventBox { +public class HeaderArea : Gtk.EventBox { weak Model.Project project; Gtk.VBox vbox; @@ -189,7 +345,18 @@ class HeaderArea : Gtk.EventBox { //we are currently only supporting audio tracks. We'll probably have //a separate method for adding video track, midi track, aux input, etc - TrackHeader header = new AudioTrackHeader(audio_track, this, trackview.get_track_height()); + Gtk.Builder builder = new Gtk.Builder(); + try { + builder.add_from_file(AppDirs.get_resources_dir().get_child("fillmore.glade").get_path()); + } catch(GLib.Error e) { + warning("%s\n", e.message); + return; + } + builder.connect_signals(null); + AudioTrackHeader header = (AudioTrackHeader) builder.get_object("HeaderArea"); + header.setup(builder, audio_track, this, trackview.get_track_height() - 2); + // - 2 allows room for TrackSeparator + vbox.pack_start(header, false, false, 0); vbox.pack_start(new TrackSeparator(), false, false, 0); vbox.show_all(); diff --git a/src/fillmore/sources.mk b/src/fillmore/sources.mk index d87665b..c790662 100644 --- a/src/fillmore/sources.mk +++ b/src/fillmore/sources.mk @@ -1,7 +1,6 @@ $(SRC_PREFIX)SRC_FILES = \ audio_project.vala \ fillmore.vala \ - FillmoreClassFactory.vala \ header_area.vala \ ProjectProperties.vala \ trackinformation.vala diff --git a/src/fillmore/trackinformation.vala b/src/fillmore/trackinformation.vala index 7792f45..6aeff14 100644 --- a/src/fillmore/trackinformation.vala +++ b/src/fillmore/trackinformation.vala @@ -12,8 +12,8 @@ namespace UI { construct { set_title("New Track"); set_modal(true); - add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, - Gtk.STOCK_OK, Gtk.ResponseType.OK, + add_buttons(Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL, + Gtk.Stock.OK, Gtk.ResponseType.OK, null); Gtk.Label label = new Gtk.Label("Track name:"); entry = new Gtk.Entry(); diff --git a/src/lombard/lombard.vala b/src/lombard/lombard.vala index ba1bf44..7e2bc79 100644 --- a/src/lombard/lombard.vala +++ b/src/lombard/lombard.vala @@ -7,12 +7,29 @@ using Logging; int debug_level; -const OptionEntry[] options = { - { "debug-level", 0, 0, OptionArg.INT, &debug_level, +bool do_print_graph = false; + +private OptionEntry[]? entries = null; + +public OptionEntry[] get_options() { + if (entries != null) + return entries; + + OptionEntry print_graph = { "print-graph", 0, 0, OptionArg.NONE, &do_print_graph, + "Show Save Graph in help menu. Must set environment variable GST_DEBUG_DUMP_DOT_DIR", + null }; + entries += print_graph; + + OptionEntry debug_level = { "debug-level", 0, 0, OptionArg.INT, &debug_level, "Control amount of diagnostic information", - "[0 (minimal),5 (maximum)]" }, - { null } -}; + "[0 (minimal),5 (maximum)]" }; + entries += debug_level; + + OptionEntry terminator = { null }; + entries += terminator; + + return entries; +} class App : Gtk.Window, TransportDelegate { Gtk.DrawingArea drawing_area; @@ -52,44 +69,45 @@ class App : Gtk.Window, TransportDelegate { const Gtk.ActionEntry[] entries = { { "Project", null, "_Project", null, null, null }, - { "Open", Gtk.STOCK_OPEN, "_Open...", null, null, on_open }, - { "Save", Gtk.STOCK_SAVE, null, null, null, on_save }, - { "SaveAs", Gtk.STOCK_SAVE_AS, "Save _As...", "<Shift><Control>S", null, on_save_as }, - { "Play", Gtk.STOCK_MEDIA_PLAY, "_Play / Pause", "space", null, on_play_pause }, + { "Open", Gtk.Stock.OPEN, "_Open...", null, null, on_open }, + { "Save", Gtk.Stock.SAVE, null, null, null, on_save }, + { "SaveAs", Gtk.Stock.SAVE_AS, "Save _As...", "<Shift><Control>S", null, on_save_as }, + { "Play", Gtk.Stock.MEDIA_PLAY, "_Play / Pause", "space", null, on_play_pause }, { "Export", null, "_Export...", "<Control>E", null, on_export }, - { "Quit", Gtk.STOCK_QUIT, null, null, null, on_quit }, + { "Quit", Gtk.Stock.QUIT, null, null, null, on_quit }, { "Edit", null, "_Edit", null, null, null }, - { "Undo", Gtk.STOCK_UNDO, null, "<Control>Z", null, on_undo }, - { "Cut", Gtk.STOCK_CUT, null, null, null, on_cut }, - { "Copy", Gtk.STOCK_COPY, null, null, null, on_copy }, - { "Paste", Gtk.STOCK_PASTE, null, null, null, on_paste }, - { "Delete", Gtk.STOCK_DELETE, null, "Delete", null, on_delete }, - { "SelectAll", Gtk.STOCK_SELECT_ALL, null, "<Control>A", null, on_select_all }, + { "Undo", Gtk.Stock.UNDO, null, "<Control>Z", null, on_undo }, + { "Cut", Gtk.Stock.CUT, null, null, null, on_cut }, + { "Copy", Gtk.Stock.COPY, null, null, null, on_copy }, + { "Paste", Gtk.Stock.PASTE, null, null, null, on_paste }, + { "Delete", Gtk.Stock.DELETE, null, "Delete", null, on_delete }, + { "SelectAll", Gtk.Stock.SELECT_ALL, null, "<Control>A", null, on_select_all }, { "SplitAtPlayhead", null, "_Split at Playhead", "<Control>P", null, on_split_at_playhead }, { "TrimToPlayhead", null, "Trim to Play_head", "<Control>H", null, on_trim_to_playhead }, - { "ClipProperties", Gtk.STOCK_PROPERTIES, "Properti_es", "<Alt>Return", + { "ClipProperties", Gtk.Stock.PROPERTIES, "Properti_es", "<Alt>Return", null, on_clip_properties }, { "View", null, "_View", null, null, null }, - { "ZoomIn", Gtk.STOCK_ZOOM_IN, "Zoom _In", "<Control>plus", null, on_zoom_in }, - { "ZoomOut", Gtk.STOCK_ZOOM_OUT, "Zoom _Out", "<Control>minus", null, on_zoom_out }, + { "ZoomIn", Gtk.Stock.ZOOM_IN, "Zoom _In", "<Control>plus", null, on_zoom_in }, + { "ZoomOut", Gtk.Stock.ZOOM_OUT, "Zoom _Out", "<Control>minus", null, on_zoom_out }, { "ZoomProject", null, "Fit to _Window", "<Shift>Z", null, on_zoom_to_project }, { "Go", null, "_Go", null, null, null }, - { "Start", Gtk.STOCK_GOTO_FIRST, "_Start", "Home", null, on_go_start }, - { "End", Gtk.STOCK_GOTO_LAST, "_End", "End", null, on_go_end }, + { "Start", Gtk.Stock.GOTO_FIRST, "_Start", "Home", null, on_go_start }, + { "End", Gtk.Stock.GOTO_LAST, "_End", "End", null, on_go_end }, { "Help", null, "_Help", null, null, null }, - { "Contents", Gtk.STOCK_HELP, "_Contents", "F1", + { "Contents", Gtk.Stock.HELP, "_Contents", "F1", "More information on Lombard", on_help_contents}, - { "About", Gtk.STOCK_ABOUT, null, null, null, on_about }, + { "About", Gtk.Stock.ABOUT, null, null, null, on_about }, { "SaveGraph", null, "Save _Graph", null, "Save graph", on_save_graph } }; const Gtk.ToggleActionEntry[] check_actions = { { LibraryToggle, null, "_Library", "F9", null, on_view_library, true }, - { "Snap", null, "_Snap to Clip Edges", null, null, on_snap, true } + { "Snap", null, "_Snap to Clip Edges", null, null, on_snap, true }, + { "SnapGrid", null, "Snap to _Grid", null, null, on_snap_grid, false } }; const string ui = """ @@ -128,6 +146,7 @@ class App : Gtk.Window, TransportDelegate { <menuitem name="ViewZoomProject" action="ZoomProject"/> <separator/> <menuitem name="Snap" action="Snap"/> + <menuitem name="SnapGrid" action="SnapGrid"/> </menu> <menu name="GoMenu" action="Go"> <menuitem name="GoStart" action="Start"/> @@ -164,7 +183,7 @@ class App : Gtk.Window, TransportDelegate { public App(string? project_file) throws Error { try { - set_icon_from_file( + set_default_icon_from_file( AppDirs.get_resources_dir().get_child("lombard_icon.png").get_path()); } catch (GLib.Error e) { warning("Could not load application icon: %s", e.message); @@ -196,6 +215,7 @@ class App : Gtk.Window, TransportDelegate { project = new Model.VideoProject(project_filename); project.snap_to_clip = true; + project.snap_to_grid = false; project.name_changed.connect(set_project_name); project.load_error.connect(on_load_error); project.load_complete.connect(on_load_complete); @@ -236,7 +256,7 @@ class App : Gtk.Window, TransportDelegate { // TODO: only destroy it if --debug is not specified on the command line // or conversely, only add it if --debug is specified on the command line - if (save_graph != null) { + if (!do_print_graph && save_graph != null) { save_graph.destroy(); } @@ -298,8 +318,6 @@ class App : Gtk.Window, TransportDelegate { h_pane = new Gtk.HPaned(); h_pane.set_position(300); - h_pane.child2_resize = 1; - h_pane.child1_resize = 0; if (showing) { h_pane.add1(library_scrolled); @@ -321,6 +339,8 @@ class App : Gtk.Window, TransportDelegate { v_pane.child1_resize = 1; v_pane.child2_resize = 0; + v_pane.child1_shrink = 0; + v_pane.child2_shrink = 0; h_adjustment = timeline_scrolled.get_hadjustment(); h_adjustment.changed.connect(on_adjustment_changed); @@ -338,6 +358,13 @@ class App : Gtk.Window, TransportDelegate { h_pane.remove(library_scrolled); } } + h_pane.child2_resize = 1; + h_pane.child1_resize = 0; + h_pane.child1_shrink = 0; + h_pane.child2_shrink = 0; + library_scrolled.set_size_request(50, 50); + drawing_area.set_size_request(50, 50); + h_pane.set_size_request(50, 50); show_all(); } @@ -377,7 +404,7 @@ class App : Gtk.Window, TransportDelegate { DialogUtils.error(message, minor_message); } - public void on_load_error(string message) { + public void on_load_error(Model.ErrorClass error_class, string message) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_load_error"); load_errors.add(message); } @@ -401,6 +428,11 @@ class App : Gtk.Window, TransportDelegate { action.set_active(project.snap_to_clip); } + action = main_group.get_action("SnapGrid") as Gtk.ToggleAction; + if (action.get_active() != project.snap_to_grid) { + action.set_active(project.snap_to_grid); + } + if (project.library_visible) { if (h_pane.child1 != library_scrolled) { h_pane.add1(library_scrolled); @@ -547,8 +579,8 @@ class App : Gtk.Window, TransportDelegate { Gee.ArrayList<string> files = library.get_selected_files(); if (files.size == 1) { string file_name = files.get(0); - Model.ClipFile? clip_file = project.find_clipfile(file_name); - DialogUtils.show_clip_properties(this, null, clip_file, frames_per_second); + Model.MediaFile? media_file = project.find_mediafile(file_name); + DialogUtils.show_clip_properties(this, null, media_file, frames_per_second); } } else { Gee.ArrayList<ClipView> clips = timeline.selected_clips; @@ -682,6 +714,10 @@ class App : Gtk.Window, TransportDelegate { project.snap_to_clip = !project.snap_to_clip; } + void on_snap_grid() { + project.snap_to_grid = !project.snap_to_grid; + } + void on_view_library() { Gtk.ToggleAction action = main_group.get_action(LibraryToggle) as Gtk.ToggleAction; toggle_library(action.get_active()); @@ -823,7 +859,7 @@ class App : Gtk.Window, TransportDelegate { void on_help_contents() { try { - Gtk.show_uri(null, "http://trac.yorba.org/wiki/UsingLombard0.1", 0); + Gtk.show_uri(null, "http://trac.yorba.org/wiki/UsingLombard0.2", 0); } catch (GLib.Error e) { } } @@ -864,7 +900,7 @@ void main(string[] args) { debug_level = -1; OptionContext context = new OptionContext( " [project file] - Create and edit movies"); - context.add_main_entries(options, null); + context.add_main_entries(get_options(), null); context.add_group(Gst.init_get_option_group()); try { @@ -880,6 +916,9 @@ void main(string[] args) { GLib.Environment.set_application_name("Lombard"); AppDirs.init(args[0], _PROGRAM_NAME); + string rc_file = AppDirs.get_resources_dir().get_child("lombard.rc").get_path(); + + Gtk.rc_parse(rc_file); Gst.init(ref args); if (args.length > 2) { diff --git a/src/lombard/video_project.vala b/src/lombard/video_project.vala index 1265288..f9c5fac 100644 --- a/src/lombard/video_project.vala +++ b/src/lombard/video_project.vala @@ -29,10 +29,10 @@ class VideoProject : Project { return App.NAME; } - public override TimeCode get_clip_time(ClipFile f) { + public override TimeCode get_clip_time(MediaFile f) { TimeCode t = {}; - if (f.is_of_type(MediaType.VIDEO)) { + if (f.get_caps(MediaType.VIDEO) != null) { Fraction rate; if (!get_framerate_fraction(out rate)) { rate.numerator = 2997; diff --git a/src/marina/AudioMeter.vala b/src/marina/AudioMeter.vala index 196291b..3776992 100644 --- a/src/marina/AudioMeter.vala +++ b/src/marina/AudioMeter.vala @@ -16,7 +16,7 @@ public class AudioMeter : Gtk.DrawingArea { double current_level_right = -100; const double minDB = -70; - public AudioMeter(Model.AudioTrack track) { + public void setup(Model.AudioTrack track) { int number_of_channels; if (track.get_num_channels(out number_of_channels)) { stereo = number_of_channels < 1; @@ -69,7 +69,7 @@ public class AudioMeter : Gtk.DrawingArea { initialize_meter(); } - context.set_source_rgb(0, 0, 0); + context.set_source_rgb(0.1, 0.1, 0.1); context.rectangle(0, 0, allocation.width, allocation.height); context.fill(); diff --git a/src/marina/ClassFactory.vala b/src/marina/ClassFactory.vala index 53ccfbb..01b6067 100644 --- a/src/marina/ClassFactory.vala +++ b/src/marina/ClassFactory.vala @@ -26,7 +26,11 @@ public class ClassFactory { assert(transport_delegate != null); return new TrackViewConcrete(transport_delegate, track, timeline); } - + + public virtual Model.MediaFile get_media_file(string filename, int64 duration) { + return new Model.MediaFileConcrete(filename, duration); + } + public static void set_class_factory(ClassFactory class_factory) { ClassFactory.class_factory = class_factory; } diff --git a/src/marina/ClipLibraryView.vala b/src/marina/ClipLibraryView.vala index c405add..239d6c7 100644 --- a/src/marina/ClipLibraryView.vala +++ b/src/marina/ClipLibraryView.vala @@ -13,7 +13,7 @@ public class ClipLibraryView : Gtk.EventBox { Gtk.TreeSelection selection; Gtk.Label label = null; Gtk.ListStore list_store; - int num_clipfiles; + int num_mediafiles; Gee.ArrayList<string> files_dragging = new Gee.ArrayList<string>(); Gtk.IconTheme icon_theme; @@ -58,7 +58,7 @@ public class ClipLibraryView : Gtk.EventBox { list_store.set_default_sort_func(name_sort); list_store.set_sort_column_id(name_column.get_sort_column_id(), Gtk.SortType.ASCENDING); - num_clipfiles = 0; + num_mediafiles = 0; if (drag_message != null) { label = new Gtk.Label(drag_message); label.modify_fg(Gtk.StateType.NORMAL, parse_color("#fff")); @@ -68,8 +68,8 @@ public class ClipLibraryView : Gtk.EventBox { tree_view.modify_base(Gtk.StateType.NORMAL, parse_color("#444")); tree_view.set_headers_visible(false); - project.clipfile_added.connect(on_clipfile_added); - project.clipfile_removed.connect(on_clipfile_removed); + project.mediafile_added.connect(on_mediafile_added); + project.mediafile_removed.connect(on_mediafile_removed); project.cleared.connect(on_remove_all_rows); project.time_signature_changed.connect(on_time_signature_changed); @@ -160,7 +160,7 @@ public class ClipLibraryView : Gtk.EventBox { context_menu.popdown(); } - return true; + return false; } bool on_button_released(Gdk.EventButton b) { @@ -185,7 +185,7 @@ public class ClipLibraryView : Gtk.EventBox { if (path == null || (cell_x == 0 && cell_y == 0)) { selection_changed(false); - return true; + return false; } bool shift_pressed = (b.state & Gdk.ModifierType.SHIFT_MASK) != 0; @@ -199,7 +199,7 @@ public class ClipLibraryView : Gtk.EventBox { selection.select_path(path); selection_changed(true); - return true; + return false; } void on_cursor_changed() { @@ -309,48 +309,48 @@ public class ClipLibraryView : Gtk.EventBox { return column; } - void update_iter(Gtk.TreeIter it, Model.ClipFile clip_file) { + void update_iter(Gtk.TreeIter it, Model.MediaFile media_file) { Gdk.Pixbuf icon; - if (clip_file.is_online()) { - if (clip_file.thumbnail == null) - icon = (clip_file.is_of_type(Model.MediaType.VIDEO) ? + if (media_file.is_online()) { + if (media_file.get_thumbnail() == null) + icon = (media_file.get_caps(Model.MediaType.VIDEO) != null ? default_video_icon : default_audio_icon); else { - icon = clip_file.thumbnail; + icon = media_file.get_thumbnail(); } } else { icon = default_error_icon; } list_store.set(it, ColumnType.THUMBNAIL, icon, - ColumnType.NAME, isolate_filename(clip_file.filename), - ColumnType.DURATION, time_provider.get_time_duration(clip_file.length), - ColumnType.FILENAME, clip_file.filename, -1); + ColumnType.NAME, isolate_filename(media_file.filename), + ColumnType.DURATION, time_provider.get_time_duration(media_file.length), + ColumnType.FILENAME, media_file.filename, -1); } - void on_clipfile_added(Model.ClipFile f) { - emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_file_added"); + void on_mediafile_added(Model.MediaFile f) { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_media_file_added"); Gtk.TreeIter it; - if (find_clipfile(f, out it) >= 0) { + if (find_mediafile(f, out it) >= 0) { list_store.remove(it); } else { - if (num_clipfiles == 0) { + if (num_mediafiles == 0) { if (label != null) { remove(label); } add(tree_view); tree_view.show(); } - num_clipfiles++; + num_mediafiles++; } list_store.append(out it); update_iter(it, f); } - int find_clipfile(Model.ClipFile f, out Gtk.TreeIter iter) { + int find_mediafile(Model.MediaFile f, out Gtk.TreeIter iter) { Gtk.TreeModel model = tree_view.get_model(); bool b = model.get_iter_first(out iter); @@ -369,19 +369,19 @@ public class ClipLibraryView : Gtk.EventBox { return -1; } - public void on_clipfile_removed(Model.ClipFile f) { - emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_file_removed"); + public void on_mediafile_removed(Model.MediaFile f) { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_media_file_removed"); Gtk.TreeIter it; - if (find_clipfile(f, out it) >= 0) { + if (find_mediafile(f, out it) >= 0) { remove_row(ref it); } } bool remove_row(ref Gtk.TreeIter it) { bool b = list_store.remove(it); - num_clipfiles--; - if (num_clipfiles == 0) { + num_mediafiles--; + if (num_mediafiles == 0) { remove(tree_view); if (label != null) { add(label); @@ -410,9 +410,9 @@ public class ClipLibraryView : Gtk.EventBox { while (more_items) { string filename; list_store.get(iter, ColumnType.FILENAME, out filename, -1); - Model.ClipFile clip_file = project.find_clipfile(filename); + Model.MediaFile media_file = project.find_mediafile(filename); list_store.set(iter, ColumnType.DURATION, - time_provider.get_time_duration(clip_file.length), -1); + time_provider.get_time_duration(media_file.length), -1); more_items = list_store.iter_next(ref iter); } } @@ -422,13 +422,13 @@ public class ClipLibraryView : Gtk.EventBox { if (list_store.get_iter(out it, path)) { string filename; model.get(it, ColumnType.FILENAME, out filename, -1); - if (project.clipfile_on_track(filename)) { + if (project.mediafile_on_track(filename)) { if (DialogUtils.delete_cancel("Clip is in use. Delete anyway?") != Gtk.ResponseType.YES) return; } - project.remove_clipfile(filename); + project.remove_mediafile(filename); if (Path.get_dirname(filename) == project.get_audio_path()) { if (DialogUtils.delete_keep("Delete clip from disk? This action is not undoable.") diff --git a/src/marina/ClipView.vala b/src/marina/ClipView.vala new file mode 100644 index 0000000..41004ef --- /dev/null +++ b/src/marina/ClipView.vala @@ -0,0 +1,398 @@ +/* Copyright 2009-2010 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +using Logging; + +public class GapView : Gtk.DrawingArea { + public Model.Gap gap; + Gdk.Color fill_color; + + public GapView(int64 start, int64 length, int width, int height) { + + gap = new Model.Gap(start, start + length); + + Gdk.Color.parse("#777", out fill_color); + + set_flags(Gtk.WidgetFlags.NO_WINDOW); + + set_size_request(width, height); + } + + public signal void removed(GapView gap_view); + public signal void unselected(GapView gap_view); + + public void remove() { + removed(this); + } + + public void unselect() { + unselected(this); + } + + public override bool expose_event(Gdk.EventExpose e) { + Cairo.Context context = Gdk.cairo_create(window); + draw_rounded_rectangle(context, fill_color, true, allocation.x, allocation.y, + allocation.width - 1, allocation.height - 1); + return true; + } +} + +public class ClipView : Gtk.DrawingArea { + enum MotionMode { + NONE, + DRAGGING, + LEFT_TRIM, + RIGHT_TRIM + } + + public enum SelectionType { + NONE, + ADD, + EXTEND + } + + public Model.Clip clip; + public int64 initial_time; + weak Model.TimeSystem time_provider; + public int height; // TODO: We request size of height, but we aren't allocated this height. + // We should be using the allocated height, not the requested height. + public static Gtk.Menu context_menu; + TransportDelegate transport_delegate; + Gdk.Color color_black; + Gdk.Color color_normal; + Gdk.Color color_selected; + int drag_point; + int snap_amount; + bool snapped; + MotionMode motion_mode = MotionMode.NONE; + bool button_down = false; + bool pending_selection; + SelectionType selection_type; + const int MIN_DRAG = 5; + const int TRIM_WIDTH = 10; + public const int SNAP_DELTA = 10; + + static Gdk.Cursor left_trim_cursor = new Gdk.Cursor(Gdk.CursorType.LEFT_SIDE); + static Gdk.Cursor right_trim_cursor = new Gdk.Cursor(Gdk.CursorType.RIGHT_SIDE); + static Gdk.Cursor hand_cursor = new Gdk.Cursor.from_name(Gdk.Display.get_default(), "dnd-none"); + // will be used for drag + static Gdk.Cursor plus_cursor = new Gdk.Cursor.from_name(Gdk.Display.get_default(), "dnd-copy"); + + public signal void clip_deleted(Model.Clip clip); + public signal void clip_moved(); + public signal void selection_request(SelectionType selection_type); + public signal void move_request(int64 delta); + public signal void move_commit(int64 delta); + public signal void move_begin(bool copy); + public signal void trim_begin(Gdk.WindowEdge edge); + public signal void trim_request(Gdk.WindowEdge edge, int64 delta); + public signal void trim_commit(Gdk.WindowEdge edge); + public signal void selection_changed(); + public ClipView(TransportDelegate transport_delegate, Model.Clip clip, + Model.TimeSystem time_provider, int height) { + this.transport_delegate = transport_delegate; + this.clip = clip; + this.time_provider = time_provider; + this.height = height; + + clip.moved.connect(on_clip_moved); + clip.updated.connect(on_clip_updated); + clip.selection_changed.connect(on_selection_changed); + + Gdk.Color.parse("000", out color_black); + get_clip_colors(); + + set_flags(Gtk.WidgetFlags.NO_WINDOW); + + adjust_size(height); + } + + public TrackView get_track_view() { + return get_parent() as TrackView; + } + + void get_clip_colors() { + if (clip.mediafile.is_online()) { + Gdk.Color.parse(clip.type == Model.MediaType.VIDEO ? "#d82" : "#84a", + out color_selected); + Gdk.Color.parse(clip.type == Model.MediaType.VIDEO ? "#da5" : "#b9d", + out color_normal); + } else { + Gdk.Color.parse("red", out color_selected); + Gdk.Color.parse("#AA0000", out color_normal); + } + } + + void on_clip_updated() { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_updated"); + get_clip_colors(); + queue_draw(); + } + + void on_selection_changed() { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_selection_changed"); + selection_changed(); + } + + // Note that a view's size may vary slightly (by a single pixel) depending on its + // starting position. This is because the clip's length may not be an integer number of + // pixels, and may get rounded either up or down depending on the clip position. + public void adjust_size(int height) { + int width = time_provider.time_to_xpos(clip.start + clip.duration) - + time_provider.time_to_xpos(clip.start); + set_size_request(width + 1, height); + } + + public void on_clip_moved(Model.Clip clip) { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_moved"); + adjust_size(height); + clip_moved(); + } + + public void delete_clip() { + clip_deleted(clip); + } + + public void draw(Cairo.Context context) { + context.save(); + weak Gdk.Color fill = clip.is_selected ? color_selected : color_normal; + + bool left_trimmed = clip.media_start != 0 && !clip.is_recording; + + bool right_trimmed = clip.mediafile.is_online() ? + (clip.media_start + clip.duration != clip.mediafile.length) : false; + + if (!left_trimmed && !right_trimmed) { + draw_rounded_rectangle(context, fill, true, allocation.x + 1, allocation.y + 1, + allocation.width - 2, allocation.height - 2); + draw_rounded_rectangle(context, color_black, false, allocation.x, allocation.y, + allocation.width - 1, allocation.height - 1); + + } else if (!left_trimmed && right_trimmed) { + draw_left_rounded_rectangle(context, fill, true, allocation.x + 1, allocation.y + 1, + allocation.width - 2, allocation.height - 2); + draw_left_rounded_rectangle(context, color_black, false, allocation.x, allocation.y, + allocation.width - 1, allocation.height - 1); + + } else if (left_trimmed && !right_trimmed) { + draw_right_rounded_rectangle(context, fill, true, allocation.x + 1, allocation.y + 1, + allocation.width - 2, allocation.height - 2); + draw_right_rounded_rectangle(context, color_black, false, allocation.x, allocation.y, + allocation.width - 1, allocation.height - 1); + + } else { + draw_square_rectangle(context, fill, true, allocation.x + 1, allocation.y + 1, + allocation.width - 2, allocation.height - 2); + draw_square_rectangle(context, color_black, false, allocation.x, allocation.y, + allocation.width - 1, allocation.height - 1); + } + + context.rectangle(allocation.x, allocation.y, allocation.width, allocation.height); + context.clip(); + Pango.Layout layout = Pango.cairo_create_layout(context); + Gdk.Color color = style.text[Gtk.StateType.NORMAL]; + + context.set_source_rgb(color.red, color.green, color.blue); + layout.set_font_description(style.font_desc); + string s; + if (clip.is_recording) { + s = "Recording"; + } else if (!clip.mediafile.is_online()) { + s = "%s (Offline)".printf(clip.name); + } + else { + s = "%s".printf(clip.name); + } + layout.set_text(s, (int) s.length); + + int width, height; + layout.get_pixel_size(out width, out height); + context.move_to(allocation.x + 10, allocation.y + height); + Pango.cairo_show_layout(context, layout); + context.restore(); + } + + public override bool expose_event(Gdk.EventExpose event) { + Cairo.Context context = Gdk.cairo_create(window); + draw(context); + return true; + } + + public override bool button_press_event(Gdk.EventButton event) { + if (!transport_delegate.is_stopped()) { + return false; + } + + event.x -= allocation.x; + bool primary_press = event.button == 1; + if (primary_press) { + button_down = true; + drag_point = (int)event.x; + snap_amount = 0; + snapped = false; + } + + if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0) { + selection_type = SelectionType.ADD; + } else if ((event.state & Gdk.ModifierType.SHIFT_MASK) != 0) { + selection_type = SelectionType.EXTEND; + } else { + selection_type = SelectionType.NONE; + } + // The clip is not responsible for changing the selection state. + // It may depend upon knowledge of multiple clips. Let anyone who is interested + // update our state. + if (is_left_trim(event.x, event.y)) { + selection_request(SelectionType.NONE); + if (primary_press) { + trim_begin(Gdk.WindowEdge.WEST); + motion_mode = MotionMode.LEFT_TRIM; + } + } else if (is_right_trim(event.x, event.y)){ + selection_request(SelectionType.NONE); + if (primary_press) { + trim_begin(Gdk.WindowEdge.EAST); + motion_mode = MotionMode.RIGHT_TRIM; + } + } else { + if (!clip.is_selected) { + pending_selection = false; + selection_request(selection_type); + } else { + pending_selection = true; + } + } + + if (event.button == 3) { + context_menu.select_first(true); + context_menu.popup(null, null, null, event.button, event.time); + } else { + context_menu.popdown(); + } + + return false; + } + + public override bool button_release_event(Gdk.EventButton event) { + if (!transport_delegate.is_stopped()) { + return false; + } + + event.x -= allocation.x; + button_down = false; + if (event.button == 1) { + switch (motion_mode) { + case MotionMode.NONE: { + if (pending_selection) { + selection_request(SelectionType.ADD); + } + } + break; + case MotionMode.DRAGGING: { + int64 delta = time_provider.xsize_to_time((int) event.x - drag_point); + if (motion_mode == MotionMode.DRAGGING) { + move_commit(delta); + } + } + break; + case MotionMode.LEFT_TRIM: + trim_commit(Gdk.WindowEdge.WEST); + break; + case MotionMode.RIGHT_TRIM: + trim_commit(Gdk.WindowEdge.EAST); + break; + } + } + motion_mode = MotionMode.NONE; + return false; + } + + public override bool motion_notify_event(Gdk.EventMotion event) { + if (!transport_delegate.is_stopped()) { + return true; + } + + event.x -= allocation.x; + int delta_pixels = (int)(event.x - drag_point) - snap_amount; + if (snapped) { + snap_amount += delta_pixels; + if (snap_amount.abs() < SNAP_DELTA) { + return true; + } + delta_pixels += snap_amount; + snap_amount = 0; + snapped = false; + } + + int64 delta_time = time_provider.xsize_to_time(delta_pixels); + + switch (motion_mode) { + case MotionMode.NONE: + if (!button_down && is_left_trim(event.x, event.y)) { + window.set_cursor(left_trim_cursor); + } else if (!button_down && is_right_trim(event.x, event.y)) { + window.set_cursor(right_trim_cursor); + } else if (clip.is_selected && button_down) { + if (delta_pixels.abs() > MIN_DRAG) { + bool do_copy = (event.state & Gdk.ModifierType.CONTROL_MASK) != 0; + if (do_copy) { + window.set_cursor(plus_cursor); + } else { + window.set_cursor(hand_cursor); + } + motion_mode = MotionMode.DRAGGING; + move_begin(do_copy); + } + } else { + window.set_cursor(null); + } + break; + case MotionMode.RIGHT_TRIM: + if (button_down) { + int64 duration = clip.duration; + trim_request(Gdk.WindowEdge.EAST, delta_time); + if (duration != clip.duration) { + drag_point += time_provider.time_to_xsize(clip.duration - duration); + } + return true; + } + break; + case MotionMode.LEFT_TRIM: + if (button_down) { + trim_request(Gdk.WindowEdge.WEST, delta_time); + } + return true; + case MotionMode.DRAGGING: + move_request(delta_time); + return true; + } + return false; + } + + bool is_trim_height(double y) { + return y - allocation.y > allocation.height / 2; + } + + bool is_left_trim(double x, double y) { + return is_trim_height(y) && x > 0 && x < TRIM_WIDTH; + } + + bool is_right_trim(double x, double y) { + return is_trim_height(y) && x > allocation.width - TRIM_WIDTH && + x < allocation.width; + } + + public void select() { + if (!clip.is_selected) { + selection_request(SelectionType.ADD); + } + } + + public void snap(int64 amount) { + snap_amount = time_provider.time_to_xsize(amount); + snapped = true; + } +} diff --git a/src/marina/DialogUtils.vala b/src/marina/DialogUtils.vala index 7a1aa49..3d753d6 100644 --- a/src/marina/DialogUtils.vala +++ b/src/marina/DialogUtils.vala @@ -48,12 +48,12 @@ namespace DialogUtils { Gtk.FileChooserDialog d = new Gtk.FileChooserDialog("Open Files", parent, Gtk.FileChooserAction.OPEN, - Gtk.STOCK_CANCEL, + Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL, - Gtk.STOCK_OPEN, + Gtk.Stock.OPEN, Gtk.ResponseType.ACCEPT, null); d.set_current_folder(GLib.Environment.get_home_dir()); - Gee.ArrayList<Gtk.FileFilter> filters = new Gee.ArrayList<Gtk.FileFilter>(); + Gee.ArrayList<Gtk.FileFilter> filters = new Gee.ArrayList<Gtk.FileFilter>(); add_filters(filter_descriptions, d, filters, allow_all); d.set_select_multiple(allow_multiple); if (d.run() == Gtk.ResponseType.ACCEPT) { @@ -68,8 +68,8 @@ namespace DialogUtils { filter_description_struct[] filter_descriptions, ref string filename) { bool return_value = false; Gtk.FileChooserDialog d = new Gtk.FileChooserDialog(title, parent, - Gtk.FileChooserAction.SAVE, Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, - Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT, null); + Gtk.FileChooserAction.SAVE, Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL, + Gtk.Stock.SAVE, Gtk.ResponseType.ACCEPT, null); if (filename != null) { d.set_current_folder(Path.get_dirname(filename)); } else { @@ -186,15 +186,15 @@ namespace DialogUtils { } public Gtk.ResponseType delete_keep(string message) { - return two_button_dialog(message, "Keep", Gtk.STOCK_DELETE); + return two_button_dialog(message, "Keep", Gtk.Stock.DELETE); } public Gtk.ResponseType add_cancel(string message) { - return two_button_dialog(message, Gtk.STOCK_CANCEL, Gtk.STOCK_ADD); + return two_button_dialog(message, Gtk.Stock.CANCEL, Gtk.Stock.ADD); } public Gtk.ResponseType delete_cancel(string message) { - return two_button_dialog(message, Gtk.STOCK_CANCEL, Gtk.STOCK_DELETE); + return two_button_dialog(message, Gtk.Stock.CANCEL, Gtk.Stock.DELETE); } public bool confirm_replace(Gtk.Window? parent, string filename) { @@ -202,7 +202,7 @@ namespace DialogUtils { parent, Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, "<big><b>A file named \"%s\" already exists. Do you want to replace it?</b></big>", Path.get_basename(filename)); - md.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + md.add_buttons(Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL, "Replace", Gtk.ResponseType.ACCEPT); int response = md.run(); md.destroy(); @@ -222,8 +222,8 @@ namespace DialogUtils { public Gtk.ResponseType save_close_cancel(Gtk.Window? parent, string? title, string message) { ButtonStruct[] buttons = { ButtonStruct("Close _without saving", Gtk.ResponseType.CLOSE), - ButtonStruct(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL), - ButtonStruct(Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT) + ButtonStruct(Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL), + ButtonStruct(Gtk.Stock.SAVE, Gtk.ResponseType.ACCEPT) }; return run_dialog(parent, Gtk.MessageType.WARNING, title, message, buttons); @@ -248,12 +248,20 @@ namespace DialogUtils { t.attach(a, x, x + 1, y, y + 1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL, xpad, ypad); } + void on_dialog_response(Gtk.Dialog dialog, int response_id) { + if (response_id == Gtk.ResponseType.CLOSE) { + dialog.destroy(); + } + } + public void show_clip_properties(Gtk.Window parent, ClipView? selected_clip, - Model.ClipFile ? clip_file, Fraction? frames_per_second) { + Model.MediaFile ? media_file, Fraction? frames_per_second) { Gtk.Dialog d = new Gtk.Dialog.with_buttons("Clip Properties", parent, - Gtk.DialogFlags.MODAL, Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT); + Gtk.DialogFlags.NO_SEPARATOR, Gtk.Stock.CLOSE, Gtk.ResponseType.CLOSE); + + d.response.connect(on_dialog_response); if (selected_clip != null) { - clip_file = selected_clip.clip.clipfile; + media_file = selected_clip.clip.mediafile; } d.set("has-separator", false); @@ -274,7 +282,7 @@ namespace DialogUtils { } add_label_to_table(t, "<i>Location:</i>", 0, row, tab_padding, 0, false); - add_label_to_table(t, "%s".printf(clip_file.filename), 1, row++, 5, 0, true); + add_label_to_table(t, "%s".printf(media_file.filename), 1, row++, 5, 0, true); if (selected_clip != null) { add_label_to_table(t, "<i>Timeline length:</i>", 0, row, tab_padding, 0, false); @@ -287,11 +295,11 @@ namespace DialogUtils { frames_per_second), frames_per_second); length_string = time.to_string(); time = frame_to_time(time_to_frame_with_rate( - selected_clip.clip.clipfile.length, frames_per_second), frames_per_second); + selected_clip.clip.mediafile.length, frames_per_second), frames_per_second); actual_length = time.to_string(); } else { length_string = time_to_string(selected_clip.clip.duration); - actual_length = time_to_string(selected_clip.clip.clipfile.length); + actual_length = time_to_string(selected_clip.clip.mediafile.length); } add_label_to_table(t, "%s".printf(length_string), 1, row++, 5, 0, true); @@ -302,17 +310,17 @@ namespace DialogUtils { } } - if (clip_file.has_caps_structure(Model.MediaType.VIDEO)) { + if (media_file.get_caps(Model.MediaType.VIDEO) != null) { add_label_to_table(t, "<b>Video</b>", 0, row++, 5, 0, false); int w, h; - if (clip_file.get_dimensions(out w, out h)) { + if (media_file.get_dimensions(out w, out h)) { add_label_to_table(t, "<i>Dimensions:</i>", 0, row, tab_padding, 0, false); add_label_to_table(t, "%d x %d".printf(w, h), 1, row++, 5, 0, true); } Fraction r; - if (clip_file.get_frame_rate(out r)) { + if (media_file.get_frame_rate(out r)) { add_label_to_table(t, "<i>Frame rate:</i>", 0, row, tab_padding, 0, false); if (r.numerator % r.denominator != 0) @@ -326,17 +334,17 @@ namespace DialogUtils { } } - if (clip_file.has_caps_structure(Model.MediaType.AUDIO)) { + if (media_file.get_caps(Model.MediaType.AUDIO) != null) { add_label_to_table(t, "<b>Audio</b>", 0, row++, 5, 0, false); int rate; - if (clip_file.get_sample_rate(out rate)) { + if (media_file.get_sample_rate(out rate)) { add_label_to_table(t, "<i>Sample rate:</i>", 0, row, tab_padding, 0, false); add_label_to_table(t, "%d Hz".printf(rate), 1, row++, 5, 0, true); } string s; - if (clip_file.get_num_channels_string(out s)) { + if (media_file.get_num_channels_string(out s)) { add_label_to_table(t, "<i>Number of channels:</i>", 0, row, tab_padding, 0, false); add_label_to_table(t, "%s".printf(s), 1, row++, 5, 0, true); } @@ -345,7 +353,5 @@ namespace DialogUtils { d.vbox.pack_start(t, false, false, 0); d.show_all(); - d.run(); - d.destroy(); } } diff --git a/src/marina/MediaEngine.vala b/src/marina/MediaEngine.vala index e7235d7..07a20df 100644 --- a/src/marina/MediaEngine.vala +++ b/src/marina/MediaEngine.vala @@ -17,6 +17,107 @@ public enum PlayState { namespace View { +public class InputSource { + + public string device { + get; private set; + } + + public string friendly_name { + get; private set; + } + + public int number_of_channels { + get; private set; + } + + public InputSource(string device, string friendly_name, int number_of_channels) { + this.device = device; + this.friendly_name = friendly_name; + this.number_of_channels = number_of_channels; + } +} + +public class InputSources { + static Gee.ArrayList<InputSource> get_source_devices(Gst.Element element) { + GLib.Value factory_name = ""; + element.get_factory().get_property("name", ref factory_name); + Gee.ArrayList<InputSource> input_sources = new Gee.ArrayList<InputSource>(); + + Value device = ""; + element.get_property("device", ref device); + if (device.get_string() != "") { + if (element is Gst.PropertyProbe) { + ((Gst.PropertyProbe)element).probe_property_name("device"); + + unowned GLib.ValueArray devices = + ((Gst.PropertyProbe)element).get_values_name("device"); + foreach (unowned Gst.Value d in devices) { + set_device_name(element, d); + Value nice_name = ""; + element.get_property("device-name", ref nice_name); + + int number_of_channels = number_of_channels_for_element(element); + input_sources.add( + new InputSource(d.get_string(), nice_name.get_string(), + number_of_channels)); + element.set_state(Gst.State.NULL); + } + } + } + return input_sources; + } + + static void set_device_name(Gst.Element element, Value device) { + element.set_property("device", device); + + //channels don't fixate until in paused state + if (element.set_state(Gst.State.PAUSED) == Gst.StateChangeReturn.ASYNC) { + Gst.State state; + Gst.State pending = Gst.State.VOID_PENDING; + do { + Gtk.main_iteration(); + element.get_state(out state, out pending, 0); + } while (pending != Gst.State.VOID_PENDING); + } + } + + static int number_of_channels_for_element(Gst.Element input_source) { + Gst.Pad source_pad = input_source.get_pad("src"); + Gst.Caps caps = source_pad.get_caps(); + + Gst.Structure structure = caps.get_structure(0); + int return_value; + structure.get_int("channels", out return_value); + return return_value; + } + + public static Gee.ArrayList<InputSource> get_input_selections(string element) { + try { + Gst.Bin a_bin = (Gst.Bin) Gst.parse_bin_from_description(element, false); + Gst.Iterator<Gst.Element> sources = a_bin.iterate_sources(); + Gst.Element input_source; + while (sources.next(out input_source) == Gst.IteratorResult.OK) { + return get_source_devices(input_source); + } + } catch { + + } + return new Gee.ArrayList<InputSource>(); + } + + public static int get_number_of_channels(string element_name, string? device) { + if (device == null) { + return -1; + } + Gst.Element element = Gst.ElementFactory.make(element_name, null); + set_device_name(element, device); + int number_of_channels = number_of_channels_for_element(element); + element.set_state(Gst.State.NULL); + return number_of_channels; + } +} + class MediaClip : Object { public Gst.Element file_source; weak Model.Clip clip; @@ -80,7 +181,7 @@ class MediaClip : Object { "singledecoder", filename); if (((Gst.Bin) file_source).add(sbin)) { if (!file_source.sync_state_with_parent()) { - clip.clipfile.set_online(false); + clip.mediafile.set_online(false); } } } @@ -171,13 +272,13 @@ public abstract class MediaTrack : Object { void on_clip_updated(Model.Clip clip) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_updated"); - if (clip.clipfile.is_online()) { + if (clip.mediafile.is_online()) { try { MediaClip media_clip; if (clip.type == Model.MediaType.AUDIO) { - media_clip = new MediaAudioClip(composition, clip, clip.clipfile.filename); + media_clip = new MediaAudioClip(composition, clip, clip.mediafile.filename); } else { - media_clip = new MediaVideoClip(composition, clip, clip.clipfile.filename); + media_clip = new MediaVideoClip(composition, clip, clip.mediafile.filename); } media_clip.clip_removed.connect(on_media_clip_removed); @@ -375,6 +476,8 @@ public class MediaAudioTrack : MediaTrack { public MediaAudioTrack(MediaEngine media_engine, Model.AudioTrack track) throws Error { base(media_engine, track); track.parameter_changed.connect(on_parameter_changed); + track.mute_changed.connect(on_mute_changed); + track.indirect_mute_changed.connect(on_mute_changed); audio_convert = make_element("audioconvert"); audio_resample = make_element("audioresample"); @@ -427,9 +530,13 @@ public class MediaAudioTrack : MediaTrack { pan.set_property("panorama", new_value); break; case Model.Parameter.VOLUME: - volume.set_property("volume", new_value); + volume.set_property("volume", new_value); break; - } + } + } + + void on_mute_changed(Model.AudioTrack track) { + volume.set_property("mute", track.mute || track.indirect_mute); } void on_level_changed(Gst.Object source, double level_left, double level_right) { @@ -443,10 +550,10 @@ public class MediaAudioTrack : MediaTrack { return media_engine.get_audio_silence(); } - override void link_new_pad(Gst.Pad pad, Gst.Element track_element) { + protected override void link_new_pad(Gst.Pad pad, Gst.Element track_element) { Gst.Bin bin = (Gst.Bin) pad.get_parent_element(); if (!bin.link_many(audio_convert, audio_resample, level, pan, volume)) { - stderr.printf("could not link_new_pad for audio track"); + warning("could not link_new_pad for audio track"); } Gst.Pad volume_pad = volume.get_pad("src"); @@ -454,7 +561,7 @@ public class MediaAudioTrack : MediaTrack { track_element.get_compatible_pad_template(volume_pad.get_pad_template()), null); if (volume_pad.link(adder_pad) != Gst.PadLinkReturn.OK) { - error("could not link to adder %s->%s\n", volume.name, track_element.name); + warning("could not link to adder %s->%s\n", volume.name, track_element.name); } } @@ -702,7 +809,7 @@ public class MediaEngine : MultiFileProgressInterface, Object { bus.add_signal_watch(); bus.message["error"] += on_error; bus.message["warning"] += on_warning; - bus.message["eos"] += on_eos; + bus.message["eos"] += on_eos; bus.message["state-changed"] += on_state_change; bus.message["element"] += on_element; } @@ -771,8 +878,11 @@ public class MediaEngine : MultiFileProgressInterface, Object { } protected Gst.Caps build_audio_caps(int num_channels) { - string caps = "audio/x-raw-int,rate=%d,channels=%d,width=%d,depth=%d"; - caps = caps.printf(get_sample_rate(), num_channels, get_sample_width(), get_sample_depth()); + string caps = "audio/x-raw-int,rate=%d,width=%d,depth=%d"; + caps = caps.printf(get_sample_rate(), get_sample_width(), get_sample_depth()); + if (num_channels > 0) { + caps = "%s,channels=%d".printf(caps, num_channels); + } return Gst.Caps.from_string(caps); } @@ -791,6 +901,7 @@ public class MediaEngine : MultiFileProgressInterface, Object { string text; message.parse_warning(out error, out text); warning("%s", text); + project.print_graph(pipeline, "bus_warning"); } void on_error(Gst.Bus bus, Gst.Message message) { @@ -812,7 +923,7 @@ public class MediaEngine : MultiFileProgressInterface, Object { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_element"); unowned Gst.Structure structure = message.get_structure(); - if (play_state == PlayState.PLAYING && structure.name.to_string() == "level") { + if (play_state == PlayState.PLAYING && structure.get_name() == "level") { Gst.Value? rms = structure.get_value("rms"); uint size = rms.list_get_size(); Gst.Value? temp = rms.list_get_value(0); @@ -823,14 +934,14 @@ public class MediaEngine : MultiFileProgressInterface, Object { temp = rms.list_get_value(1); level_right = temp.get_double(); } - level_changed(message.src(), level_left, level_right); + level_changed(message.src, level_left, level_right); } } void on_state_change(Gst.Bus bus, Gst.Message message) { - if (message.src() != pipeline) { + if (message.src != pipeline) { emit(this, Facility.GRAPH, Level.VERBOSE, - "on_state_change returning. message from %s".printf(message.src().get_name())); + "on_state_change returning. message from %s".printf(message.src.get_name())); return; } @@ -960,10 +1071,6 @@ public class MediaEngine : MultiFileProgressInterface, Object { callback_pulse(); if (play_state == PlayState.PLAYING) { - if (position >= project.get_length()) { - go(project.get_length()); - pause(); - } position_changed(time); } else if (play_state == PlayState.EXPORTING) { if (time > project.get_length()) { @@ -994,8 +1101,39 @@ public class MediaEngine : MultiFileProgressInterface, Object { // TODO: don't expose Gst.State public void set_gst_state(Gst.State state) { - if (pipeline.set_state(state) == Gst.StateChangeReturn.FAILURE) - error("can't set state"); + if (pipeline.set_state(state) == Gst.StateChangeReturn.FAILURE) { + warning("Failed to change state"); + string message = null; + switch (play_state) { + case PlayState.PRE_EXPORT: + case PlayState.CANCEL_EXPORT: + case PlayState.EXPORTING: + message = "Error exporting"; + break; + case PlayState.LOADING: + message = "Error loading"; + break; + case PlayState.STOPPED: + message = "Error stopping"; + break; + case PlayState.PRE_PLAY: + case PlayState.PLAYING: + message = "Error playing"; + break; + case PlayState.PRE_RECORD_NULL: + case PlayState.PRE_RECORD: + case PlayState.RECORDING: + case PlayState.POST_RECORD: + message = "Error recording"; + play_state = PlayState.POST_RECORD; + pipeline.set_state(Gst.State.PAUSED); + playing = false; + break; + default: + return; + } + error_occurred(message, null); + } } void seek(Gst.SeekFlags flags, int64 pos) { @@ -1059,7 +1197,10 @@ public class MediaEngine : MultiFileProgressInterface, Object { } public void post_record() { - assert(gst_state == Gst.State.NULL); + if (gst_state != Gst.State.NULL) { + warning("gst_state was null"); + return; + } record_track._delete_clip(record_region); @@ -1076,14 +1217,18 @@ public class MediaEngine : MultiFileProgressInterface, Object { } public void record(Model.AudioTrack track) { - assert(gst_state != Gst.State.NULL); + if (gst_state == Gst.State.NULL) { + warning("gst_state was null"); + return; + } play_state = PlayState.PRE_RECORD_NULL; set_gst_state(Gst.State.NULL); record_track = track; string filename = new_audio_filename(track); - Model.ClipFile clip_file = new Model.ClipFile(filename); - record_region = new Model.Clip(clip_file, Model.MediaType.AUDIO, "", position, 0, 1, true); + ClassFactory class_factory = ClassFactory.get_class_factory(); + Model.MediaFile media_file = class_factory.get_media_file(filename, 0); + record_region = new Model.Clip(media_file, Model.MediaType.AUDIO, "", position, 0, 1, true); } public void start_record(Model.Clip region) throws Error { @@ -1098,11 +1243,12 @@ public class MediaEngine : MultiFileProgressInterface, Object { record_bin = new Gst.Bin("recordingbin"); record_track._move(record_region, position); record_track.clip_added(record_region, true); - audio_in = make_element("gconfaudiosrc"); + audio_in = make_element("alsasrc"); + audio_in.set("device", record_track.device); record_capsfilter = make_element("capsfilter"); record_capsfilter.set("caps", get_record_audio_caps()); record_sink = make_element("filesink"); - record_sink.set("location", record_region.clipfile.filename); + record_sink.set("location", record_region.mediafile.filename); wav_encoder = make_element("wavenc"); record_bin.add_many(audio_in, record_capsfilter, wav_encoder, record_sink); @@ -1115,7 +1261,9 @@ public class MediaEngine : MultiFileProgressInterface, Object { } protected Gst.Caps get_record_audio_caps() { - return build_audio_caps(1); + int channel_count = 0; + record_track.get_num_channels(out channel_count); + return build_audio_caps(channel_count); } string new_audio_filename(Model.Track track) { diff --git a/src/marina/MediaFile.vala b/src/marina/MediaFile.vala new file mode 100644 index 0000000..4d579d3 --- /dev/null +++ b/src/marina/MediaFile.vala @@ -0,0 +1,41 @@ +/* Copyright 2010 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ +namespace Model { + +public enum MediaType { + AUDIO, + VIDEO +} + +public abstract class MediaFile : Object { + public abstract string filename { + get; + set; + } + + public abstract int64 length { + get; + set; + } + + public abstract Gst.Caps? get_caps(MediaType type); + public abstract void set_caps(MediaType type, Gst.Caps caps); + public abstract Gdk.Pixbuf? get_thumbnail(); + + public signal void updated(); + + public abstract bool is_online(); + public abstract void set_online(bool o); + public abstract void set_thumbnail(Gdk.Pixbuf b); + + public abstract bool get_frame_rate(out Fraction rate); + public abstract bool get_dimensions(out int w, out int h); + public abstract bool get_sample_rate(out int rate); + public abstract bool get_video_format(out uint32 fourcc); + public abstract bool get_num_channels(out int channels); + public abstract bool get_num_channels_string(out string s); +} +} diff --git a/src/marina/MediaFileConcrete.vala b/src/marina/MediaFileConcrete.vala new file mode 100644 index 0000000..25a6f2f --- /dev/null +++ b/src/marina/MediaFileConcrete.vala @@ -0,0 +1,175 @@ +/* Copyright 2009-2010 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + using Logging; + +namespace Model { +public class MediaFileConcrete : MediaFile { + public string _filename; + public override string filename { + public get { + return _filename; + } + public set { + _filename = value; + } + } + + int64 _length; + public override int64 length { + public get { + if (!online) { + warning("retrieving length while clip offline"); + } + return _length; + } + + public set { + _length = value; + } + } + + bool online; + + public Gst.Caps video_caps; // or null if no video + public Gst.Caps audio_caps; // or null if no audio + public Gdk.Pixbuf thumbnail = null; + + public MediaFileConcrete(string filename, int64 length = 0) { + this.filename = filename; + this.length = length; + online = false; + } + + public override Gst.Caps? get_caps(MediaType media_type) { + switch (media_type) { + case MediaType.AUDIO: + return audio_caps; + case MediaType.VIDEO: + return video_caps; + } + return null; + } + + public override void set_caps(MediaType media_type, Gst.Caps caps) { + switch (media_type) { + case MediaType.AUDIO: + audio_caps = caps; + break; + case MediaType.VIDEO: + video_caps = caps; + break; + } + } + + public override Gdk.Pixbuf? get_thumbnail() { + return thumbnail; + } + + public override bool is_online() { + return online; + } + + public override void set_online(bool o) { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "set_online"); + online = o; + updated(); + } + + public override void set_thumbnail(Gdk.Pixbuf b) { + // TODO: Investigate this + // 56x56 - 62x62 icon size does not work for some reason when + // we display the thumbnail while dragging the clip. + + thumbnail = b.scale_simple(64, 44, Gdk.InterpType.BILINEAR); + } + + public bool has_caps_structure(MediaType m) { + if (m == MediaType.AUDIO) { + if (audio_caps == null || audio_caps.get_size() < 1) + return false; + } else if (m == MediaType.VIDEO) { + if (video_caps == null || video_caps.get_size() < 1) + return false; + } + return true; + } + + public bool is_of_type(MediaType t) { + if (t == MediaType.VIDEO) + return video_caps != null; + return audio_caps != null; + } + + bool get_caps_structure(MediaType m, out Gst.Structure s) { + if (!has_caps_structure(m)) + return false; + if (m == MediaType.AUDIO) { + s = audio_caps.get_structure(0); + } else if (m == MediaType.VIDEO) { + s = video_caps.get_structure(0); + } + return true; + } + + public override bool get_frame_rate(out Fraction rate) { + Gst.Structure structure; + if (!get_caps_structure(MediaType.VIDEO, out structure)) + return false; + return structure.get_fraction("framerate", out rate.numerator, out rate.denominator); + } + + public override bool get_dimensions(out int w, out int h) { + Gst.Structure s; + + if (!get_caps_structure(MediaType.VIDEO, out s)) + return false; + + return s.get_int("width", out w) && s.get_int("height", out h); + } + + public override bool get_sample_rate(out int rate) { + Gst.Structure s; + if (!get_caps_structure(MediaType.AUDIO, out s)) + return false; + + return s.get_int("rate", out rate); + } + + public override bool get_video_format(out uint32 fourcc) { + Gst.Structure s; + + if (!get_caps_structure(MediaType.VIDEO, out s)) + return false; + + return s.get_fourcc("format", out fourcc); + } + + public override bool get_num_channels(out int channels) { + Gst.Structure s; + if (!get_caps_structure(MediaType.AUDIO, out s)) { + return false; + } + + return s.get_int("channels", out channels); + } + + public override bool get_num_channels_string(out string s) { + int i; + if (!get_num_channels(out i)) + return false; + + if (i == 1) + s = "Mono"; + else if (i == 2) + s = "Stereo"; + else if ((i % 2) == 0) + s = "Surround %d.1".printf(i - 1); + else + s = "%d".printf(i); + return true; + } +} +} diff --git a/src/marina/MultiFileProgress.vala b/src/marina/MultiFileProgress.vala index a44e733..e9f9c43 100644 --- a/src/marina/MultiFileProgress.vala +++ b/src/marina/MultiFileProgress.vala @@ -44,7 +44,7 @@ public class MultiFileProgress : Gtk.Window { Gtk.HButtonBox button_area = new Gtk.HButtonBox(); button_area.set("layout-style", Gtk.ButtonBoxStyle.CENTER); - cancel_button = new Gtk.Button.from_stock(Gtk.STOCK_CANCEL); + cancel_button = new Gtk.Button.from_stock(Gtk.Stock.CANCEL); cancel_button.clicked.connect(on_cancel_clicked); button_area.add(cancel_button); diff --git a/src/marina/ProjectLoader.vala b/src/marina/ProjectLoader.vala index 7a9ffdf..102a2cf 100644 --- a/src/marina/ProjectLoader.vala +++ b/src/marina/ProjectLoader.vala @@ -8,30 +8,37 @@ using Logging; namespace Model { +public enum ErrorClass { + LoadFailure, + FormatError, + MissingFiles, + Benign +} + public class LoaderHandler : Object { - public signal void load_error(string error_message); + public signal void load_error(ErrorClass error_class, string error_message); public signal void complete(); - + public LoaderHandler() { } - + public virtual bool commit_library(string[] attr_names, string[] attr_values) { return true; } - + public virtual bool commit_marina(string[] attr_names, string[] attr_values) { return true; } - + public virtual bool commit_track(string[] attr_names, string[] attr_values) { return true; } - + public virtual bool commit_clip(string[] attr_names, string[] attr_values) { return true; } - - public virtual bool commit_clipfile(string[] attr_names, string[] attr_values) { + + public virtual bool commit_mediafile(string[] attr_names, string[] attr_values) { return true; } @@ -46,25 +53,24 @@ public class LoaderHandler : Object { public virtual bool commit_click(string[] attr_names, string[] attr_values) { return true; } - + public virtual bool commit_library_preference(string[] attr_names, string[] attr_values) { return true; } - + public virtual void leave_library() { } public virtual void leave_marina() { - } + } public virtual void leave_track() { } - + public virtual void leave_clip() { } - - public virtual void leave_clipfile() { - + + public virtual void leave_mediafile() { } } @@ -81,19 +87,19 @@ public class XmlTreeLoader { context.parse(document, document.length); } catch (MarkupError e) { } - - } - + } + void xml_start_element(GLib.MarkupParseContext c, string name, string[] attr_names, string[] attr_values) { - Model.XmlElement new_element = new Model.XmlElement(name, attr_names, attr_values, current_element); + Model.XmlElement new_element = new Model.XmlElement(name, attr_names, + attr_values, current_element); if (root == null) { root = new_element; } else { assert(current_element != null); current_element.add_child(new_element); } - + current_element = new_element; } @@ -109,8 +115,8 @@ public class XmlTreeLoader { class ProjectBuilder : Object { LoaderHandler handler; - public signal void error_occurred(string error); - + public signal void error_occurred(ErrorClass error_class, string error); + public ProjectBuilder(LoaderHandler handler) { this.handler = handler; } @@ -119,16 +125,17 @@ class ProjectBuilder : Object { if (node.name == expected_name) { return true; } - - error_occurred("expected %s, got %s".printf(expected_name, node.name)); + + error_occurred(ErrorClass.FormatError, + "expected %s, got %s".printf(expected_name, node.name)); return false; } - + void handle_clip(XmlElement clip) { if (check_name("clip", clip)) { if (handler.commit_clip(clip.attribute_names, clip.attribute_values)) { if (clip.children.size != 0) { - error_occurred("clip cannot have children"); + error_occurred(ErrorClass.FormatError, "clip cannot have children"); } handler.leave_clip(); } @@ -154,31 +161,31 @@ class ProjectBuilder : Object { handler.commit_library_preference( preference.attribute_names, preference.attribute_values); } else { - error_occurred("Unknown preference: %s".printf(preference.name)); + error_occurred(ErrorClass.Benign, "Unknown preference: %s".printf(preference.name)); } } - + void handle_time_signature(XmlElement time_signature) { foreach (XmlElement child in time_signature.children) { if (check_name("entry", child)) { if (!handler.commit_time_signature_entry(child.attribute_names, child.attribute_values)) { - error_occurred("Improper time signature node"); + error_occurred(ErrorClass.FormatError, "Improper time signature node"); } } } } - + void handle_tempo(XmlElement tempo) { foreach (XmlElement child in tempo.children) { if (check_name("entry", child)) { if (!handler.commit_tempo_entry(child.attribute_names, child.attribute_values)) { - error_occurred("Improper tempo node"); + error_occurred(ErrorClass.FormatError, "Improper tempo node"); } } } } - + void handle_map(XmlElement map) { switch (map.name) { case "tempo": @@ -188,21 +195,21 @@ class ProjectBuilder : Object { handle_time_signature(map); break; default: - error_occurred("improper map node"); + error_occurred(ErrorClass.FormatError, "improper map node"); break; } } - + void handle_library(XmlElement library) { if (handler.commit_library(library.attribute_names, library.attribute_values)) { foreach (XmlElement child in library.children) { - if (!handler.commit_clipfile(child.attribute_names, child.attribute_values)) - error_occurred("Improper library node"); + if (!handler.commit_mediafile(child.attribute_names, child.attribute_values)) + error_occurred(ErrorClass.FormatError, "Improper library node"); } handler.leave_library(); } } - + void handle_tracks(XmlElement tracks) { foreach (XmlElement child in tracks.children) { handle_track(child); @@ -214,34 +221,38 @@ class ProjectBuilder : Object { handle_preference(child); } } + void handle_maps(XmlElement maps) { foreach (XmlElement child in maps.children) { handle_map(child); } } + public bool check_project(XmlElement? root) { if (root == null) { - error_occurred("Invalid XML file!"); + error_occurred(ErrorClass.LoadFailure, "Invalid XML file!"); return false; } - + if (check_name("marina", root) && handler.commit_marina(root.attribute_names, root.attribute_values)) { if (root.children.size != 3 && root.children.size != 4) { - error_occurred("Improper number of children!"); + error_occurred(ErrorClass.LoadFailure, "Improper number of children!"); return false; } - + if (!check_name("library", root.children[0]) || !check_name("tracks", root.children[1]) || - !check_name("preferences", root.children[2])) + !check_name("preferences", root.children[2])) { return false; - + } + if (root.children.size == 4 && !check_name("maps", root.children[3])) { return false; } - } else + } else { return false; + } return true; } @@ -252,22 +263,22 @@ class ProjectBuilder : Object { if (root.children.size == 4) { handle_maps(root.children[3]); } - + handler.leave_marina(); } } public class XmlElement { public string name { get; private set; } - + public string[] attribute_names; - + public string[] attribute_values; - + public Gee.ArrayList<XmlElement> children { get { return _children; } } - + public weak XmlElement? parent { get; private set; } - + private Gee.ArrayList<XmlElement> _children; public XmlElement(string name, string[] attribute_names, string[] attribute_values, XmlElement? parent) { @@ -278,7 +289,7 @@ public class XmlElement { this.parent = parent; this._children = new Gee.ArrayList<XmlElement>(); } - + public void add_child(XmlElement child_element) { _children.add(child_element); } @@ -295,20 +306,20 @@ public class ProjectLoader : Object { public signal void load_started(string filename); public signal void load_complete(); - public signal void load_error(string error); - + public signal void load_error(ErrorClass error_class, string error); + public ProjectLoader(LoaderHandler loader_handler, string? file_name) { this.file_name = file_name; this.loader_handler = loader_handler; loader_handler.load_error.connect(on_load_error); loader_handler.complete.connect(on_handler_complete); } - - void on_load_error(string error) { + + void on_load_error(ErrorClass error_class, string error) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_load_error"); - load_error(error); + load_error(error_class, error); } - + void on_handler_complete() { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_handler_complete"); handler_completed = true; @@ -317,23 +328,23 @@ public class ProjectLoader : Object { load_complete(); } } - + public void load() { try { FileUtils.get_contents(file_name, out text, out text_len); } catch (FileError e) { emit(this, Facility.LOADING, Level.MEDIUM, "error loading %s: %s".printf(file_name, e.message)); - load_error(e.message); + load_error(ErrorClass.LoadFailure, e.message); load_complete(); return; } emit(this, Facility.LOADING, Level.VERBOSE, "Building tree for %s".printf(file_name)); XmlTreeLoader tree_loader = new XmlTreeLoader(text); - + ProjectBuilder builder = new ProjectBuilder(loader_handler); builder.error_occurred.connect(on_load_error); - + if (builder.check_project(tree_loader.root)) { emit(this, Facility.LOADING, Level.VERBOSE, "project checked out. starting load"); load_started(file_name); diff --git a/src/marina/Ruler.vala b/src/marina/Ruler.vala index 7918e0e..02e8e50 100644 --- a/src/marina/Ruler.vala +++ b/src/marina/Ruler.vala @@ -22,7 +22,7 @@ public class Ruler : Gtk.DrawingArea { int frame = provider.get_start_token(x); Cairo.Context context = Gdk.cairo_create(window); - + context.save(); Gdk.cairo_set_source_color(context, parse_color("#777")); context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height); context.fill(); @@ -42,7 +42,8 @@ public class Ruler : Gtk.DrawingArea { string? display_string = provider.get_display_string(frame); if (display_string != null) { - Pango.Layout layout = create_pango_layout(display_string); + Pango.Layout layout = Pango.cairo_create_layout(context); + layout.set_text(display_string, (int) display_string.length); int w; int h; @@ -52,8 +53,9 @@ public class Ruler : Gtk.DrawingArea { if (text_pos < 0) { text_pos = 0; } - - Gdk.draw_layout(window, style.white_gc, text_pos, 7, layout); + context.move_to(text_pos, 7); + context.set_source_rgb(1, 1, 1); + Pango.cairo_show_layout(context, layout); } frame = provider.get_next_position(frame); @@ -61,6 +63,7 @@ public class Ruler : Gtk.DrawingArea { context.set_antialias(old_antialias); context.set_line_width(1.0); context.stroke(); + context.restore(); return true; } diff --git a/src/marina/StatusBar.vala b/src/marina/StatusBar.vala index 4e29c83..1d03453 100644 --- a/src/marina/StatusBar.vala +++ b/src/marina/StatusBar.vala @@ -30,11 +30,19 @@ public class StatusBar : Gtk.DrawingArea { window.draw_rectangle(style.bg_gc[(int) Gtk.StateType.NORMAL], true, allocation.x, allocation.y, allocation.width, allocation.height); + Cairo.Context context = Gdk.cairo_create(window); + context.save(); + Pango.Layout layout = Pango.cairo_create_layout(context); + string time = provider.get_time_string(current_position); + Gdk.Color color = style.text[Gtk.StateType.NORMAL]; + context.set_source_rgb(color.red, color.green, color.blue); - Pango.Layout layout = create_pango_layout(time); - Gdk.draw_layout(window, style.white_gc, allocation.x + 4, allocation.y + 2, layout); - + layout.set_font_description(style.font_desc); + layout.set_text(time, (int)time.length); + context.move_to(allocation.x + 4, allocation.y + 2); + Pango.cairo_show_layout(context, layout); + context.restore(); return true; } } diff --git a/src/marina/TimeSystem.vala b/src/marina/TimeSystem.vala index 77a4411..7a3de47 100644 --- a/src/marina/TimeSystem.vala +++ b/src/marina/TimeSystem.vala @@ -24,6 +24,8 @@ public interface TimeSystem : Object { public abstract int xsize_to_frame(int xsize); public abstract string get_time_string(int64 time); public abstract string get_time_duration(int64 time); + public abstract int64 next_tick(int64 time); + public abstract int64 previous_tick(int64 time); } public abstract class TimeSystemBase : Object { @@ -36,11 +38,11 @@ public abstract class TimeSystemBase : Object { const int BORDER = 4; // TODO: should use same value as timeline. will happen when this gets // refactored back into view code. - abstract int[] get_timeline_seconds(); - abstract int correct_sub_second_value(float seconds, int div, int fps); + protected abstract int[] get_timeline_seconds(); + protected abstract int correct_sub_second_value(float seconds, int div, int fps); protected int correct_seconds_value (float seconds, int div, int fps) { - if (seconds < 1.0f) { + if (seconds < 1) { return correct_sub_second_value(seconds, div, fps); } @@ -78,6 +80,10 @@ public abstract class TimeSystemBase : Object { return (int64) ((float)(size * Gst.SECOND) / pixels_per_second); } + public int64 xsize_to_time_double(double size) { + return (int64)((size * Gst.SECOND) / pixels_per_second); + } + public int time_to_xsize(int64 time) { return (int) (time * pixels_per_second / Gst.SECOND); } @@ -89,6 +95,11 @@ public abstract class TimeSystemBase : Object { pos++; return pos; } + + public int64 tick_increment(int64 the_time, int64 boundary, int increment) { + int64 result = ((the_time / boundary) + increment) * boundary; + return result < 0 ? 0 : result; + } } public class TimecodeTimeSystem : TimeSystem, TimeSystemBase { @@ -100,7 +111,7 @@ public class TimecodeTimeSystem : TimeSystem, TimeSystemBase { public Fraction frame_rate_fraction = Fraction(30000, 1001); - override int correct_sub_second_value(float seconds, int div, int fps) { + protected override int correct_sub_second_value(float seconds, int div, int fps) { int frames = (int)(fps * seconds); if (frames == 0) { return 1; @@ -130,6 +141,17 @@ public class TimecodeTimeSystem : TimeSystem, TimeSystemBase { // Timecode is already zero-based return get_time_string(the_time); } + + public int64 next_tick(int64 the_time) { + return tick_increment(the_time, + xsize_to_time_double(large_pixel_frames * pixels_per_frame), 1); + } + + public int64 previous_tick(int64 the_time) { + return tick_increment(the_time, + xsize_to_time_double(large_pixel_frames * pixels_per_frame), -1); + } + public void calculate_pixel_step(float inc, float pixel_min, float pixel_div) { int pixels_per_large = 300; int pixels_per_medium = 50; @@ -143,11 +165,12 @@ public class TimecodeTimeSystem : TimeSystem, TimeSystemBase { pixels_per_second = pixel_min * GLib.Math.powf(pixel_div, pixel_percentage); int fps = frame_rate_fraction.nearest_int(); - large_pixel_frames = correct_seconds_value(pixels_per_large / pixels_per_second, 0, fps); - medium_pixel_frames = correct_seconds_value(pixels_per_medium / pixels_per_second, - large_pixel_frames, fps); - small_pixel_frames = correct_seconds_value(pixels_per_small / pixels_per_second, - medium_pixel_frames, fps); + float seconds = pixels_per_large / pixels_per_second; + large_pixel_frames = correct_seconds_value(seconds, 0, fps); + seconds = pixels_per_medium / pixels_per_second; + medium_pixel_frames = correct_seconds_value(seconds, large_pixel_frames, fps); + seconds = pixels_per_small / pixels_per_second; + small_pixel_frames = correct_seconds_value(seconds, medium_pixel_frames, fps); if (small_pixel_frames == medium_pixel_frames) { int i = medium_pixel_frames; @@ -203,7 +226,7 @@ public class TimecodeTimeSystem : TimeSystem, TimeSystemBase { } } - override int[] get_timeline_seconds() { + protected override int[] get_timeline_seconds() { return { 1, 2, 5, 10, 15, 20, 30, 60, 120, 300, 600, 900, 1200, 1800, 3600 }; } } @@ -261,21 +284,20 @@ public class BarBeatTimeSystem : TimeSystem, TimeSystemBase { geometry_changed(); } - override int correct_sub_second_value(float bars, int div, int unused) { + protected override int correct_sub_second_value(float bars, int div, int unused) { int sixteenths = (int)(sixteenths_per_bar * bars); if (sixteenths == 0) { return 1; } - if (sixteenths > sixteenths_per_beat) { - return sixteenths_per_beat; - } - - if (sixteenths > 2) { - return 2; - } - + int current_try = sixteenths_per_bar / 2; + do { + if (current_try < sixteenths) { + return current_try; + } + current_try = current_try / 2; + } while (current_try > 1); return 1; } @@ -324,10 +346,47 @@ public class BarBeatTimeSystem : TimeSystem, TimeSystemBase { return beats_to_string(total_beats, true, true); } + public int64 next_tick(int64 the_time) { + return tick_increment(the_time, + xsize_to_time_double(large_pixel_sixteenth * pixels_per_sixteenth), 1); + } + + public int64 previous_tick(int64 the_time) { + return tick_increment(the_time, + xsize_to_time_double(large_pixel_sixteenth * pixels_per_sixteenth), -1); + } + + void constrain_seconds_value(out int pixel_sixteenth, int pixels_per, int min_pixels, + int max_pixels, float pixels_per_bar) { + float pixels_per_size = 0; + int count = 0; + do { + pixel_sixteenth = correct_seconds_value( + pixels_per / pixels_per_bar, 0, sixteenths_per_bar); + pixels_per_size = pixel_sixteenth * pixels_per_sixteenth; + if (pixels_per_size < min_pixels) { + pixels_per = (int) (pixels_per * 1.05); + } else if (pixels_per_size > max_pixels) { + pixels_per = (int) (pixels_per * 0.95); + } else { + break; + } + ++count; + } while (count < 0); + } + public void calculate_pixel_step(float inc, float pixel_min, float pixel_div) { - int pixels_per_large = 80; - int pixels_per_medium = 40; - int pixels_per_small = 20; + int pixels_per_large = 120; + int min_pixels_per_large = 80; + int max_pixels_per_large = 160; + + int pixels_per_medium = 60; + int min_pixels_per_medium = 30; + int max_pixels_per_medium = 80; + + int pixels_per_small = 15; + int min_pixels_per_small = 10; + int max_pixels_per_small = 20; pixel_percentage += inc; if (pixel_percentage < 0.0f) { @@ -338,25 +397,17 @@ public class BarBeatTimeSystem : TimeSystem, TimeSystemBase { pixels_per_second = pixel_min * GLib.Math.powf(pixel_div, pixel_percentage); float pixels_per_bar = pixels_per_second / bars_per_second; - large_pixel_sixteenth = correct_seconds_value( - pixels_per_large / pixels_per_bar, 0, sixteenths_per_bar); - - medium_pixel_sixteenth = correct_seconds_value(pixels_per_medium / pixels_per_bar, - large_pixel_sixteenth, sixteenths_per_bar); - small_pixel_sixteenth = correct_seconds_value(pixels_per_small / pixels_per_bar, - medium_pixel_sixteenth, sixteenths_per_bar); - if (small_pixel_sixteenth == medium_pixel_sixteenth) { - int i = medium_pixel_sixteenth; + pixels_per_sixteenth = pixels_per_bar / (float) sixteenths_per_bar; - while (--i > 0) { - if ((medium_pixel_sixteenth % i) == 0) { - small_pixel_sixteenth = i; - break; - } - } + constrain_seconds_value(out large_pixel_sixteenth, pixels_per_large, + min_pixels_per_large, max_pixels_per_large, pixels_per_bar); + constrain_seconds_value(out medium_pixel_sixteenth, pixels_per_medium, + min_pixels_per_medium, max_pixels_per_medium, pixels_per_bar); + constrain_seconds_value(out small_pixel_sixteenth, pixels_per_small, + min_pixels_per_small, max_pixels_per_small, pixels_per_bar); + if (small_pixel_sixteenth * pixels_per_sixteenth < min_pixels_per_small) { + small_pixel_sixteenth = medium_pixel_sixteenth; } - - pixels_per_sixteenth = pixels_per_bar / (float) sixteenths_per_bar; pixel_snap_time = xsize_to_time(PIXEL_SNAP_INTERVAL); } @@ -399,7 +450,7 @@ public class BarBeatTimeSystem : TimeSystem, TimeSystemBase { } } - override int[] get_timeline_seconds() { + protected override int[] get_timeline_seconds() { return timeline_bars; } } diff --git a/src/marina/TrackView.vala b/src/marina/TrackView.vala index 4f8e82d..d4dc9a2 100644 --- a/src/marina/TrackView.vala +++ b/src/marina/TrackView.vala @@ -24,7 +24,36 @@ class TrackViewConcrete : TrackView, Gtk.Fixed { track.clip_removed.connect(on_clip_removed); } - override void size_request(out Gtk.Requisition requisition) { + public override bool expose_event(Gdk.EventExpose event) { + Cairo.Context context = Gdk.cairo_create(window); + Cairo.Antialias old_antialias = context.get_antialias(); + + context.set_antialias(Cairo.Antialias.NONE); + context.set_source_rgb(0.3, 0.3, 0.3); + double old_line_width = context.get_line_width(); + + context.set_line_width(2); + int64 current_time = timeline.provider.xpos_to_time(event.area.x); + int64 stop = timeline.provider.xpos_to_time(event.area.x + event.area.width); + while (current_time <= stop) { + current_time = timeline.provider.next_tick(current_time); + int x = timeline.provider.time_to_xpos(current_time); + context.move_to(x, allocation.y); + context.line_to(x, allocation.y + allocation.height - 1); + } + context.stroke(); + context.set_line_width(1); + context.set_source_rgb(0.5, 0.5, 0.5); + context.move_to(event.area.x, allocation.y + allocation.height - 1); + context.line_to(event.area.x + event.area.width, allocation.y + allocation.height - 1); + + context.stroke(); + context.set_antialias(old_antialias); + context.set_line_width(old_line_width); + return base.expose_event(event); + } + + protected override void size_request(out Gtk.Requisition requisition) { base.size_request(out requisition); requisition.height = TrackHeight; requisition.width += TimeLine.BORDER; // right margin @@ -55,7 +84,7 @@ class TrackViewConcrete : TrackView, Gtk.Fixed { timeline.track_changed(); clip_view_added(view); if (select) { - view.selection_request(view, false); + view.selection_request(ClipView.SelectionType.NONE); } } @@ -74,7 +103,7 @@ class TrackViewConcrete : TrackView, Gtk.Fixed { clip_view.show(); } - void on_trim_begin(ClipView clip_view) { + void on_trim_begin(ClipView clip_view, Gdk.WindowEdge edge) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_trim_begin"); move_to_top(clip_view); } @@ -121,7 +150,7 @@ class TrackViewConcrete : TrackView, Gtk.Fixed { } */ - override bool button_press_event(Gdk.EventButton e) { + protected override bool button_press_event(Gdk.EventButton e) { if (e.type != Gdk.EventType.BUTTON_PRESS && e.type != Gdk.EventType.2BUTTON_PRESS && e.type != Gdk.EventType.3BUTTON_PRESS) diff --git a/src/marina/clip.vala b/src/marina/clip.vala index e12c683..3462870 100644 --- a/src/marina/clip.vala +++ b/src/marina/clip.vala @@ -8,11 +8,6 @@ using Logging; namespace Model { -public enum MediaType { - AUDIO, - VIDEO -} - public class Gap { public int64 start; public int64 end; @@ -31,147 +26,12 @@ public class Gap { } } -public class ClipFile : Object { - public string filename; - int64 _length; - public int64 length { - public get { - if (!online) { - warning("retrieving length while clip offline"); - } - return _length; - } - - public set { - _length = value; - } - } - - bool online; - - public Gst.Caps video_caps; // or null if no video - public Gst.Caps audio_caps; // or null if no audio - public Gdk.Pixbuf thumbnail = null; - - public signal void updated(); - - public ClipFile(string filename, int64 length = 0) { - this.filename = filename; - this.length = length; - online = false; - } - - public bool is_online() { - return online; - } - - public void set_online(bool o) { - emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "set_online"); - online = o; - updated(); - } - - public void set_thumbnail(Gdk.Pixbuf b) { - // TODO: Investigate this - // 56x56 - 62x62 icon size does not work for some reason when - // we display the thumbnail while dragging the clip. - - thumbnail = b.scale_simple(64, 44, Gdk.InterpType.BILINEAR); - } - - public bool has_caps_structure(MediaType m) { - if (m == MediaType.AUDIO) { - if (audio_caps == null || audio_caps.get_size() < 1) - return false; - } else if (m == MediaType.VIDEO) { - if (video_caps == null || video_caps.get_size() < 1) - return false; - } - return true; - } - - public bool is_of_type(MediaType t) { - if (t == MediaType.VIDEO) - return video_caps != null; - return audio_caps != null; - } - - bool get_caps_structure(MediaType m, out Gst.Structure s) { - if (!has_caps_structure(m)) - return false; - if (m == MediaType.AUDIO) { - s = audio_caps.get_structure(0); - } else if (m == MediaType.VIDEO) { - s = video_caps.get_structure(0); - } - return true; - } - - public bool get_frame_rate(out Fraction rate) { - Gst.Structure structure; - if (!get_caps_structure(MediaType.VIDEO, out structure)) - return false; - return structure.get_fraction("framerate", out rate.numerator, out rate.denominator); - } - - public bool get_dimensions(out int w, out int h) { - Gst.Structure s; - - if (!get_caps_structure(MediaType.VIDEO, out s)) - return false; - - return s.get_int("width", out w) && s.get_int("height", out h); - } - - public bool get_sample_rate(out int rate) { - Gst.Structure s; - if (!get_caps_structure(MediaType.AUDIO, out s)) - return false; - - return s.get_int("rate", out rate); - } - - public bool get_video_format(out uint32 fourcc) { - Gst.Structure s; - - if (!get_caps_structure(MediaType.VIDEO, out s)) - return false; - - return s.get_fourcc("format", out fourcc); - } - - public bool get_num_channels(out int channels) { - Gst.Structure s; - if (!get_caps_structure(MediaType.AUDIO, out s)) { - return false; - } - - return s.get_int("channels", out channels); - } - - public bool get_num_channels_string(out string s) { - int i; - if (!get_num_channels(out i)) - return false; - - if (i == 1) - s = "Mono"; - else if (i == 2) - s = "Stereo"; - else if ((i % 2) == 0) - s = "Surround %d.1".printf(i - 1); - else - s = "%d".printf(i); - return true; - } -} - public abstract class Fetcher : Object { protected Gst.Element filesrc; protected Gst.Element decodebin; protected Gst.Pipeline pipeline; - public ClipFile clipfile; + public MediaFile mediafile; public string error_string; protected abstract void on_pad_added(Gst.Pad pad); @@ -202,12 +62,13 @@ public abstract class Fetcher : Object { } public class ClipFetcher : Fetcher { - public signal void clipfile_online(bool online); + public signal void mediafile_online(bool online); public ClipFetcher(string filename) throws Error { - clipfile = new ClipFile(filename); + ClassFactory class_factory = ClassFactory.get_class_factory(); + mediafile = class_factory.get_media_file(filename, 0); - clipfile_online.connect(clipfile.set_online); + mediafile_online.connect(mediafile.set_online); filesrc = make_element("filesrc"); filesrc.set("location", filename); @@ -234,7 +95,7 @@ public class ClipFetcher : Fetcher { pipeline.set_state(Gst.State.PLAYING); } - public string get_filename() { return clipfile.filename; } + public string get_filename() { return mediafile.filename; } protected override void on_pad_added(Gst.Pad pad) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_pad_added"); @@ -276,7 +137,7 @@ public class ClipFetcher : Fetcher { protected override void on_state_change(Gst.Bus bus, Gst.Message message) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_state_change"); - if (message.src() != pipeline) + if (message.src != pipeline) return; Gst.State old_state; @@ -290,12 +151,12 @@ public class ClipFetcher : Fetcher { if (new_state == Gst.State.PLAYING) { Gst.Pad? pad = get_pad("video"); if (pad != null) { - clipfile.video_caps = pad.caps; + mediafile.set_caps(MediaType.VIDEO, pad.caps); } pad = get_pad("audio"); if (pad != null) { - clipfile.audio_caps = pad.caps; + mediafile.set_caps(MediaType.AUDIO, pad.caps); } Gst.Format format = Gst.Format.TIME; @@ -305,9 +166,9 @@ public class ClipFetcher : Fetcher { do_error("Can't fetch length"); return; } - clipfile.length = length; + mediafile.length = length; - clipfile_online(true); + mediafile_online(true); pipeline.set_state(Gst.State.NULL); } else if (new_state == Gst.State.NULL) { ready(this); @@ -322,8 +183,8 @@ public class ThumbnailFetcher : Fetcher { bool done_seek; bool have_thumbnail; - public ThumbnailFetcher(ClipFile f, int64 time) throws Error { - clipfile = f; + public ThumbnailFetcher(MediaFile f, int64 time) throws Error { + mediafile = f; seek_position = time; SingleDecodeBin single_bin = new SingleDecodeBin ( @@ -360,7 +221,7 @@ public class ThumbnailFetcher : Fetcher { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_have_thumbnail"); if (done_seek) { have_thumbnail = true; - clipfile.set_thumbnail(buf); + mediafile.set_thumbnail(buf); } } @@ -375,7 +236,7 @@ public class ThumbnailFetcher : Fetcher { protected override void on_state_change(Gst.Bus bus, Gst.Message message) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_state_change"); - if (message.src() != pipeline) + if (message.src != pipeline) return; Gst.State new_state; @@ -402,12 +263,26 @@ public class ThumbnailFetcher : Fetcher { } public class Clip : Object { - public ClipFile clipfile; + public MediaFile mediafile; public MediaType type; // TODO: If a clip is being recorded, we don't want to set duration in the MediaClip file. // Address when handling multiple track recording. This is an ugly hack. public bool is_recording; public string name; + bool _is_selected = false; + public bool is_selected { + get { + return _is_selected; + } + + set { + if (value != _is_selected) { + _is_selected = value; + selection_changed(); + } + } + } + int64 _start; public int64 start { get { @@ -416,10 +291,13 @@ public class Clip : Object { set { _start = value; + if (_start < 0) { + _start = 0; + } if (connected) { start_changed(_start); } - moved(this); + moved(); } } @@ -443,9 +321,9 @@ public class Clip : Object { } if (!is_recording) { - if (value + _media_start > clipfile.length) { + if (value + _media_start > mediafile.length) { // saturating the duration - value = clipfile.length - media_start; + value = mediafile.length - media_start; } } @@ -453,7 +331,7 @@ public class Clip : Object { if (connected) { duration_changed(_duration); } - moved(this); + moved(); } } @@ -463,30 +341,31 @@ public class Clip : Object { get { return start + duration; } } - public signal void moved(Clip clip); - public signal void updated(Clip clip); + public signal void moved(); + public signal void updated(); public signal void media_start_changed(int64 media_start); public signal void duration_changed(int64 duration); public signal void start_changed(int64 start); - public signal void removed(Clip clip); + public signal void removed(); + public signal void selection_changed(); - public Clip(ClipFile clipfile, MediaType t, string name, + public Clip(MediaFile mediafile, MediaType t, string name, int64 start, int64 media_start, int64 duration, bool is_recording) { this.is_recording = is_recording; - this.clipfile = clipfile; + this.mediafile = mediafile; this.type = t; this.name = name; - this.connected = clipfile.is_online(); + this.connected = mediafile.is_online(); this.set_media_start_duration(media_start, duration); this.start = start; - clipfile.updated.connect(on_clipfile_updated); + mediafile.updated.connect(on_mediafile_updated); } public void gnonlin_connect() { connected = true; } public void gnonlin_disconnect() { connected = false; } - void on_clipfile_updated(ClipFile f) { - emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clipfile_updated"); + void on_mediafile_updated(MediaFile f) { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_mediafile_updated"); if (f.is_online()) { if (!connected) { connected = true; @@ -502,7 +381,7 @@ public class Clip : Object { connected = false; } } - updated(this); + updated(); } public bool overlap_pos(int64 start, int64 length) { @@ -535,13 +414,13 @@ public class Clip : Object { } public Clip copy() { - return new Clip(clipfile, type, name, start, media_start, duration, false); + return new Clip(mediafile, type, name, start, media_start, duration, false); } public bool is_trimmed() { - if (!clipfile.is_online()) + if (!mediafile.is_online()) return false; - return duration != clipfile.length; + return duration != mediafile.length; } public void trim(int64 delta, Gdk.WindowEdge edge) { @@ -573,9 +452,9 @@ public class Clip : Object { duration = 0; } - if (clipfile.is_online() && media_start + duration > clipfile.length) { + if (mediafile.is_online() && media_start + duration > mediafile.length) { // We are saturating the value - media_start = clipfile.length - duration; + media_start = mediafile.length - duration; } _media_start = media_start; @@ -586,7 +465,7 @@ public class Clip : Object { duration_changed(_duration); } - moved(this); + moved(); } public void save(FileStream f, int id) { diff --git a/src/marina/command.vala b/src/marina/command.vala index c2c5083..7c93764 100644 --- a/src/marina/command.vala +++ b/src/marina/command.vala @@ -202,24 +202,24 @@ public class ClipSplitCommand : Command { } } -public class ClipFileDeleteCommand : Command { - ClipFile clipfile; +public class MediaFileDeleteCommand : Command { + MediaFile mediafile; Project project; - public ClipFileDeleteCommand(Project p, ClipFile cf) { - clipfile = cf; + public MediaFileDeleteCommand(Project p, MediaFile cf) { + mediafile = cf; project = p; } public override void apply() { - project._remove_clipfile(clipfile); + project._remove_mediafile(mediafile); } public override void undo() { try { - project._add_clipfile(clipfile); + project._add_mediafile(mediafile); } catch (Error e) { - project.error_occurred("Could not add clipfile.", e.message); + project.error_occurred("Could not add mediafile.", e.message); } } @@ -350,24 +350,24 @@ public class BpmCommand : Command { } public class AddClipCommand : Command { - ClipFile clip_file; + MediaFile media_file; Project project; - public AddClipCommand(Project project, ClipFile clip_file) { + public AddClipCommand(Project project, MediaFile media_file) { this.project = project; - this.clip_file = clip_file; + this.media_file = media_file; } public override void apply() { try { - project._add_clipfile(clip_file); + project._add_mediafile(media_file); } catch (GLib.Error error) { project.error_occurred("Error importing", "An error occurred importing this file."); } } public override void undo() { - project._remove_clipfile(clip_file); + project._remove_mediafile(media_file); } public override bool merge(Command command) { diff --git a/src/marina/import.vala b/src/marina/import.vala index d5110ff..4cdfd5d 100644 --- a/src/marina/import.vala +++ b/src/marina/import.vala @@ -45,7 +45,7 @@ public class ClipImporter : MultiFileProgressInterface, Object { Gee.ArrayList<string> queued_filenames = new Gee.ArrayList<string>(); Gee.ArrayList<string> no_import_formats = new Gee.ArrayList<string>(); - public signal void clip_complete(ClipFile f); + public signal void clip_complete(MediaFile f); public signal void importing_started(int num_clips); public signal void error_occurred(string error); @@ -136,11 +136,12 @@ public class ClipImporter : MultiFileProgressInterface, Object { void do_import_complete() throws Error{ if (import_state == ImportState.IMPORTING) { - our_fetcher.clipfile.filename = append_extension( + our_fetcher.mediafile.filename = append_extension( queued_filenames[current_file_importing], "mov"); - clip_complete(our_fetcher.clipfile); - } else - total_time += our_fetcher.clipfile.length; + clip_complete(our_fetcher.mediafile); + } else if (our_fetcher.mediafile.is_online()) { + total_time += our_fetcher.mediafile.length; + } current_file_importing++; @@ -155,9 +156,9 @@ public class ClipImporter : MultiFileProgressInterface, Object { //for now, use the clip as is return false; /* - if (f.clipfile.is_of_type(MediaType.VIDEO)) { + if (f.mediafile.is_of_type(MediaType.VIDEO)) { uint32 format; - if (f.clipfile.get_video_format(out format)) { + if (f.mediafile.get_video_format(out format)) { foreach (string s in no_import_formats) { if (format == *(uint32*)s) { return false; @@ -181,8 +182,8 @@ public class ClipImporter : MultiFileProgressInterface, Object { if (need_to_import(f)) { string checksum; - if (md5_checksum_on_file(f.clipfile.filename, out checksum)) { - string base_filename = import_directory + isolate_filename(f.clipfile.filename); + if (md5_checksum_on_file(f.mediafile.filename, out checksum)) { + string base_filename = import_directory + isolate_filename(f.mediafile.filename); int index = 0; string new_filename = base_filename; @@ -194,7 +195,7 @@ public class ClipImporter : MultiFileProgressInterface, Object { filenames[current_file_importing] = append_extension(new_filename, "mov"); current_file_importing--; - total_time -= f.clipfile.length; + total_time -= f.mediafile.length; break; } index++; @@ -208,9 +209,9 @@ public class ClipImporter : MultiFileProgressInterface, Object { } } } else - error("Cannot get md5 checksum for file %s!", f.clipfile.filename); + error("Cannot get md5 checksum for file %s!", f.mediafile.filename); } else { - clip_complete(f.clipfile); + clip_complete(f.mediafile); } do_import_complete(); } catch (Error e) { @@ -219,7 +220,7 @@ public class ClipImporter : MultiFileProgressInterface, Object { } void do_import(ClipFetcher f) throws Error { - file_updated(f.clipfile.filename, current_file_importing); + file_updated(f.mediafile.filename, current_file_importing); previous_time = 0; our_fetcher = f; @@ -242,14 +243,14 @@ public class ClipImporter : MultiFileProgressInterface, Object { pipeline.add_many(mux, filesink); - if (f.clipfile.is_of_type(MediaType.VIDEO)) { + if (f.mediafile.get_caps(MediaType.VIDEO) != null) { video_convert = make_element("ffmpegcolorspace"); pipeline.add(video_convert); video_decoder = new SingleDecodeBin(Gst.Caps.from_string( "video/x-raw-yuv"), "videodecodebin", - f.clipfile.filename); + f.mediafile.filename); video_decoder.pad_added.connect(on_pad_added); pipeline.add(video_decoder); @@ -257,7 +258,7 @@ public class ClipImporter : MultiFileProgressInterface, Object { if (!video_convert.link(mux)) error("do_import: Cannot link video converter to mux!"); } - if (f.clipfile.is_of_type(MediaType.AUDIO)) { + if (f.mediafile.get_caps(MediaType.AUDIO) != null) { audio_convert = make_element("audioconvert"); pipeline.add(audio_convert); @@ -265,7 +266,7 @@ public class ClipImporter : MultiFileProgressInterface, Object { // if you need to import ogg and other float flavors. see bug 2055 audio_decoder = new SingleDecodeBin( Gst.Caps.from_string("audio/x-raw-int"), - "audiodecodebin", f.clipfile.filename); + "audiodecodebin", f.mediafile.filename); audio_decoder.pad_added.connect(on_pad_added); pipeline.add(audio_decoder); @@ -324,7 +325,7 @@ public class ClipImporter : MultiFileProgressInterface, Object { void on_state_changed(Gst.Bus b, Gst.Message m) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_state_changed"); - if (m.src() != pipeline) + if (m.src != pipeline) return; Gst.State old_state; @@ -341,13 +342,13 @@ public class ClipImporter : MultiFileProgressInterface, Object { if (new_state == Gst.State.PAUSED) { if (!import_done) { if (video_pad != null) { - our_fetcher.clipfile.video_caps = video_pad.caps; + our_fetcher.mediafile.set_caps(MediaType.VIDEO, video_pad.caps); } if (audio_pad != null) { - our_fetcher.clipfile.audio_caps = audio_pad.caps; + our_fetcher.mediafile.set_caps(MediaType.AUDIO, audio_pad.caps); } emit(this, Facility.IMPORT, Level.VERBOSE, - "Got clipfile info for: %s".printf(our_fetcher.clipfile.filename)); + "Got mediafile info for: %s".printf(our_fetcher.mediafile.filename)); } } else if (new_state == Gst.State.NULL) { if (import_state == ImportState.CANCELLED) { @@ -399,23 +400,23 @@ public class LibraryImporter : Object { project.error_occurred("Error importing", "An error occurred importing this file."); } - protected virtual void append_existing_clipfile(ClipFile f) { + protected virtual void append_existing_mediafile(MediaFile f) { } - protected virtual void on_clip_complete(ClipFile f) { + protected virtual void on_clip_complete(MediaFile f) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_complete"); - ClipFile cf = project.find_clipfile(f.filename); + MediaFile cf = project.find_mediafile(f.filename); if (cf == null) { - project.add_clipfile(f); + project.add_mediafile(f); } } public void add_file(string filename) throws Error { - ClipFile cf = project.find_clipfile(filename); + MediaFile cf = project.find_mediafile(filename); if (cf != null) - append_existing_clipfile(cf); + append_existing_mediafile(cf); else importer.add_filename(filename); } @@ -437,7 +438,7 @@ public class TimelineImporter : LibraryImporter { this.both_tracks = both_tracks; } - void add_to_both(ClipFile clip_file) { + void add_to_both(MediaFile media_file) { if (both_tracks) { Track other_track; if (track is Model.VideoTrack) { @@ -446,19 +447,19 @@ public class TimelineImporter : LibraryImporter { other_track = project.find_video_track(); } if (other_track != null) { - project.add(other_track, clip_file, time_to_add); + project.add(other_track, media_file, time_to_add); } } } - protected override void append_existing_clipfile(ClipFile f) { + protected override void append_existing_mediafile(MediaFile f) { project.undo_manager.start_transaction("Create Clip"); project.add(track, f, time_to_add); add_to_both(f); project.undo_manager.end_transaction("Create Clip"); } - protected override void on_clip_complete(ClipFile f) { + protected override void on_clip_complete(MediaFile f) { project.undo_manager.start_transaction("Create Clip"); base.on_clip_complete(f); project.add(track, f, time_to_add); diff --git a/src/marina/project.vala b/src/marina/project.vala index eb738d8..983a0c1 100644 --- a/src/marina/project.vala +++ b/src/marina/project.vala @@ -14,7 +14,7 @@ public class MediaLoaderHandler : LoaderHandler { protected Track current_track; Gee.ArrayList<ClipFetcher> clipfetchers = new Gee.ArrayList<ClipFetcher>(); - int num_clipfiles_complete; + int num_mediafiles_complete; public MediaLoaderHandler(Project the_project) { this.the_project = the_project; @@ -25,17 +25,18 @@ public class MediaLoaderHandler : LoaderHandler { int number_of_attributes = attr_names.length; if (number_of_attributes != 1 || attr_names[0] != "version") { - load_error("Missing version information"); + load_error(ErrorClass.LoadFailure, "Missing version information"); return false; } - if (the_project.get_file_version() < attr_values[0].to_int()) { - load_error("Version mismatch! (File Version: %d, App Version: %d)".printf( - the_project.get_file_version(), attr_values[0].to_int())); + if (the_project.get_file_version() < int.parse(attr_values[0])) { + load_error(ErrorClass.LoadFailure, + "Version mismatch! (File Version: %d, App Version: %d)".printf( + the_project.get_file_version(), int.parse(attr_values[0]) )); return false; } - num_clipfiles_complete = 0; + num_mediafiles_complete = 0; return true; } @@ -45,17 +46,17 @@ public class MediaLoaderHandler : LoaderHandler { return true; if (attr_names[0] != "framerate") { - load_error("Missing framerate tag"); + load_error(ErrorClass.FormatError, "Missing framerate tag"); return false; } string[] arr = attr_values[0].split("/"); if (arr.length != 2) { - load_error("Invalid framerate attribute"); + load_error(ErrorClass.FormatError, "Invalid framerate attribute"); return false; } - the_project.set_default_framerate(Fraction(arr[0].to_int(), arr[1].to_int())); + the_project.set_default_framerate(Fraction(int.parse(arr[0]), int.parse(arr[1]))); return true; } @@ -79,12 +80,12 @@ public class MediaLoaderHandler : LoaderHandler { } if (name == null) { - load_error("Missing track name"); + load_error(ErrorClass.FormatError, "Missing track name"); return false; } if (type == null) { - load_error("Missing track type"); + load_error(ErrorClass.FormatError, "Missing track type"); return false; } @@ -96,13 +97,19 @@ public class MediaLoaderHandler : LoaderHandler { for (int i = 0; i < number_of_attributes; ++i) { switch(attr_names[i]) { case "panorama": - audio_track._set_pan(attr_values[i].to_double()); + audio_track._set_pan(double.parse(attr_values[i])); break; case "volume": - audio_track._set_volume(attr_values[i].to_double()); + audio_track._set_volume(double.parse(attr_values[i])); break; case "channels": - audio_track.set_default_num_channels(attr_values[i].to_int()); + audio_track.set_default_num_channels(int.parse(attr_values[i])); + break; + case "solo": + audio_track.solo = bool.parse(attr_values[i]); + break; + case "mute": + audio_track.mute = bool.parse(attr_values[i]); break; default: break; @@ -134,58 +141,58 @@ public class MediaLoaderHandler : LoaderHandler { for (int i = 0; i < number_of_attributes; i++) { switch (attr_names[i]) { case "id": - id = attr_values[i].to_int(); + id = int.parse(attr_values[i]); break; case "name": clip_name = attr_values[i]; break; case "start": - start = attr_values[i].to_int64(); + start = int64.parse(attr_values[i]); break; case "media-start": - media_start = attr_values[i].to_int64(); + media_start = int64.parse(attr_values[i]); break; case "duration": - duration = attr_values[i].to_int64(); + duration = int64.parse(attr_values[i]); break; default: // TODO: we need a way to deal with orphaned attributes, for now, reject the file - load_error("Unknown attribute %s".printf(attr_names[i])); + load_error(ErrorClass.FormatError, "Unknown attribute %s".printf(attr_names[i])); return false; } } if (id == -1) { - load_error("missing clip id"); + load_error(ErrorClass.FormatError, "missing clip id"); return false; } if (clip_name == null) { - load_error("missing clip_name"); + load_error(ErrorClass.FormatError, "missing clip_name"); return false; } if (start == -1) { - load_error("missing start time"); + load_error(ErrorClass.FormatError, "missing start time"); return false; } if (media_start == -1) { - load_error("missing media_start"); + load_error(ErrorClass.FormatError, "missing media_start"); return false; } if (duration == -1) { - load_error("missing duration"); + load_error(ErrorClass.FormatError, "missing duration"); return false; } if (id >= clipfetchers.size) { - load_error("clip file id %s was not loaded".printf(clip_name)); + load_error(ErrorClass.FormatError, "clip file id %s was not loaded".printf(clip_name)); return false; } - Clip clip = new Clip(clipfetchers[id].clipfile, current_track.media_type(), clip_name, + Clip clip = new Clip(clipfetchers[id].mediafile, current_track.media_type(), clip_name, start, media_start, duration, false); current_track.add(clip, start, false); return true; @@ -194,17 +201,17 @@ public class MediaLoaderHandler : LoaderHandler { void fetcher_ready(Fetcher f) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "fetcher_ready"); if (f.error_string != null) { - load_error("Could not load %s.".printf(f.clipfile.filename)); - warning("Could not load %s: %s", f.clipfile.filename, f.error_string); + load_error(ErrorClass.MissingFiles, "Could not load %s.".printf(f.mediafile.filename)); + warning("Could not load %s: %s", f.mediafile.filename, f.error_string); } - the_project.add_clipfile(f.clipfile); - num_clipfiles_complete++; - if (num_clipfiles_complete == clipfetchers.size) { + the_project.add_mediafile(f.mediafile); + num_mediafiles_complete++; + if (num_mediafiles_complete == clipfetchers.size) { complete(); } } - public override bool commit_clipfile(string[] attr_names, string[] attr_values) { + public override bool commit_mediafile(string[] attr_names, string[] attr_values) { string filename = null; int id = -1; @@ -212,17 +219,17 @@ public class MediaLoaderHandler : LoaderHandler { if (attr_names[i] == "filename") { filename = attr_values[i]; } else if (attr_names[i] == "id") { - id = attr_values[i].to_int(); + id = int.parse(attr_values[i]); } } if (filename == null) { - load_error("Invalid clipfile filename"); + load_error(ErrorClass.FormatError, "Invalid clipfile filename"); return false; } if (id < 0) { - load_error("Invalid clipfile id"); + load_error(ErrorClass.FormatError, "Invalid clipfile id"); return false; } @@ -231,7 +238,7 @@ public class MediaLoaderHandler : LoaderHandler { fetcher.ready.connect(fetcher_ready); clipfetchers.insert(id, fetcher); } catch (Error e) { - load_error(e.message); + load_error(ErrorClass.MissingFiles, e.message); return false; } return true; @@ -239,17 +246,17 @@ public class MediaLoaderHandler : LoaderHandler { public override bool commit_tempo_entry(string[] attr_names, string[] attr_values) { if (attr_names[0] != "tempo") { - load_error("Invalid attribute on tempo entry"); + load_error(ErrorClass.FormatError, "Invalid attribute on tempo entry"); return false; } - the_project._set_bpm(attr_values[0].to_int()); + the_project._set_bpm(int.parse(attr_values[0])); return true; } public override bool commit_time_signature_entry(string[] attr_names, string[] attr_values) { if (attr_names[0] != "signature") { - load_error("Invalid attribute on time signature"); + load_error(ErrorClass.FormatError, "Invalid attribute on time signature"); return false; } @@ -267,10 +274,11 @@ public class MediaLoaderHandler : LoaderHandler { the_project.click_during_record = attr_values[i] == "true"; break; case "volume": - the_project.click_volume = attr_values[i].to_double(); + the_project.click_volume = double.parse(attr_values[i]); break; default: - load_error("unknown attribute for click '%s'".printf(attr_names[i])); + load_error(ErrorClass.FormatError, + "unknown attribute for click '%s'".printf(attr_names[i])); return false; } } @@ -281,13 +289,14 @@ public class MediaLoaderHandler : LoaderHandler { for (int i = 0; i < attr_names.length; ++i) { switch (attr_names[i]) { case "width": - the_project.library_width = attr_values[i].to_int(); + the_project.library_width = int.parse(attr_values[i]); break; case "visible": the_project.library_visible = attr_values[i] == "true"; break; default: - load_error("unknown attribute for library '%s'".printf(attr_names[i])); + load_error(ErrorClass.FormatError, + "unknown attribute for library '%s'".printf(attr_names[i])); return false; } } @@ -333,7 +342,7 @@ along with %s; if not, write to the Free Software Foundation, Inc., public Gee.ArrayList<Track> inactive_tracks = new Gee.ArrayList<Track>(); Gee.HashSet<ClipFetcher> pending = new Gee.HashSet<ClipFetcher>(); Gee.ArrayList<ThumbnailFetcher> pending_thumbs = new Gee.ArrayList<ThumbnailFetcher>(); - protected Gee.ArrayList<ClipFile> clipfiles = new Gee.ArrayList<ClipFile>(); + protected Gee.ArrayList<MediaFile> mediafiles = new Gee.ArrayList<MediaFile>(); // TODO: media_engine is a member of project only temporarily. It will be // less work to move it to fillmore/lombard once we have a transport class. public View.MediaEngine media_engine; @@ -354,6 +363,7 @@ along with %s; if not, write to the Free Software Foundation, Inc., public bool library_visible = true; public int library_width = 600; public bool snap_to_clip; + public bool snap_to_grid; /* TODO: * This can't be const since the Vala compiler (0.7.7) crashes if we try to make it a const. @@ -365,21 +375,22 @@ along with %s; if not, write to the Free Software Foundation, Inc., public signal void playstate_changed(PlayState playstate); public signal void name_changed(string? project_file); - public signal void load_error(string error); + public signal void load_error(ErrorClass error_class, string error); public virtual signal void load_complete() { } - public signal void closed(); + public signal void closed(bool did_close); + public signal void query_closed(ref bool should_close); public signal void track_added(Track track); public signal void track_removed(Track track); public signal void error_occurred(string major_message, string? minor_message); - public signal void clipfile_added(ClipFile c); - public signal void clipfile_removed(ClipFile clip_file); + public signal void mediafile_added(MediaFile c); + public signal void mediafile_removed(MediaFile media_file); public signal void cleared(); - public abstract TimeCode get_clip_time(ClipFile f); + public abstract TimeCode get_clip_time(MediaFile f); public Project(string? filename, bool include_video) throws Error { undo_manager = new UndoManager(); @@ -400,7 +411,7 @@ along with %s; if not, write to the Free Software Foundation, Inc., ClearTrackMeters(); break; case PlayState.CLOSED: - closed(); + closed(true); break; } playstate_changed(media_engine.get_play_state()); @@ -410,16 +421,16 @@ along with %s; if not, write to the Free Software Foundation, Inc., return project_file; } - public ClipFile? get_clipfile(int index) { + public MediaFile? get_mediafile(int index) { if (index < 0 || - index >= clipfiles.size) + index >= mediafiles.size) return null; - return clipfiles[index]; + return mediafiles[index]; } - public int get_clipfile_index(ClipFile find) { + public int get_mediafile_index(MediaFile find) { int i = 0; - foreach (ClipFile f in clipfiles) { + foreach (MediaFile f in mediafiles) { if (f == find) return i; i++; @@ -492,38 +503,29 @@ along with %s; if not, write to the Free Software Foundation, Inc., } } - protected virtual void do_append(Track track, ClipFile clipfile, string name, + protected virtual void do_append(Track track, MediaFile mediafile, string name, int64 insert_time) { - switch(track.media_type()) { - case MediaType.AUDIO: - if (clipfile.audio_caps == null) { - return; - } - break; - case MediaType.VIDEO: - if (clipfile.video_caps == null) { - return; - } - break; - } + if (mediafile.get_caps(track.media_type()) == null) { + return; + } - Clip clip = new Clip(clipfile, track.media_type(), name, 0, 0, clipfile.length, false); + Clip clip = new Clip(mediafile, track.media_type(), name, 0, 0, mediafile.length, false); track.append_at_time(clip, insert_time, true); } - public void append(Track track, ClipFile clipfile) { - string name = isolate_filename(clipfile.filename); + public void append(Track track, MediaFile mediafile) { + string name = isolate_filename(mediafile.filename); int64 insert_time = 0; foreach (Track temp_track in tracks) { insert_time = int64.max(insert_time, temp_track.get_length()); } - do_append(track, clipfile, name, insert_time); + do_append(track, mediafile, name, insert_time); } - public void add(Track track, ClipFile clipfile, int64 time) { - string name = isolate_filename(clipfile.filename); - do_append(track, clipfile, name, time); + public void add(Track track, MediaFile mediafile, int64 time) { + string name = isolate_filename(mediafile.filename); + do_append(track, mediafile, name, time); } public void on_clip_removed(Track t, Clip clip) { @@ -681,41 +683,41 @@ along with %s; if not, write to the Free Software Foundation, Inc., track_removed(track); } - public void add_clipfile(ClipFile clipfile) { - Model.Command command = new Model.AddClipCommand(this, clipfile); + public void add_mediafile(MediaFile mediafile) { + Model.Command command = new Model.AddClipCommand(this, mediafile); do_command(command); } - public void _add_clipfile(ClipFile clipfile) throws Error { - clipfiles.add(clipfile); - if (clipfile.is_online() && clipfile.is_of_type(MediaType.VIDEO)) { - ThumbnailFetcher fetcher = new ThumbnailFetcher(clipfile, 0); + public void _add_mediafile(MediaFile mediafile) throws Error { + mediafiles.add(mediafile); + if (mediafile.is_online() && mediafile.get_caps(MediaType.VIDEO) != null) { + ThumbnailFetcher fetcher = new ThumbnailFetcher(mediafile, 0); fetcher.ready.connect(on_thumbnail_ready); pending_thumbs.add(fetcher); } else { - clipfile_added(clipfile); + mediafile_added(mediafile); } } void on_thumbnail_ready(Fetcher f) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_thumbnail_ready"); - clipfile_added(f.clipfile); + mediafile_added(f.mediafile); pending_thumbs.remove(f as ThumbnailFetcher); } - public bool clipfile_on_track(string filename) { - ClipFile cf = find_clipfile(filename); + public bool mediafile_on_track(string filename) { + MediaFile cf = find_mediafile(filename); foreach (Track t in tracks) { foreach (Clip c in t.clips) { - if (c.clipfile == cf) + if (c.mediafile == cf) return true; } } foreach (Track t in inactive_tracks) { foreach (Clip c in t.clips) { - if (c.clipfile == cf) + if (c.mediafile == cf) return true; } } @@ -723,10 +725,10 @@ along with %s; if not, write to the Free Software Foundation, Inc., return false; } - void delete_clipfile_from_tracks(ClipFile cf) { + void delete_mediafile_from_tracks(MediaFile cf) { foreach (Track t in tracks) { for (int i = 0; i < t.clips.size; i++) { - if (t.clips[i].clipfile == cf) { + if (t.clips[i].mediafile == cf) { t.delete_clip(t.clips[i]); i --; } @@ -735,7 +737,7 @@ along with %s; if not, write to the Free Software Foundation, Inc., foreach (Track t in inactive_tracks) { for (int i = 0; i < t.clips.size; i++) { - if (t.clips[i].clipfile == cf) { + if (t.clips[i].mediafile == cf) { t.delete_clip(t.clips[i]); i --; } @@ -743,28 +745,28 @@ along with %s; if not, write to the Free Software Foundation, Inc., } } - public void _remove_clipfile(ClipFile cf) { - clipfiles.remove(cf); - clipfile_removed(cf); + public void _remove_mediafile(MediaFile cf) { + mediafiles.remove(cf); + mediafile_removed(cf); } - public void remove_clipfile(string filename) { - ClipFile cf = find_clipfile(filename); + public void remove_mediafile(string filename) { + MediaFile cf = find_mediafile(filename); if (cf != null) { string description = "Delete From Library"; undo_manager.start_transaction(description); - delete_clipfile_from_tracks(cf); + delete_mediafile_from_tracks(cf); - Command clipfile_delete = new ClipFileDeleteCommand(this, cf); - do_command(clipfile_delete); + Command mediafile_delete = new MediaFileDeleteCommand(this, cf); + do_command(mediafile_delete); undo_manager.end_transaction(description); } } - public ClipFile? find_clipfile(string filename) { - foreach (ClipFile cf in clipfiles) + public MediaFile? find_mediafile(string filename) { + foreach (MediaFile cf in mediafiles) if (cf.filename == filename) return cf; return null; @@ -848,7 +850,7 @@ along with %s; if not, write to the Free Software Foundation, Inc., tracks.clear(); - clipfiles.clear(); + mediafiles.clear(); set_name(null); cleared(); } @@ -870,9 +872,9 @@ along with %s; if not, write to the Free Software Foundation, Inc., project_file = filename; } - void on_load_error(string error) { + void on_load_error(ErrorClass error_class, string error) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_load_error"); - load_error(error); + load_error(error_class, error); } void on_load_complete() { @@ -930,8 +932,8 @@ along with %s; if not, write to the Free Software Foundation, Inc., r.denominator); f.printf(">\n"); - for (int i = 0; i < clipfiles.size; i++) { - f.printf(" <clipfile filename=\"%s\" id=\"%d\"/>\n", clipfiles[i].filename, i); + for (int i = 0; i < mediafiles.size; i++) { + f.printf(" <clipfile filename=\"%s\" id=\"%d\"/>\n", mediafiles[i].filename, i); } f.printf(" </library>\n"); @@ -986,7 +988,13 @@ along with %s; if not, write to the Free Software Foundation, Inc., } public void close() { - media_engine.close(); + bool should_close = true; + query_closed(ref should_close); + if (should_close) { + media_engine.close(); + } else { + closed(false); + } } public void on_importer_clip_complete(ClipFetcher fetcher) { @@ -1013,8 +1021,8 @@ along with %s; if not, write to the Free Software Foundation, Inc., emit(this, Facility.DEVELOPER_WARNINGS, Level.INFO, fetcher.error_string); error_occurred("Error retrieving clip", fetcher.error_string); } else { - if (get_clipfile_index(fetcher.clipfile) == -1) { - add_clipfile(fetcher.clipfile); + if (get_mediafile_index(fetcher.mediafile) == -1) { + add_mediafile(fetcher.mediafile); } fetcher_completion.complete(fetcher); } diff --git a/src/marina/sources.mk b/src/marina/sources.mk index 7486e0f..76058ca 100644 --- a/src/marina/sources.mk +++ b/src/marina/sources.mk @@ -3,12 +3,15 @@ $(SRC_PREFIX)SRC_FILES = \ AudioMeter.vala \ ClassFactory.vala \ ClipLibraryView.vala \ + ClipView.vala \ clip.vala \ command.vala \ DialogUtils.vala \ import.vala \ Logging.vala \ MediaEngine.vala \ + MediaFile.vala \ + MediaFileConcrete.vala \ MultiFileProgress.vala \ ProjectLoader.vala \ project.vala \ @@ -21,7 +24,6 @@ $(SRC_PREFIX)SRC_FILES = \ track.vala \ TrackView.vala \ TransportDelegate.vala \ - ui_clip.vala \ UndoManager.vala \ util.vala \ video_track.vala diff --git a/src/marina/thumbnailsink.vala b/src/marina/thumbnailsink.vala index fe4f358..7c311f6 100644 --- a/src/marina/thumbnailsink.vala +++ b/src/marina/thumbnailsink.vala @@ -1,7 +1,7 @@ class ThumbnailSink : Gst.BaseSink { int width; int height; - + const string caps_string = """video/x-raw-rgb,bpp = (int) 32, depth = (int) 32, endianness = (int) BIG_ENDIAN, blue_mask = (int) 0xFF000000, @@ -12,56 +12,52 @@ class ThumbnailSink : Gst.BaseSink { framerate = (fraction) [ 0, max ]"""; public signal void have_thumbnail(Gdk.Pixbuf b); - + class construct { - Gst.StaticPadTemplate pad; + Gst.StaticPadTemplate pad; pad.name_template = "sink"; pad.direction = Gst.PadDirection.SINK; pad.presence = Gst.PadPresence.ALWAYS; pad.static_caps.str = caps_string; - - add_pad_template(pad.get()); - } - - // This empty construct block eliminates a build warning about chaining up to a private - // constructor. - construct { + + add_pad_template(pad.get()); } - + public ThumbnailSink() { + Object(); set_sync(false); } - + public override bool set_caps(Gst.Caps c) { if (c.get_size() < 1) return false; - + Gst.Structure s = c.get_structure(0); - + if (!s.get_int("width", out width) || !s.get_int("height", out height)) return false; return true; } - + void convert_pixbuf_to_rgb(Gdk.Pixbuf buf) { uchar* data = buf.get_pixels(); int limit = buf.get_width() * buf.get_height(); - + while (limit-- != 0) { uchar temp = data[0]; data[0] = data[2]; data[2] = temp; - + data += 4; } } - + public override Gst.FlowReturn preroll(Gst.Buffer b) { Gdk.Pixbuf buf = new Gdk.Pixbuf.from_data(b.data, Gdk.Colorspace.RGB, true, 8, width, height, width * 4, null); convert_pixbuf_to_rgb(buf); - + have_thumbnail(buf); return Gst.FlowReturn.OK; } diff --git a/src/marina/timeline.vala b/src/marina/timeline.vala index f72ac4d..b0da172 100644 --- a/src/marina/timeline.vala +++ b/src/marina/timeline.vala @@ -48,6 +48,7 @@ public class TimeLine : Gtk.EventBox { public weak Model.TimeSystem provider; public View.Ruler ruler; Gtk.Widget drag_widget = null; + ClipView select_anchor = null; bool copying; public Gee.ArrayList<TrackView> tracks = new Gee.ArrayList<TrackView>(); Gtk.VBox vbox; @@ -63,9 +64,9 @@ public class TimeLine : Gtk.EventBox { public signal void trackview_added(TrackView trackview); public signal void trackview_removed(TrackView trackview); - float pixel_div; - float pixel_min = 0.1f; - float pixel_max = 4505.0f; + const float pixel_min = 0.1f; + const float pixel_max = 4505.0f; + const float pixel_div = pixel_max / pixel_min; Gtk.Label high_water; public const int RULER_HEIGHT = 20; @@ -93,7 +94,6 @@ public class TimeLine : Gtk.EventBox { modify_bg(Gtk.StateType.NORMAL, parse_color("#444")); modify_fg(Gtk.StateType.NORMAL, parse_color("#f00")); - pixel_div = pixel_max / pixel_min; provider.calculate_pixel_step (0.5f, pixel_min, pixel_div); Gtk.drag_dest_set(this, Gtk.DestDefaults.ALL, drag_target_entries, actions); } @@ -141,10 +141,36 @@ public class TimeLine : Gtk.EventBox { trackview_added(track_view); if (track.media_type() == Model.MediaType.VIDEO) { vbox.reorder_child(track_view, 1); + } else if (track.media_type() == Model.MediaType.AUDIO) { + Model.AudioTrack audio_track = track as Model.AudioTrack; + audio_track.solo_changed.connect(on_solo_changed); + audio_track.indirect_mute = any_track_solo(); } vbox.show_all(); } + bool any_track_solo() { + foreach (TrackView track_view in tracks) { + Model.AudioTrack audio_track = track_view.get_track() as Model.AudioTrack; + if (audio_track != null && audio_track.solo) { + return true; + } + } + return false; + } + + void on_solo_changed(Model.AudioTrack track) { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_solo_changed"); + + bool any_solo = track.solo || any_track_solo(); + foreach (TrackView track_view in tracks) { + Model.AudioTrack audio_track = track_view.get_track() as Model.AudioTrack; + if (audio_track != null && !audio_track.solo) { + audio_track.indirect_mute = any_solo; + } + } + } + void on_track_removed(Model.Track track) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_track_removed"); foreach (TrackView track_view in tracks) { @@ -164,14 +190,15 @@ public class TimeLine : Gtk.EventBox { clip_view.move_commit.connect(on_clip_view_move_commit); clip_view.move_begin.connect(on_clip_view_move_begin); clip_view.trim_begin.connect(on_clip_view_trim_begin); + clip_view.trim_request.connect(on_clip_view_trim_request); clip_view.trim_commit.connect(on_clip_view_trim_commit); + clip_view.selection_changed.connect(on_selection_changed); } public void deselect_all_clips() { - foreach(ClipView selected_clip_view in selected_clips) { - selected_clip_view.is_selected = false; + while (selected_clips.size > 0) { + selected_clips.get(0).clip.is_selected = false; } - selected_clips.clear(); } void on_clip_view_move_begin(ClipView clip_view, bool copy) { @@ -190,7 +217,7 @@ public class TimeLine : Gtk.EventBox { } selected_clip.initial_time = selected_clip.clip.start; selected_clip.clip.gnonlin_disconnect(); - TrackView track_view = selected_clip.get_parent() as TrackView; + TrackView track_view = selected_clip.get_track_view(); if (track_view != null) { track_view.get_track().remove_clip_from_array(selected_clip.clip); } @@ -211,7 +238,7 @@ public class TimeLine : Gtk.EventBox { //The second pass moves the selected clips to the top. We can't do this in one pass //because creating a copy inserts the new copy in the z-order at the top. foreach (ClipView selected_clip in selected_clips) { - TrackView track_view = selected_clip.get_parent() as TrackView; + TrackView track_view = selected_clip.get_track_view(); track_view.move_to_top(selected_clip); } } @@ -231,7 +258,32 @@ public class TimeLine : Gtk.EventBox { } } - void on_clip_view_selection_request(ClipView clip_view, bool extend) { + void on_clip_view_trim_request(ClipView clip_view, Gdk.WindowEdge edge, int64 delta) { + bool snapped = false; + if (project.snap_to_clip) { + snapped = constrain_move(clip_view, ref delta); + } + + if (!snapped && project.snap_to_grid) { + int64 range = provider.xsize_to_time(clip_view.SNAP_DELTA); + int64 snap_time = provider.next_tick(clip_view.clip.start); + int64 difference = clip_view.clip.start + delta - snap_time; + if (difference.abs() < range) { + delta = -(clip_view.clip.start - snap_time); + clip_view.snap(provider.time_to_xsize(difference)); + } else { + snap_time = provider.previous_tick(clip_view.clip.start); + difference = clip_view.clip.start + delta - snap_time; + if (difference.abs() < range) { + delta = -(clip_view.clip.start - snap_time); + clip_view.snap(provider.time_to_xsize(difference)); + } + } + } + clip_view.clip.trim(delta, edge); + } + + void on_clip_view_selection_request(ClipView clip_view, ClipView.SelectionType selection_type) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_view_selection_request"); /* if (gap_view != null) { @@ -239,24 +291,45 @@ public class TimeLine : Gtk.EventBox { } */ bool in_selected_clips = selected_clips.contains(clip_view); - if (!extend) { + if (selection_type == ClipView.SelectionType.NONE) { if (!in_selected_clips) { deselect_all_clips(); - clip_view.is_selected = true; - selected_clips.add(clip_view); + clip_view.clip.is_selected = true; + select_anchor = clip_view; } - } else { + } else if (selection_type == ClipView.SelectionType.ADD) { if (selected_clips.size > 1) { - if (in_selected_clips && clip_view.is_selected) { - clip_view.is_selected = false; + if (in_selected_clips && clip_view.clip.is_selected) { + clip_view.clip.is_selected = false; // just deselected with multiple clips, so moving is not allowed drag_widget = null; - selected_clips.remove(clip_view); } + } else if (selected_clips.size == 0) { + select_anchor = clip_view; } if (!in_selected_clips) { - clip_view.is_selected = true; - selected_clips.add(clip_view); + clip_view.clip.is_selected = true; + } + } else if (selection_type == ClipView.SelectionType.EXTEND) { + if (select_anchor == null) { + select_anchor = clip_view; + } + if (select_anchor.get_track_view() == clip_view.get_track_view()) { + Model.Track track = select_anchor.get_track_view().get_track(); + int start_clip_index = track.get_clip_index(clip_view.clip); + int end_clip_index = track.get_clip_index(select_anchor.clip); + if (end_clip_index < start_clip_index) { + int temp = start_clip_index; + start_clip_index = end_clip_index; + end_clip_index = temp; + } + int max_clip_index = track.get_clip_count(); + for (int i = 0; i < max_clip_index; ++i) { + Model.Clip current_clip = track.get_clip(i); + if (current_clip != null) { + current_clip.is_selected = i >= start_clip_index && i <= end_clip_index; + } + } } } track_changed(); @@ -266,14 +339,14 @@ public class TimeLine : Gtk.EventBox { void on_clip_view_move_commit(ClipView clip_view, int64 delta) { window.set_cursor(null); - emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_view_move_request"); + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_view_move_commit"); Gtk.Fixed fixed = high_water.get_parent() as Gtk.Fixed; fixed.remove(high_water); high_water = null; project.undo_manager.start_transaction("Move Clip"); foreach (ClipView selected_clip_view in selected_clips) { - TrackView track_view = selected_clip_view.get_parent() as TrackView; + TrackView track_view = selected_clip_view.get_track_view(); selected_clip_view.clip.gnonlin_connect(); track_view.get_track().move(selected_clip_view.clip, selected_clip_view.clip.start, selected_clip_view.initial_time); @@ -288,7 +361,7 @@ public class TimeLine : Gtk.EventBox { void on_clip_view_trim_commit(ClipView clip_view, Gdk.WindowEdge edge) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_view_move_commit"); window.set_cursor(null); - TrackView track_view = clip_view.get_parent() as TrackView; + TrackView track_view = clip_view.get_track_view(); int64 delta = 0; switch (edge) { case Gdk.WindowEdge.WEST: @@ -309,7 +382,19 @@ public class TimeLine : Gtk.EventBox { project.undo_manager.end_transaction("Trim Clip"); } - void constrain_move(ClipView clip_view, ref int64 delta) { + void on_selection_changed(ClipView clip_view) { + emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_selection_changed"); + if (clip_view.clip.is_selected) { + if (!selected_clips.contains(clip_view)) { + selected_clips.add(clip_view); + } + } else { + if (selected_clips.contains(clip_view)) { + selected_clips.remove(clip_view); + } + } + } + bool constrain_move(ClipView clip_view, ref int64 delta) { int min_delta = clip_view.SNAP_DELTA; int delta_xsize = provider.time_to_xsize(delta); TrackView track_view = (TrackView) clip_view.parent as TrackView; @@ -320,14 +405,34 @@ public class TimeLine : Gtk.EventBox { if (track.clip_is_near(clip_view.clip, range, out adjustment)) { delta = adjustment; clip_view.snap(provider.time_to_xsize(adjustment)); + return true; } } + return false; } void on_clip_view_move_request(ClipView clip_view, int64 delta) { emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_view_move_request"); + bool snapped = false; if (project.snap_to_clip) { - constrain_move(clip_view, ref delta); + snapped = constrain_move(clip_view, ref delta); + } + + if (!snapped && project.snap_to_grid) { + int64 range = provider.xsize_to_time(clip_view.SNAP_DELTA); + int64 snap_time = provider.next_tick(clip_view.clip.start); + int64 difference = clip_view.clip.start + delta - snap_time; + if (difference.abs() < range) { + delta = -(clip_view.clip.start - snap_time); + clip_view.snap(provider.time_to_xsize(difference)); + } else { + snap_time = provider.previous_tick(clip_view.clip.start); + difference = clip_view.clip.start + delta - snap_time; + if (difference.abs() < range) { + delta = -(clip_view.clip.start - snap_time); + clip_view.snap(provider.time_to_xsize(difference)); + } + } } if (move_allowed(ref delta)) { move_the_clips(delta); @@ -386,6 +491,9 @@ public class TimeLine : Gtk.EventBox { drag_widget = null; if (is_clip_selected()) { while (selected_clips.size > 0) { + if (selected_clips[0] == select_anchor) { + select_anchor = null; + } selected_clips[0].delete_clip(); selected_clips.remove_at(0); } @@ -452,9 +560,16 @@ public class TimeLine : Gtk.EventBox { base.expose_event(event); int xpos = provider.time_to_xpos(project.transport_get_position()); - Gdk.draw_line(window, style.fg_gc[(int) Gtk.StateType.NORMAL], - xpos, 0, - xpos, allocation.height); + Cairo.Context context = Gdk.cairo_create(window); + context.save(); + Gdk.Color color = style.fg[Gtk.StateType.NORMAL]; + context.set_source_rgb(color.red, color.green, color.blue); + context.set_antialias(Cairo.Antialias.NONE); + context.set_line_width(1.0); + context.move_to(xpos, 0); + context.line_to(xpos, allocation.height); + context.stroke(); + context.restore(); return true; } @@ -525,10 +640,9 @@ public class TimeLine : Gtk.EventBox { } void deselect_all() { - foreach (ClipView clip_view in selected_clips) { - clip_view.is_selected = false; + while (selected_clips.size > 0) { + selected_clips.get(0).clip.is_selected = false; } - selected_clips.clear(); selection_changed(false); } @@ -536,7 +650,7 @@ public class TimeLine : Gtk.EventBox { /* if (gap_view != null) gap_view.unselect(); -*/ +*/ drag_widget = null; Gtk.Widget? child = find_child(event.x, event.y); @@ -551,14 +665,16 @@ public class TimeLine : Gtk.EventBox { if (drag_widget != null) { drag_widget.button_press_event(event); } else { + select_anchor = null; deselect_all(); } } else { + select_anchor = null; deselect_all(); } queue_draw(); - return true; + return false; } public override bool button_release_event(Gdk.EventButton event) { @@ -566,7 +682,7 @@ public class TimeLine : Gtk.EventBox { drag_widget.button_release_event(event); drag_widget = null; } - return true; + return false; } public override bool motion_notify_event(Gdk.EventMotion event) { @@ -590,7 +706,7 @@ public class TimeLine : Gtk.EventBox { window.set_cursor(null); } } - return true; + return false; } TrackView? find_video_track_view() { diff --git a/src/marina/track.vala b/src/marina/track.vala index 172fff6..a7f75c4 100644 --- a/src/marina/track.vala +++ b/src/marina/track.vala @@ -35,9 +35,9 @@ public abstract class Track : Object { track_hidden(this); } - public bool contains_clipfile(ClipFile f) { + public bool contains_mediafile(MediaFile f) { foreach (Clip c in clips) { - if (c.clipfile == f) + if (c.mediafile == f) return true; } return false; @@ -204,7 +204,7 @@ public abstract class Track : Object { } if (diff > 0) { - Clip cl = new Clip(clips[end_index].clipfile, clips[end_index].type, + Clip cl = new Clip(clips[end_index].mediafile, clips[end_index].type, clips[end_index].name, c.end, clips[end_index].media_start + diff, clips[end_index].duration - diff, false); @@ -274,6 +274,10 @@ public abstract class Track : Object { return clips[i]; } + public int get_clip_count() { + return clips.size; + } + public int get_clip_index(Clip c) { for (int i = 0; i < clips.size; i++) { if (clips[i] == c) { @@ -316,7 +320,7 @@ public abstract class Track : Object { assert(index != -1); clips.remove_at(index); - clip.removed(clip); + clip.removed(); clip_removed(clip); } @@ -351,7 +355,7 @@ public abstract class Track : Object { if (index == -1) error("revert_to_original: Clip not in track array!"); - c.set_media_start_duration(0, c.clipfile.length); + c.set_media_start_duration(0, c.mediafile.length); project.media_engine.go(c.start); } @@ -362,7 +366,7 @@ public abstract class Track : Object { return left_clip != null && right_clip != null && left_clip != right_clip && - left_clip.clipfile == right_clip.clipfile && + left_clip.mediafile == right_clip.mediafile && left_clip.end == right_clip.start; } @@ -376,7 +380,7 @@ public abstract class Track : Object { if (c == null) return; - Clip cn = new Clip(c.clipfile, c.type, c.name, position, + Clip cn = new Clip(c.mediafile, c.type, c.name, position, (position - c.start) + c.media_start, c.start + c.duration - position, false); @@ -446,7 +450,7 @@ public abstract class Track : Object { write_attributes(f); f.printf(">\n"); for (int i = 0; i < clips.size; i++) - clips[i].save(f, project.get_clipfile_index(clips[i].clipfile)); + clips[i].save(f, project.get_mediafile_index(clips[i].mediafile)); f.puts(" </track>\n"); } @@ -479,6 +483,74 @@ public class AudioTrack : Track { int default_num_channels; public static const int INVALID_CHANNEL_COUNT = -1; + public string device = null; + bool _mute; + public bool mute { + set { + if (value != _mute) { + _mute = value; + mute_changed(); + if (mute) { + solo = false; + } + } + } + + get { + return _mute; + } + } + + bool _solo; + public bool solo { + set { + if (value != _solo) { + _solo = value; + solo_changed(); + if (solo) { + indirect_mute = false; + mute = false; + } + } + } + + get { + return _solo; + } + } + + bool _indirect_mute; + public bool indirect_mute { + set { + if (value != _indirect_mute) { + _indirect_mute = value; + indirect_mute_changed(); + } + } + + get { + return _indirect_mute; + } + } + + bool _record_enable = false; + public bool record_enable { + set { + if (value != _record_enable) { + _record_enable = value; + record_enable_changed(); + } + } + + get { + return _record_enable; + } + } + + public signal void mute_changed(); + public signal void solo_changed(); + public signal void indirect_mute_changed(); + public signal void record_enable_changed(); public signal void parameter_changed(Parameter parameter, double new_value); public signal void level_changed(double level_left, double level_right); @@ -503,9 +575,11 @@ public class AudioTrack : Track { f.printf("volume=\"%f\" panorama=\"%f\" ", get_volume(), get_pan()); int channels; - if (get_num_channels(out channels) && - channels != INVALID_CHANNEL_COUNT) + if (get_num_channels(out channels) && channels != INVALID_CHANNEL_COUNT) { f.printf("channels=\"%d\" ", channels); + } + + f.printf("mute=\"%s\" solo=\"%s\" ", mute.to_string(), solo.to_string()); } public void set_pan(double new_value) { @@ -561,8 +635,8 @@ public class AudioTrack : Track { return false; foreach (Clip c in clips) { - if (c.clipfile.is_online()) { - bool can = c.clipfile.get_num_channels(out num); + if (c.mediafile.is_online()) { + bool can = c.mediafile.get_num_channels(out num); assert(can); return can; @@ -577,13 +651,13 @@ public class AudioTrack : Track { } public override bool check(Clip clip) { - if (!clip.clipfile.is_online()) { + if (!clip.mediafile.is_online()) { return true; } if (clips.size == 0) { int number_of_channels = 0; - if (clip.clipfile.get_num_channels(out number_of_channels)) { + if (clip.mediafile.get_num_channels(out number_of_channels)) { channel_count_changed(number_of_channels); } return true; @@ -591,7 +665,7 @@ public class AudioTrack : Track { bool good = false; int number_of_channels; - if (clip.clipfile.get_num_channels(out number_of_channels)) { + if (clip.mediafile.get_num_channels(out number_of_channels)) { int track_channel_count; if (get_num_channels(out track_channel_count)) { good = track_channel_count == number_of_channels; @@ -613,7 +687,7 @@ public class AudioTrack : Track { } public override void on_clip_updated(Clip clip) { - if (clip.clipfile.is_online()) { + if (clip.mediafile.is_online()) { int number_of_channels = 0; if (get_num_channels(out number_of_channels)) { channel_count_changed(number_of_channels); diff --git a/src/marina/ui_clip.vala b/src/marina/ui_clip.vala deleted file mode 100644 index 9a17244..0000000 --- a/src/marina/ui_clip.vala +++ /dev/null @@ -1,372 +0,0 @@ -/* Copyright 2009-2010 Yorba Foundation - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -using Logging; - -public class GapView : Gtk.DrawingArea { - public Model.Gap gap; - Gdk.Color fill_color; - - public GapView(int64 start, int64 length, int width, int height) { - - gap = new Model.Gap(start, start + length); - - Gdk.Color.parse("#777", out fill_color); - - set_flags(Gtk.WidgetFlags.NO_WINDOW); - - set_size_request(width, height); - } - - public signal void removed(GapView gap_view); - public signal void unselected(GapView gap_view); - - public void remove() { - removed(this); - } - - public void unselect() { - unselected(this); - } - - public override bool expose_event(Gdk.EventExpose e) { - draw_rounded_rectangle(window, fill_color, true, allocation.x, allocation.y, - allocation.width - 1, allocation.height - 1); - return true; - } -} - -public class ClipView : Gtk.DrawingArea { - enum MotionMode { - NONE, - DRAGGING, - LEFT_TRIM, - RIGHT_TRIM - } - - public Model.Clip clip; - public int64 initial_time; - weak Model.TimeSystem time_provider; - public bool is_selected; - public int height; // TODO: We request size of height, but we aren't allocated this height. - // We should be using the allocated height, not the requested height. - public static Gtk.Menu context_menu; - TransportDelegate transport_delegate; - Gdk.Color color_black; - Gdk.Color color_normal; - Gdk.Color color_selected; - int drag_point; - int snap_amount; - bool snapped; - MotionMode motion_mode = MotionMode.NONE; - bool button_down = false; - bool pending_selection; - const int MIN_DRAG = 5; - const int TRIM_WIDTH = 10; - public const int SNAP_DELTA = 10; - - static Gdk.Cursor left_trim_cursor = new Gdk.Cursor(Gdk.CursorType.LEFT_SIDE); - static Gdk.Cursor right_trim_cursor = new Gdk.Cursor(Gdk.CursorType.RIGHT_SIDE); - static Gdk.Cursor hand_cursor = new Gdk.Cursor.from_name(Gdk.Display.get_default(), "dnd-none"); - // will be used for drag - static Gdk.Cursor plus_cursor = new Gdk.Cursor.from_name(Gdk.Display.get_default(), "dnd-copy"); - - public signal void clip_deleted(Model.Clip clip); - public signal void clip_moved(ClipView clip); - public signal void selection_request(ClipView clip_view, bool extend_selection); - public signal void move_request(ClipView clip_view, int64 delta); - public signal void move_commit(ClipView clip_view, int64 delta); - public signal void move_begin(ClipView clip_view, bool copy); - public signal void trim_begin(ClipView clip_view, Gdk.WindowEdge edge); - public signal void trim_commit(ClipView clip_view, Gdk.WindowEdge edge); - - public ClipView(TransportDelegate transport_delegate, Model.Clip clip, - Model.TimeSystem time_provider, int height) { - this.transport_delegate = transport_delegate; - this.clip = clip; - this.time_provider = time_provider; - this.height = height; - is_selected = false; - - clip.moved.connect(on_clip_moved); - clip.updated.connect(on_clip_updated); - - Gdk.Color.parse("000", out color_black); - get_clip_colors(); - - set_flags(Gtk.WidgetFlags.NO_WINDOW); - - adjust_size(height); - } - - void get_clip_colors() { - if (clip.clipfile.is_online()) { - Gdk.Color.parse(clip.type == Model.MediaType.VIDEO ? "#d82" : "#84a", - out color_selected); - Gdk.Color.parse(clip.type == Model.MediaType.VIDEO ? "#da5" : "#b9d", - out color_normal); - } else { - Gdk.Color.parse("red", out color_selected); - Gdk.Color.parse("#AA0000", out color_normal); - } - } - - void on_clip_updated() { - emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_updated"); - get_clip_colors(); - queue_draw(); - } - - // Note that a view's size may vary slightly (by a single pixel) depending on its - // starting position. This is because the clip's length may not be an integer number of - // pixels, and may get rounded either up or down depending on the clip position. - public void adjust_size(int height) { - int width = time_provider.time_to_xpos(clip.start + clip.duration) - - time_provider.time_to_xpos(clip.start); - set_size_request(width + 1, height); - } - - public void on_clip_moved(Model.Clip clip) { - emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_moved"); - adjust_size(height); - clip_moved(this); - } - - public void delete_clip() { - clip_deleted(clip); - } - - public void draw() { - weak Gdk.Color fill = is_selected ? color_selected : color_normal; - - bool left_trimmed = clip.media_start != 0 && !clip.is_recording; - - bool right_trimmed = clip.clipfile.is_online() ? - (clip.media_start + clip.duration != clip.clipfile.length) : false; - - if (!left_trimmed && !right_trimmed) { - draw_rounded_rectangle(window, fill, true, allocation.x + 1, allocation.y + 1, - allocation.width - 2, allocation.height - 2); - draw_rounded_rectangle(window, color_black, false, allocation.x, allocation.y, - allocation.width - 1, allocation.height - 1); - - } else if (!left_trimmed && right_trimmed) { - draw_left_rounded_rectangle(window, fill, true, allocation.x + 1, allocation.y + 1, - allocation.width - 2, allocation.height - 2); - draw_left_rounded_rectangle(window, color_black, false, allocation.x, allocation.y, - allocation.width - 1, allocation.height - 1); - - } else if (left_trimmed && !right_trimmed) { - draw_right_rounded_rectangle(window, fill, true, allocation.x + 1, allocation.y + 1, - allocation.width - 2, allocation.height - 2); - draw_right_rounded_rectangle(window, color_black, false, allocation.x, allocation.y, - allocation.width - 1, allocation.height - 1); - - } else { - draw_square_rectangle(window, fill, true, allocation.x + 1, allocation.y + 1, - allocation.width - 2, allocation.height - 2); - draw_square_rectangle(window, color_black, false, allocation.x, allocation.y, - allocation.width - 1, allocation.height - 1); - } - - Gdk.GC gc = new Gdk.GC(window); - Gdk.Rectangle r = { 0, 0, 0, 0 }; - - // Due to a Vala compiler bug, we have to do this initialization here... - r.x = allocation.x; - r.y = allocation.y; - r.width = allocation.width; - r.height = allocation.height; - - gc.set_clip_rectangle(r); - - Pango.Layout layout; - if (clip.is_recording) { - layout = create_pango_layout("Recording"); - } else if (!clip.clipfile.is_online()) { - layout = create_pango_layout("%s (Offline)".printf(clip.name)); - } - else { - layout = create_pango_layout("%s".printf(clip.name)); - } - int width, height; - layout.get_pixel_size(out width, out height); - Gdk.draw_layout(window, gc, allocation.x + 10, allocation.y + height, layout); - } - - public override bool expose_event(Gdk.EventExpose event) { - draw(); - return true; - } - - public override bool button_press_event(Gdk.EventButton event) { - if (!transport_delegate.is_stopped()) { - return true; - } - - event.x -= allocation.x; - bool primary_press = event.button == 1; - if (primary_press) { - button_down = true; - drag_point = (int)event.x; - snap_amount = 0; - snapped = false; - } - - bool extend_selection = (event.state & Gdk.ModifierType.CONTROL_MASK) != 0; - // The clip is not responsible for changing the selection state. - // It may depend upon knowledge of multiple clips. Let anyone who is interested - // update our state. - if (is_left_trim(event.x, event.y)) { - selection_request(this, false); - if (primary_press) { - trim_begin(this, Gdk.WindowEdge.WEST); - motion_mode = MotionMode.LEFT_TRIM; - } - } else if (is_right_trim(event.x, event.y)){ - selection_request(this, false); - if (primary_press) { - trim_begin(this, Gdk.WindowEdge.EAST); - motion_mode = MotionMode.RIGHT_TRIM; - } - } else { - if (!is_selected) { - pending_selection = false; - selection_request(this, extend_selection); - } else { - pending_selection = true; - } - } - - if (event.button == 3) { - context_menu.select_first(true); - context_menu.popup(null, null, null, event.button, event.time); - } else { - context_menu.popdown(); - } - - return true; - } - - public override bool button_release_event(Gdk.EventButton event) { - if (!transport_delegate.is_stopped()) { - return true; - } - - event.x -= allocation.x; - button_down = false; - if (event.button == 1) { - switch (motion_mode) { - case MotionMode.NONE: { - if (pending_selection) { - selection_request(this, true); - } - } - break; - case MotionMode.DRAGGING: { - int64 delta = time_provider.xsize_to_time((int) event.x - drag_point); - if (motion_mode == MotionMode.DRAGGING) { - move_commit(this, delta); - } - } - break; - case MotionMode.LEFT_TRIM: - trim_commit(this, Gdk.WindowEdge.WEST); - break; - case MotionMode.RIGHT_TRIM: - trim_commit(this, Gdk.WindowEdge.EAST); - break; - } - } - motion_mode = MotionMode.NONE; - return true; - } - - public override bool motion_notify_event(Gdk.EventMotion event) { - if (!transport_delegate.is_stopped()) { - return true; - } - - event.x -= allocation.x; - int delta_pixels = (int)(event.x - drag_point) - snap_amount; - if (snapped) { - snap_amount += delta_pixels; - if (snap_amount.abs() < SNAP_DELTA) { - return true; - } - delta_pixels += snap_amount; - snap_amount = 0; - snapped = false; - } - - int64 delta_time = time_provider.xsize_to_time(delta_pixels); - - switch (motion_mode) { - case MotionMode.NONE: - if (!button_down && is_left_trim(event.x, event.y)) { - window.set_cursor(left_trim_cursor); - } else if (!button_down && is_right_trim(event.x, event.y)) { - window.set_cursor(right_trim_cursor); - } else if (is_selected && button_down) { - if (delta_pixels.abs() > MIN_DRAG) { - bool do_copy = (event.state & Gdk.ModifierType.CONTROL_MASK) != 0; - if (do_copy) { - window.set_cursor(plus_cursor); - } else { - window.set_cursor(hand_cursor); - } - motion_mode = MotionMode.DRAGGING; - move_begin(this, do_copy); - } - } else { - window.set_cursor(null); - } - break; - case MotionMode.RIGHT_TRIM: - case MotionMode.LEFT_TRIM: - if (button_down) { - if (motion_mode == MotionMode.LEFT_TRIM) { - clip.trim(delta_time, Gdk.WindowEdge.WEST); - } else { - int64 duration = clip.duration; - clip.trim(delta_time, Gdk.WindowEdge.EAST); - if (duration != clip.duration) { - drag_point += (int)delta_pixels; - } - } - } - return true; - case MotionMode.DRAGGING: - move_request(this, delta_time); - return true; - } - return false; - } - - bool is_trim_height(double y) { - return y - allocation.y > allocation.height / 2; - } - - bool is_left_trim(double x, double y) { - return is_trim_height(y) && x > 0 && x < TRIM_WIDTH; - } - - bool is_right_trim(double x, double y) { - return is_trim_height(y) && x > allocation.width - TRIM_WIDTH && - x < allocation.width; - } - - public void select() { - if (!is_selected) { - selection_request(this, true); - } - } - - public void snap(int64 amount) { - snap_amount = time_provider.time_to_xsize(amount); - snapped = true; - } -} diff --git a/src/marina/util.vala b/src/marina/util.vala index 576324a..b6b3d66 100644 --- a/src/marina/util.vala +++ b/src/marina/util.vala @@ -69,8 +69,8 @@ public struct Fraction { numerator = 0; denominator = 0; } else { - numerator = elements[0].to_int(); - denominator = elements[1].to_int(); + numerator = int.parse(elements[0]); + denominator = int.parse(elements[1]); } } @@ -155,8 +155,8 @@ public bool version_at_least(string v, string w) { for (int i = 0 ; i < wa.length ; ++i) { if (i >= va.length) return false; - int vi = va[i].to_int(); - int wi = wa[i].to_int(); + int vi = int.parse(va[i]); + int wi = int.parse(wa[i]); if (vi > wi) return true; if (wi > vi) @@ -259,121 +259,119 @@ const double LINE_WIDTH = 1.0; const double RADIUS = 15.0; const Cairo.Antialias ANTIALIAS = Cairo.Antialias.DEFAULT; // NONE/DEFAULT -public void draw_rounded_rectangle(Gdk.Window window, Gdk.Color color, bool filled, +public void draw_rounded_rectangle(Cairo.Context context, Gdk.Color color, bool filled, int x0, int y0, int width, int height) { if (width == 0 || height == 0) return; double x1 = x0 + width; double y1 = y0 + height; - - Cairo.Context cairo_window = Gdk.cairo_create(window); - Gdk.cairo_set_source_color(cairo_window, color); - cairo_window.set_antialias(ANTIALIAS); + + Gdk.cairo_set_source_color(context, color); + context.set_antialias(ANTIALIAS); if ((width / 2) < RADIUS) { if ((height / 2) < RADIUS) { - cairo_window.move_to(x0, ((y0 + y1) / 2)); - cairo_window.curve_to(x0, y0, x0, y0, (x0 + x1) / 2, y0); - cairo_window.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2); - cairo_window.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1); - cairo_window.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2); + context.move_to(x0, ((y0 + y1) / 2)); + context.curve_to(x0, y0, x0, y0, (x0 + x1) / 2, y0); + context.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2); + context.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1); + context.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2); } else { - cairo_window.move_to(x0, y0 + RADIUS); - cairo_window.curve_to(x0,y0, x0, y0, (x0 + x1) / 2, y0); - cairo_window.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS); - cairo_window.line_to(x1, y1 - RADIUS); - cairo_window.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1); - cairo_window.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS); + context.move_to(x0, y0 + RADIUS); + context.curve_to(x0,y0, x0, y0, (x0 + x1) / 2, y0); + context.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS); + context.line_to(x1, y1 - RADIUS); + context.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1); + context.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS); } } else { if ((height / 2) < RADIUS) { - cairo_window.move_to(x0, (y0 + y1) / 2); - cairo_window.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0); - cairo_window.line_to(x1 - RADIUS, y0); - cairo_window.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2); - cairo_window.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1); - cairo_window.line_to(x0 + RADIUS, y1); - cairo_window.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2); + context.move_to(x0, (y0 + y1) / 2); + context.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0); + context.line_to(x1 - RADIUS, y0); + context.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2); + context.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1); + context.line_to(x0 + RADIUS, y1); + context.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2); } else { - cairo_window.move_to(x0, y0 + RADIUS); - cairo_window.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0); - cairo_window.line_to(x1 - RADIUS, y0); - cairo_window.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS); - cairo_window.line_to(x1, y1 - RADIUS); - cairo_window.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1); - cairo_window.line_to(x0 + RADIUS, y1); - cairo_window.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS); + context.move_to(x0, y0 + RADIUS); + context.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0); + context.line_to(x1 - RADIUS, y0); + context.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS); + context.line_to(x1, y1 - RADIUS); + context.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1); + context.line_to(x0 + RADIUS, y1); + context.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS); } } - cairo_window.close_path(); + context.close_path(); if (filled) { - cairo_window.fill(); + context.fill(); } else { - cairo_window.set_line_width(LINE_WIDTH); - cairo_window.stroke(); + context.set_line_width(LINE_WIDTH); + context.stroke(); } } -public void draw_right_rounded_rectangle(Gdk.Window window, Gdk.Color color, bool filled, +public void draw_right_rounded_rectangle(Cairo.Context context, Gdk.Color color, bool filled, int x0, int y0, int width, int height) { if (width == 0 || height == 0) return; double x1 = x0 + width; double y1 = y0 + height; - - Cairo.Context cairo_window = Gdk.cairo_create(window); - Gdk.cairo_set_source_color(cairo_window, color); - cairo_window.set_antialias(ANTIALIAS); + + Gdk.cairo_set_source_color(context, color); + context.set_antialias(ANTIALIAS); if ((width / 2) < RADIUS) { if ((height / 2) < RADIUS) { - cairo_window.move_to(x0, y0); - cairo_window.line_to((x0 + x1) / 2, y0); - cairo_window.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2); - cairo_window.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1); - cairo_window.line_to(x0, y1); - cairo_window.line_to(x0, y0); + context.move_to(x0, y0); + context.line_to((x0 + x1) / 2, y0); + context.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2); + context.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1); + context.line_to(x0, y1); + context.line_to(x0, y0); } else { - cairo_window.move_to(x0, y0); - cairo_window.line_to((x0 + x1) / 2, y0); - cairo_window.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS); - cairo_window.line_to(x1, y1 - RADIUS); - cairo_window.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1); - cairo_window.line_to(x0, y1); - cairo_window.line_to(x0, y0); + context.move_to(x0, y0); + context.line_to((x0 + x1) / 2, y0); + context.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS); + context.line_to(x1, y1 - RADIUS); + context.curve_to(x1, y1, x1, y1, (x1 + x0) / 2, y1); + context.line_to(x0, y1); + context.line_to(x0, y0); } } else { if ((height / 2) < RADIUS) { - cairo_window.move_to(x0, y0); - cairo_window.line_to(x1 - RADIUS, y0); - cairo_window.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2); - cairo_window.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1); - cairo_window.line_to(x0, y1); - cairo_window.line_to(x0, y0); + context.move_to(x0, y0); + context.line_to(x1 - RADIUS, y0); + context.curve_to(x1, y0, x1, y0, x1, (y0 + y1) / 2); + context.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1); + context.line_to(x0, y1); + context.line_to(x0, y0); } else { - cairo_window.move_to(x0, y0); - cairo_window.line_to(x1 - RADIUS, y0); - cairo_window.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS); - cairo_window.line_to(x1, y1 - RADIUS); - cairo_window.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1); - cairo_window.line_to(x0, y1); - cairo_window.line_to(x0, y0); + context.move_to(x0, y0); + context.line_to(x1 - RADIUS, y0); + context.curve_to(x1, y0, x1, y0, x1, y0 + RADIUS); + context.line_to(x1, y1 - RADIUS); + context.curve_to(x1, y1, x1, y1, x1 - RADIUS, y1); + context.line_to(x0, y1); + context.line_to(x0, y0); } } - cairo_window.close_path(); + context.close_path(); if (filled) { - cairo_window.fill(); + context.fill(); } else { - cairo_window.set_line_width(LINE_WIDTH); - cairo_window.stroke(); + context.set_line_width(LINE_WIDTH); + context.stroke(); } } -public void draw_left_rounded_rectangle(Gdk.Window window, Gdk.Color color, bool filled, +public void draw_left_rounded_rectangle(Cairo.Context context, Gdk.Color color, bool filled, int x0, int y0, int width, int height) { if (width == 0 || height == 0) return; @@ -381,69 +379,67 @@ public void draw_left_rounded_rectangle(Gdk.Window window, Gdk.Color color, bool double x1 = x0 + width; double y1 = y0 + height; - Cairo.Context cairo_window = Gdk.cairo_create(window); - Gdk.cairo_set_source_color(cairo_window, color); - cairo_window.set_antialias(ANTIALIAS); + Gdk.cairo_set_source_color(context, color); + context.set_antialias(ANTIALIAS); if ((width / 2) < RADIUS) { if ((height / 2) < RADIUS) { - cairo_window.move_to(x0, ((y0 + y1) / 2)); - cairo_window.curve_to(x0, y0, x0, y0, (x0 + x1) / 2, y0); - cairo_window.line_to(x1, y0); - cairo_window.line_to(x1, y1); - cairo_window.line_to((x1 + x0) / 2, y1); - cairo_window.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2); + context.move_to(x0, ((y0 + y1) / 2)); + context.curve_to(x0, y0, x0, y0, (x0 + x1) / 2, y0); + context.line_to(x1, y0); + context.line_to(x1, y1); + context.line_to((x1 + x0) / 2, y1); + context.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2); } else { - cairo_window.move_to(x0, y0 + RADIUS); - cairo_window.curve_to(x0,y0, x0, y0, (x0 + x1) / 2, y0); - cairo_window.line_to(x1, y0); - cairo_window.line_to(x1, y1); - cairo_window.line_to((x1 + x0) / 2, y1); - cairo_window.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS); + context.move_to(x0, y0 + RADIUS); + context.curve_to(x0,y0, x0, y0, (x0 + x1) / 2, y0); + context.line_to(x1, y0); + context.line_to(x1, y1); + context.line_to((x1 + x0) / 2, y1); + context.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS); } } else { if ((height / 2) < RADIUS) { - cairo_window.move_to(x0, (y0 + y1) / 2); - cairo_window.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0); - cairo_window.line_to(x1, y0); - cairo_window.line_to(x1, y1); - cairo_window.line_to(x0 + RADIUS, y1); - cairo_window.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2); + context.move_to(x0, (y0 + y1) / 2); + context.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0); + context.line_to(x1, y0); + context.line_to(x1, y1); + context.line_to(x0 + RADIUS, y1); + context.curve_to(x0, y1, x0, y1, x0, (y0 + y1) / 2); } else { - cairo_window.move_to(x0, y0 + RADIUS); - cairo_window.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0); - cairo_window.line_to(x1, y0); - cairo_window.line_to(x1, y1); - cairo_window.line_to(x0 + RADIUS, y1); - cairo_window.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS); + context.move_to(x0, y0 + RADIUS); + context.curve_to(x0, y0, x0, y0, x0 + RADIUS, y0); + context.line_to(x1, y0); + context.line_to(x1, y1); + context.line_to(x0 + RADIUS, y1); + context.curve_to(x0, y1, x0, y1, x0, y1 - RADIUS); } } - cairo_window.close_path(); + context.close_path(); if (filled) { - cairo_window.fill(); + context.fill(); } else { - cairo_window.set_line_width(LINE_WIDTH); - cairo_window.stroke(); + context.set_line_width(LINE_WIDTH); + context.stroke(); } } -public void draw_square_rectangle(Gdk.Window window, Gdk.Color color, bool filled, +public void draw_square_rectangle(Cairo.Context context, Gdk.Color color, bool filled, int x, int y, int width, int height) { if (width == 0 || height == 0) return; - Cairo.Context cairo_window = Gdk.cairo_create(window); - Gdk.cairo_set_source_color(cairo_window, color); - cairo_window.set_antialias(ANTIALIAS); + Gdk.cairo_set_source_color(context, color); + context.set_antialias(ANTIALIAS); - cairo_window.rectangle(x, y, width, height); + context.rectangle(x, y, width, height); if (filled) { - cairo_window.fill(); + context.fill(); } else { - cairo_window.set_line_width(LINE_WIDTH); - cairo_window.stroke(); + context.set_line_width(LINE_WIDTH); + context.stroke(); } } diff --git a/src/marina/video_track.vala b/src/marina/video_track.vala index bc858b2..cb8e043 100644 --- a/src/marina/video_track.vala +++ b/src/marina/video_track.vala @@ -22,7 +22,7 @@ public class VideoTrack : Track { Fraction rate1; Fraction rate2; - if (!clip.clipfile.is_online()) + if (!clip.mediafile.is_online()) return true; if (clips.size == 0) @@ -33,7 +33,7 @@ public class VideoTrack : Track { return false; } - if (!clip.clipfile.get_frame_rate(out rate1)) { + if (!clip.mediafile.get_frame_rate(out rate1)) { error_occurred("can't get frame rate", null); return false; } @@ -94,8 +94,8 @@ public class VideoTrack : Track { return false; foreach (Clip c in clips) { - if (c.clipfile.is_online()) { - bool can = c.clipfile.get_frame_rate(out rate); + if (c.mediafile.is_online()) { + bool can = c.mediafile.get_frame_rate(out rate); assert(can); return can; diff --git a/src/test/Makefile b/src/test/Makefile index 41b0361..bc4398c 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -6,6 +6,7 @@ all: $(PROGRAM) VALA_LDFLAGS = `pkg-config --libs $(EXT_PKGS)` -include sources.mk SRC_FILES += \ + ../marina/MediaFile.vala \ ../marina/ProjectLoader.vala \ ../marina/Logging.vala \ ../marina/util.vala diff --git a/src/test/marina/ProjectLoading.vala b/src/test/marina/ProjectLoading.vala index 488fac2..fd3bf94 100644 --- a/src/test/marina/ProjectLoading.vala +++ b/src/test/marina/ProjectLoading.vala @@ -8,7 +8,7 @@ namespace Model { public class ClipFetcher { public string error_string; - public ClipFile clipfile; + public MediaFile mediafile; string filename; public ClipFetcher(string filename) { @@ -19,12 +19,7 @@ public class ClipFetcher { } public signal void ready(ClipFetcher fetcher); } - -public class ClipFile { - public string filename; -} } - // Describes an XML Document and if the test should consider it a valid or an invalid document struct ValidDocument { public bool valid; @@ -63,7 +58,7 @@ void state_change_fixture_teardown(void *fixture) { bool document_valid; // if a document is invalid, on_error_occurred will set this variable to false -void on_error_occurred(string? message) { +void on_error_occurred(Model.ErrorClass error_class, string? message) { Test.message("received error: %s", message); document_valid = false; }
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor