LogoopenSUSE Build Service > Projects
Sign Up | Log In

View File fillmore-lombard-git.patch of Package fillmore-lombard (Project GNOME:Apps)

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;
 }