[ease] [shapes] Added shapes, backgrounds, and more.



commit f7ef8a99026b2d727877f4315675b634b63735f1
Author: Nate Stedman <natesm gmail com>
Date:   Tue Jul 27 04:05:19 2010 -0400

    [shapes] Added shapes, backgrounds, and more.
    
    New class: Background
    Background contains a gradient, color, and image, replacing
    those properties in Slide.
    
    Cairo-rendered Actors
    Added CairoElement and CairoActor. CairoElement is an abstract
    class that automatically sets up a CairoActor for rendering
    in presentations, bypassing duplication of effort (Clutter/Cairo).
    
    ShapeElement
    Added ShapeElement, a subclass of CairoElement, that renders a
    shape to the screen. ShapeElement also uses Background, so both
    it and Slide will get any additional functionality "for free".
    
    Restructured redraw notifications and undo
    The "needs redraw" system required for rendering Cairo-based
    Actors and other Cairo drawn representations (SlideButtonPanel)
    of Elements is now directly tied into the undo system. If
    something can be undone, something must have changed, so we
    can redraw.

 data/ui/background.ui                              |  310 +++++++++++++++
 data/ui/editor-window.ui                           |   21 +
 data/ui/inspector-element-shape.ui                 |   77 ++++
 data/ui/inspector-slide.ui                         |  305 +--------------
 ease-core/Makefile.am                              |    8 +-
 ease-core/ease-background-widget.vala              |  418 ++++++++++++++++++++
 ease-core/ease-background.vala                     |  225 +++++++++++
 ease-core/ease-cairo-actor.vala                    |   56 +++
 ease-core/ease-cairo-element.vala                  |   28 ++
 ease-core/ease-change-source.vala                  |   56 +++
 ease-core/ease-color.vala                          |   24 +-
 ease-core/ease-element.vala                        |   71 +++-
 ease-core/ease-gradient.vala                       |   83 +---
 ease-core/ease-image-element.vala                  |   16 +-
 ...se-iterables.vala => ease-iterable-models.vala} |    0
 ease-core/ease-media-element.vala                  |    6 +
 ease-core/ease-shape-element.vala                  |  180 +++++++++
 ease-core/ease-slide.vala                          |  182 ++-------
 ease-core/ease-text-element.vala                   |   23 +-
 ease-core/ease-theme.vala                          |   16 +-
 ease-core/ease-undo-action.vala                    |   21 +-
 ease-core/ease-undo-item.vala                      |   10 +
 ease-core/ease-undo-source.vala                    |    6 +
 src/ease-editor-window.vala                        |   29 ++-
 src/ease-inspector-element-pane.vala               |    2 +-
 src/ease-inspector-pane.vala                       |    2 +-
 src/ease-inspector-slide-pane.vala                 |  335 +---------------
 src/ease-inspector-transition-pane.vala            |   12 +-
 src/ease-inspector.vala                            |    3 +-
 src/ease-slide-button-panel.vala                   |    3 +
 30 files changed, 1612 insertions(+), 916 deletions(-)
---
diff --git a/data/ui/background.ui b/data/ui/background.ui
new file mode 100644
index 0000000..b12090d
--- /dev/null
+++ b/data/ui/background.ui
@@ -0,0 +1,310 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkAlignment" id="root">
+    <property name="visible">True</property>
+    <property name="top_padding">4</property>
+    <property name="bottom_padding">4</property>
+    <property name="left_padding">4</property>
+    <property name="right_padding">4</property>
+    <child>
+      <object class="GtkVBox" id="vbox-root">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">10</property>
+        <child>
+          <object class="GtkVBox" id="vbox-style">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkHBox" id="hbox-style">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkLabel" id="style-label">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Background Style</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBox" id="combobox-style">
+                <property name="height_request">30</property>
+                <property name="visible">True</property>
+                <signal name="changed" handler="ease_background_widget_on_background_changed"/>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVBox" id="vbox-color">
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkHBox" id="hbox2">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkLabel" id="color-label">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Background Color</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkColorButton" id="color-color">
+                <property name="height_request">30</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="color">#000000000000</property>
+                <signal name="color_set" handler="ease_background_widget_on_color_set"/>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVBox" id="vbox-gradient">
+            <property name="orientation">vertical</property>
+            <property name="spacing">4</property>
+            <child>
+              <object class="GtkHBox" id="hbox3">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkLabel" id="gradient-label">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Background Gradient</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkHBox" id="hbox4">
+                <property name="visible">True</property>
+                <property name="spacing">4</property>
+                <child>
+                  <object class="GtkColorButton" id="color-startgradient">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="color">#000000000000</property>
+                    <signal name="color_set" handler="ease_background_widget_on_color_set"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkColorButton" id="color-endgradient">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="color">#000000000000</property>
+                    <signal name="color_set" handler="ease_background_widget_on_color_set"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkComboBox" id="combo-gradient">
+                    <property name="height_request">30</property>
+                    <property name="visible">True</property>
+                    <signal name="changed" handler="ease_background_widget_on_gradient_type_changed"/>
+                    <child>
+                      <object class="GtkCellRendererText" id="cellrenderertext1"/>
+                      <attributes>
+                        <attribute name="text">0</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button-reverse">
+                <property name="label" translatable="yes">Reverse Gradient</property>
+                <property name="height_request">30</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="xalign">0</property>
+                <signal name="clicked" handler="ease_background_widget_on_reverse_gradient"/>
+              </object>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkVBox" id="vbox2">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">2</property>
+                <child>
+                  <object class="GtkHBox" id="hbox5">
+                    <property name="visible">True</property>
+                    <child>
+                      <object class="GtkLabel" id="label-angle">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">Gradient Angle</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkHScale" id="hscale-angle">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="update_policy">discontinuous</property>
+                    <property name="adjustment">adjustment-angle</property>
+                    <property name="draw_value">False</property>
+                    <signal name="value_changed" handler="ease_background_widget_on_set_angle"/>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="padding">10</property>
+                <property name="position">3</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVBox" id="vbox-image">
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkHBox" id="hbox1">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkLabel" id="image-label">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Background Image</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkFileChooserButton" id="button-image">
+                <property name="height_request">30</property>
+                <property name="visible">True</property>
+                <signal name="file_set" handler="ease_background_widget_on_file_set"/>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkAdjustment" id="adjustment-angle">
+    <property name="upper">6.2800000000000002</property>
+    <property name="step_increment">0.10000000000000001</property>
+    <property name="page_increment">1</property>
+  </object>
+</interface>
diff --git a/data/ui/editor-window.ui b/data/ui/editor-window.ui
index bfb92e5..06b27ed 100644
--- a/data/ui/editor-window.ui
+++ b/data/ui/editor-window.ui
@@ -214,6 +214,27 @@
                     <signal name="activate" handler="ease_editor_window_insert_text"/>
                   </object>
                 </child>
+                <child>
+                  <object class="GtkSeparatorMenuItem" id="menuitem4">
+                    <property name="visible">True</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkMenuItem" id="Insert Rectangle">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">_Rectangle</property>
+                    <property name="use_underline">True</property>
+                    <signal name="activate" handler="ease_editor_window_on_insert_rectangle"/>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkMenuItem" id="Insert Oval">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">_Oval</property>
+                    <property name="use_underline">True</property>
+                    <signal name="activate" handler="ease_editor_window_on_insert_oval"/>
+                  </object>
+                </child>
               </object>
             </child>
           </object>
diff --git a/data/ui/inspector-element-shape.ui b/data/ui/inspector-element-shape.ui
new file mode 100644
index 0000000..741353d
--- /dev/null
+++ b/data/ui/inspector-element-shape.ui
@@ -0,0 +1,77 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkAlignment" id="root">
+    <property name="visible">True</property>
+    <property name="top_padding">4</property>
+    <property name="bottom_padding">4</property>
+    <property name="left_padding">4</property>
+    <property name="right_padding">4</property>
+    <child>
+      <object class="GtkVBox" id="vbox-root">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">10</property>
+        <child>
+          <object class="GtkVBox" id="vbox-shape-types">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkRadioButton" id="rect">
+                <property name="label" translatable="yes">_Rectangle</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_underline">True</property>
+                <property name="active">True</property>
+                <property name="draw_indicator">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkRadioButton" id="oval">
+                <property name="label" translatable="yes">_Oval</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_underline">True</property>
+                <property name="active">True</property>
+                <property name="draw_indicator">True</property>
+                <property name="group">rect</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHSeparator" id="hseparator1">
+            <property name="visible">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkAdjustment" id="adjustment-angle">
+    <property name="upper">6.2800000000000002</property>
+    <property name="step_increment">0.10000000000000001</property>
+    <property name="page_increment">1</property>
+  </object>
+</interface>
diff --git a/data/ui/inspector-slide.ui b/data/ui/inspector-slide.ui
index 92efe38..f8affa5 100644
--- a/data/ui/inspector-slide.ui
+++ b/data/ui/inspector-slide.ui
@@ -4,313 +4,16 @@
   <!-- interface-naming-policy project-wide -->
   <object class="GtkAlignment" id="root">
     <property name="visible">True</property>
-    <property name="top_padding">4</property>
-    <property name="bottom_padding">4</property>
-    <property name="left_padding">4</property>
-    <property name="right_padding">4</property>
+    <property name="yalign">0</property>
+    <property name="yscale">0</property>
     <child>
-      <object class="GtkVBox" id="vbox-root">
+      <object class="GtkVBox" id="root-box">
         <property name="visible">True</property>
         <property name="orientation">vertical</property>
-        <property name="spacing">10</property>
         <child>
-          <object class="GtkVBox" id="vbox-style">
-            <property name="visible">True</property>
-            <property name="orientation">vertical</property>
-            <child>
-              <object class="GtkHBox" id="hbox-style">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkLabel" id="style-label">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Background Style</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <placeholder/>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkComboBox" id="combobox-style">
-                <property name="height_request">30</property>
-                <property name="visible">True</property>
-                <signal name="changed" handler="ease_inspector_slide_pane_on_background_changed"/>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkVBox" id="vbox-color">
-            <property name="orientation">vertical</property>
-            <child>
-              <object class="GtkHBox" id="hbox2">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkLabel" id="color-label">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Background Color</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <placeholder/>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkColorButton" id="color-color">
-                <property name="height_request">30</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="color">#000000000000</property>
-                <signal name="color_set" handler="ease_inspector_slide_pane_on_color_set"/>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkVBox" id="vbox-gradient">
-            <property name="orientation">vertical</property>
-            <property name="spacing">4</property>
-            <child>
-              <object class="GtkHBox" id="hbox3">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkLabel" id="gradient-label">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Background Gradient</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <placeholder/>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkHBox" id="hbox4">
-                <property name="visible">True</property>
-                <property name="spacing">4</property>
-                <child>
-                  <object class="GtkColorButton" id="color-startgradient">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="receives_default">True</property>
-                    <property name="color">#000000000000</property>
-                    <signal name="color_set" handler="ease_inspector_slide_pane_on_color_set"/>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkColorButton" id="color-endgradient">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="receives_default">True</property>
-                    <property name="color">#000000000000</property>
-                    <signal name="color_set" handler="ease_inspector_slide_pane_on_color_set"/>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkComboBox" id="combo-gradient">
-                    <property name="height_request">30</property>
-                    <property name="visible">True</property>
-                    <signal name="changed" handler="ease_inspector_slide_pane_on_gradient_type_changed"/>
-                    <child>
-                      <object class="GtkCellRendererText" id="cellrenderertext1"/>
-                      <attributes>
-                        <attribute name="text">0</attribute>
-                      </attributes>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="position">2</property>
-                  </packing>
-                </child>
-              </object>
-              <packing>
-                <property name="position">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="button-reverse">
-                <property name="label" translatable="yes">Reverse Gradient</property>
-                <property name="height_request">30</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="xalign">0</property>
-                <signal name="clicked" handler="ease_inspector_slide_pane_on_reverse_gradient"/>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">2</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkVBox" id="vbox2">
-                <property name="visible">True</property>
-                <property name="orientation">vertical</property>
-                <property name="spacing">2</property>
-                <child>
-                  <object class="GtkHBox" id="hbox5">
-                    <property name="visible">True</property>
-                    <child>
-                      <object class="GtkLabel" id="label-angle">
-                        <property name="visible">True</property>
-                        <property name="label" translatable="yes">Gradient Angle</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <placeholder/>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkHScale" id="hscale-angle">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="update_policy">discontinuous</property>
-                    <property name="adjustment">adjustment-angle</property>
-                    <property name="draw_value">False</property>
-                    <signal name="value_changed" handler="ease_inspector_slide_pane_on_set_angle"/>
-                  </object>
-                  <packing>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-              </object>
-              <packing>
-                <property name="padding">10</property>
-                <property name="position">3</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">2</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkVBox" id="vbox-image">
-            <property name="orientation">vertical</property>
-            <child>
-              <object class="GtkHBox" id="hbox1">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkLabel" id="image-label">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Background Image</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <placeholder/>
-                </child>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkFileChooserButton" id="button-image">
-                <property name="height_request">30</property>
-                <property name="visible">True</property>
-                <signal name="file_set" handler="ease_inspector_slide_pane_on_file_set"/>
-              </object>
-              <packing>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">3</property>
-          </packing>
+          <placeholder/>
         </child>
       </object>
     </child>
   </object>
-  <object class="GtkAdjustment" id="adjustment-angle">
-    <property name="upper">6.2800000000000002</property>
-    <property name="step_increment">0.10000000000000001</property>
-    <property name="page_increment">1</property>
-  </object>
 </interface>
diff --git a/ease-core/Makefile.am b/ease-core/Makefile.am
index 68ba4a7..9c0f91e 100644
--- a/ease-core/Makefile.am
+++ b/ease-core/Makefile.am
@@ -14,6 +14,10 @@ AM_CPPFLAGS = \
 libease_core_0_3_la_SOURCES = \
 	ease-actor.vala \
 	ease-animated-zoom-slider.vala \
+	ease-background.vala \
+	ease-background-widget.vala \
+	ease-cairo-actor.vala \
+	ease-cairo-element.vala \
 	ease-color.vala \
 	ease-dialogs.vala \
 	ease-document.vala \
@@ -23,8 +27,9 @@ libease_core_0_3_la_SOURCES = \
 	ease-html-exporter.vala \
 	ease-image-actor.vala \
 	ease-image-element.vala \
-	ease-iterables.vala \
+	ease-iterable-models.vala \
 	ease-media-element.vala \
+	ease-shape-element.vala \
 	ease-slide.vala \
 	ease-temp.vala \
 	ease-text-actor.vala \
@@ -49,7 +54,6 @@ libease_core_0_3_la_VALAFLAGS = \
 	--header=libease-core.h \
 	--thread \
 	--library EaseCore-0.3 \
-	--gir=EaseCore-0.3.gir \
 	--vapidir=../vapi \
 	--pkg flutter-0.3 \
 	-g \
diff --git a/ease-core/ease-background-widget.vala b/ease-core/ease-background-widget.vala
new file mode 100644
index 0000000..5032443
--- /dev/null
+++ b/ease-core/ease-background-widget.vala
@@ -0,0 +1,418 @@
+/*  Ease, a GTK presentation application
+    Copyright (C) 2010 Nate Stedman
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * A widget that controls the properties of an { link Ease.Background}.
+ */
+public class Ease.BackgroundWidget : Gtk.Alignment
+{
+	private const string UI_FILE_PATH = "background.ui";
+	private const string BG_DIALOG_TITLE = _("Select Background Image");
+	
+	private Gtk.ComboBox background_type;
+	private Gtk.ListStore store;
+	private Gtk.ComboBox gradient_type;
+	private Gtk.VBox box_color;
+	private Gtk.VBox box_gradient;
+	private Gtk.VBox box_image;
+	private Gtk.HScale grad_angle;
+	
+	private Gtk.ColorButton bg_color;
+	private Gtk.ColorButton grad_color1;
+	private Gtk.ColorButton grad_color2;
+	private Gtk.FileChooserButton bg_image;
+	
+	private Background background;
+	
+	private bool silence_undo;
+	private Document document;
+	private Slide slide;
+	private Element element;
+	
+	public BackgroundWidget(Background bg, Element e)
+	{
+		background = bg;
+		document = e.parent.parent;
+		element = e;
+		init();
+	}
+	
+	public BackgroundWidget.for_slide(Slide s)
+	{
+		document = s.parent;
+		background = s.background;
+		slide = s;
+		init();
+	}
+	
+	private void init()
+	{	
+		// load the GtkBuilder file
+		var builder = new Gtk.Builder();
+		try
+		{
+			builder.add_from_file(data_path(Path.build_filename(Temp.UI_DIR,
+				                                                UI_FILE_PATH)));
+		}
+		catch (Error e) { error("Error loading UI: %s", e.message); }
+		
+		// add the root of the builder file to this widget
+		add(builder.get_object("root") as Gtk.Widget);
+		set(0, 1, 0, 0);
+		
+		// get controls
+		box_color = builder.get_object("vbox-color") as Gtk.VBox;
+		box_gradient = builder.get_object("vbox-gradient") as Gtk.VBox;
+		box_image = builder.get_object("vbox-image") as Gtk.VBox;
+		bg_color = builder.get_object("color-color") as Gtk.ColorButton;
+		grad_color1 =
+			builder.get_object("color-startgradient") as Gtk.ColorButton;
+		grad_color2 =
+			builder.get_object("color-endgradient") as Gtk.ColorButton;
+		bg_image =
+			builder.get_object("button-image") as Gtk.FileChooserButton;
+		gradient_type =
+			builder.get_object("combo-gradient") as Gtk.ComboBox;
+		grad_angle = builder.get_object("hscale-angle") as Gtk.HScale;
+		
+		// display the correct UI
+		display_bg_ui(background.background_type);
+		
+		// set up the gradient type combobox
+		gradient_type.model = GradientType.list_store();
+		
+		// get the combobox
+		background_type = builder.get_object("combobox-style") as Gtk.ComboBox;
+		
+		// build a liststore for the combobox
+		store = new Gtk.ListStore(2, typeof(string), typeof(BackgroundType));
+		Gtk.TreeIter iter;
+		foreach (var b in BackgroundType.TYPES)
+		{
+			store.append(out iter);
+			store.set(iter, 0, b.description(), 1, b);
+		}
+		
+		var render = new Gtk.CellRendererText();
+		
+		background_type.pack_start(render, true);
+		background_type.set_attributes(render, "text", 0);
+		background_type.model = store;
+		
+		// set the background type combo box
+		Gtk.TreeIter itr;
+		BackgroundType type;
+		store.get_iter_first(out itr);
+		do
+		{
+			store.get(itr, 1, out type);
+			if (type == background.background_type)
+			{
+				background_type.set_active_iter(itr);
+				break;
+			}
+		} while (store.iter_next(ref itr));
+		
+		// set the gradient variant box
+		GradientType grad_type;
+		gradient_type.model.get_iter_first(out itr);
+		do
+		{
+			gradient_type.model.get(itr, 1, out grad_type);
+			if (grad_type == background.gradient.mode)
+			{
+				gradient_type.set_active_iter(itr);
+				break;
+			}
+		} while (gradient_type.model.iter_next(ref itr));
+		
+		
+		// connect signals
+		builder.connect_signals(this);
+	}
+	
+	private void emit_undo(UndoAction action)
+	{
+		if (!silence_undo)
+		{
+			if (slide != null) slide.undo(action);
+			else element.undo(action);
+		}
+	}
+	
+	[CCode (instance_pos = -1)]
+	public void on_background_changed(Gtk.Widget? sender)
+	{
+		Gtk.TreeIter itr;
+		store.get_iter_first(out itr);
+		
+		// find the correct position
+		for (int i = 0; i < background_type.active; i++)
+		{
+			store.iter_next(ref itr);
+		}
+		
+		// get the background type at that position
+		BackgroundType type;
+		store.get(itr, 1, out type);
+		
+		// create an undo action
+		var action = new UndoAction(background, "background-type");
+		
+		// ease doesn't provide a default for images, so one must be requested
+		if (type == BackgroundType.IMAGE && background.image == null)
+		{
+			var dialog = new Gtk.FileChooserDialog(BG_DIALOG_TITLE,
+			                                       widget_window(this),
+			                                       Gtk.FileChooserAction.OPEN,
+			                                       "gtk-cancel",
+			                                       Gtk.ResponseType.CANCEL,
+			                                       "gtk-open",
+			                                       Gtk.ResponseType.ACCEPT);
+			switch (dialog.run())
+			{
+				case Gtk.ResponseType.ACCEPT:
+					try
+					{
+						var fname = dialog.get_filename();
+						background.image_source = fname;
+						var i = document.add_media_file(fname);
+						background.image = i;
+					}
+					catch (GLib.Error e)
+					{
+						critical("Error adding background image: %s",
+						         e.message);
+					}
+					dialog.destroy();
+					break;
+				case Gtk.ResponseType.CANCEL:
+					action.apply();
+					dialog.destroy();
+					return;
+			}
+		}
+		
+		action.applied.connect((a) => {
+			silence_undo = true;
+			background_type.set_active(background.background_type);
+			display_bg_ui(background.background_type);
+			silence_undo = false;
+		});
+		
+		// add properties to the UndoAction and report it to the controller
+		switch (type)
+		{
+			case BackgroundType.COLOR:
+				action.add(background, "color");
+				break;
+			case BackgroundType.GRADIENT:
+				action.add(background, "gradient");
+				break;
+			case BackgroundType.IMAGE:
+				action.add(background, "image");
+				break;
+		}
+		
+		background.background_type = type;
+		
+		emit_undo(action);
+		
+		// switch to that background type
+		display_bg_ui(type);
+	}
+	
+	[CCode (instance_pos = -1)]
+	public void on_gradient_type_changed(Gtk.ComboBox? sender)
+	{
+		var action = new UndoAction(background.gradient, "mode");
+		GradientType type;
+		Gtk.TreeIter itr;
+		sender.model.get_iter_first(out itr);
+		for (int i = 0; i < sender.get_active(); i++)
+		{
+			sender.model.iter_next(ref itr);
+		}
+		sender.model.get(itr, 1, out type);
+		background.gradient.mode = type;
+		emit_undo(action);
+	}
+	
+	[CCode (instance_pos = -1)]
+	public void on_color_set(Gtk.ColorButton? sender)
+	{
+		UndoAction action = null;
+		if (sender == bg_color)
+		{
+			action = background.color.undo_action();
+			background.color.gdk = sender.color;
+			action.applied.connect(() => {
+				sender.set_color(background.gradient.end.gdk);
+			});
+		}
+		else if (sender == grad_color1)
+		{
+			action = background.gradient.start.undo_action();
+			background.gradient.start.gdk = sender.color;
+			action.applied.connect(() => {
+				sender.set_color(background.gradient.start.gdk);
+			});
+		}
+		else if (sender == grad_color2)
+		{
+			action = background.gradient.end.undo_action();
+			background.gradient.end.gdk = sender.color;
+			action.applied.connect(() => {
+				sender.set_color(background.gradient.end.gdk);
+			});
+		}
+		if (action != null) emit_undo(action);
+	}
+	
+	[CCode (instance_pos = -1)]
+	public void on_file_set(Gtk.FileChooserButton? sender)
+	{
+		var action = new UndoAction(background, "image");
+		action.add(background, "image-source");
+		
+		// slide might change in the meantime
+				
+		// set the button's filename when the action is applied
+		action.applied.connect((a) => {
+			// if slide changes, this is still ok
+			if (background.image_source != null)
+			{
+				bg_image.set_filename(background.image_source);
+			}
+			else
+			{
+				bg_image.unselect_all();
+			}
+			
+			display_bg_ui(background.background_type);
+		});
+		
+		try
+		{
+			background.image_source = sender.get_filename();
+			var i = document.add_media_file(sender.get_filename());
+			background.image = i;
+		}
+		catch (GLib.Error e)
+		{
+			critical("Error adding background image: %s", e.message);
+		}
+		
+		emit_undo(action);
+	}
+	
+	[CCode (instance_pos = -1)]
+	public void on_reverse_gradient(Gtk.Widget? sender)
+	{
+		// create an undo action
+		var action = new UndoAction(background.gradient, "start");
+		action.add(background.gradient, "end");
+		
+		// flip the gradient
+		background.gradient.flip();
+		
+		// update the ui
+		grad_color1.set_color(background.gradient.start.gdk);
+		grad_color2.set_color(background.gradient.end.gdk);
+		
+		// reset the buttons when the action is applied
+		action.applied.connect(() => {
+			grad_color1.set_color(background.gradient.start.gdk);
+			grad_color2.set_color(background.gradient.end.gdk);
+		});
+				
+		// add the undo action
+		emit_undo(action);
+	}
+	
+	[CCode (instance_pos = -1)]
+	public void on_set_angle(Gtk.Widget? sender)
+	{
+		var action = new UndoAction(background.gradient, "angle");
+		background.gradient.angle =
+			(sender as Gtk.HScale).adjustment.value;
+		emit_undo(action);
+		
+		action.applied.connect((a) => {
+			display_bg_ui(background.background_type);
+		});
+	}
+	
+	private void display_bg_ui(BackgroundType type)
+	{
+		switch (type)
+		{
+			case BackgroundType.COLOR:
+				box_color.show_all();
+				box_gradient.hide_all();
+				box_image.hide_all();
+				
+				if (background.color == null)
+				{
+					background.color = Color.white;
+				}
+				background.background_type = BackgroundType.COLOR;
+				
+				bg_color.set_color(background.color.gdk);
+								
+				break;
+			
+			case BackgroundType.GRADIENT:
+				box_color.hide_all();
+				box_gradient.show_all();
+				box_image.hide_all();
+				
+				if (background.gradient == null)
+				{
+					background.gradient = new Gradient(Color.black,
+					                                         Color.white);
+					gradient_type.set_active(background.gradient.mode);
+				}
+				background.background_type = BackgroundType.GRADIENT;
+				
+				grad_color1.set_color(background.gradient.start.gdk);
+				grad_color2.set_color(background.gradient.end.gdk);
+				
+				grad_angle.adjustment.value = background.gradient.angle;
+								
+				break;
+			
+			case BackgroundType.IMAGE:
+				box_color.hide_all();
+				box_gradient.hide_all();
+				box_image.show_all();
+				
+				background.background_type = BackgroundType.IMAGE;
+				if (background.image_source != null)
+				{
+					bg_image.set_filename(background.image_source);
+				}
+				else
+				{
+					bg_image.unselect_all();
+				}
+							
+				break;
+		}
+	}
+}
+
diff --git a/ease-core/ease-background.vala b/ease-core/ease-background.vala
new file mode 100644
index 0000000..591a19e
--- /dev/null
+++ b/ease-core/ease-background.vala
@@ -0,0 +1,225 @@
+/*  Ease, a GTK presentation application
+    Copyright (C) 2010 Nate Stedman
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * Background abstraction, supporting { link Color}, { link Gradient}, and
+ * images. For controlling a Background, see { link BackgroundWidget}.
+ */
+public class Ease.Background : GLib.Object
+{
+	/**
+	 * The background background_type of this element.
+	 */
+	public BackgroundType background_type { get; set; }
+	
+	/**
+	 * The background color, if this element uses a solid color for a
+	 * background.
+	 *
+	 * To use this property, { link background_type} must also be set to
+	 * { link BackgroundType.COLOR}.
+	 */
+	public Color color { get; set; default = Color.black; }
+	
+	/**
+	 * The background gradient, if this slide uses a gradient for a background.
+	 *
+	 * To use this property, { link background_type} must also be set to
+	 * { link BackgroundType.GRADIENT}.
+	 */
+	public Gradient gradient { get; set; }
+	
+	/**
+	 * The background image, if this element uses an image for a background.
+	 *
+	 * To use this property, { link background_type} must also be set to
+	 * { link BackgroundType.IMAGE}.
+	 */
+	public string image { get; set; }
+	
+	/**
+	 * The original path to the background image. This path is used in the UI.
+	 */
+	public string image_source { get; set; }
+	
+	/**
+	 * Emitted when an image file is added to this background.
+	 */
+	public signal void image_added(string image_path);
+	
+	/**
+	 * Creates a new Background.
+	 */
+	public Background() { }
+	
+	/**
+	 * Returns the default gradient background.
+	 */
+	public Background.default_gradient()
+	{
+		color = Color.white;
+		gradient = Gradient.default_background;
+		background_type = BackgroundType.GRADIENT;
+	}
+	
+	/**
+	 * Creates a background from a JSON object.
+	 */
+	public Background.from_json(Json.Object obj)
+	{
+		if (obj.has_member(Theme.BACKGROUND_IMAGE))
+		{
+			image = obj.get_string_member(Theme.BACKGROUND_IMAGE);
+			image_source = obj.get_string_member(Theme.BACKGROUND_IMAGE_SOURCE);
+		}
+		if (obj.has_member(Theme.BACKGROUND_COLOR))
+		{
+			color = new Color.from_string(
+				obj.get_string_member(Theme.BACKGROUND_COLOR));
+		}
+		if (obj.has_member(Theme.BACKGROUND_GRADIENT))
+		{
+			gradient = new Gradient.from_string(
+				obj.get_string_member(Theme.BACKGROUND_GRADIENT));
+		}
+		background_type = BackgroundType.from_string(
+			obj.get_string_member(Theme.BACKGROUND_TYPE));
+	}
+	
+	/**
+	 * Writes this background's properties to the given JSON object.
+	 */
+	public void to_json(ref Json.Object obj)
+	{
+		if (image != null)
+		{
+			obj.set_string_member(Theme.BACKGROUND_IMAGE, image);
+			obj.set_string_member(Theme.BACKGROUND_IMAGE_SOURCE, image_source);
+		}
+		if (color != null)
+		{
+			obj.set_string_member(Theme.BACKGROUND_COLOR, color.to_string());
+		}
+		if (gradient!= null)
+		{
+			obj.set_string_member(Theme.BACKGROUND_GRADIENT,
+			                      gradient.to_string());
+		}
+		obj.set_string_member(Theme.BACKGROUND_TYPE, background_type.to_string());
+	}
+	
+	/**
+	 * Sets up a CairoContext to render this background.
+	 *
+	 * @param cr The context to set up.
+	 * @param width The width of the rendering.
+	 * @param height The height of the rendering.
+	 * @param path The base path to any possible media files.
+	 */
+	public void set_cairo(Cairo.Context cr, int width, int height, string path)
+	{
+		switch (background_type)
+		{
+			case BackgroundType.COLOR:
+				color.set_cairo(cr);
+				break;
+			case BackgroundType.GRADIENT:
+				gradient.set_cairo(cr, width, height);
+				break;
+			case BackgroundType.IMAGE:
+				try
+				{
+					string full = Path.build_filename(path, image);
+					var pixbuf = new Gdk.Pixbuf.from_file_at_scale(full,
+			    	                                               width,
+			    	                                               height,
+			    	                                               false);
+					Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
+				}
+				catch (Error e)
+				{
+					critical("Error rendering image background: %s", e.message);
+				}
+				break;
+		}
+	}
+	
+	// TODO: this is a bit hacky, but it works...
+	internal bool owns_undoitem(UndoItem item)
+	{
+		if (this in item) return true;
+		if (color in item) return true;
+		if (gradient.start in item) return true;
+		if (gradient.end in item) return true;
+		if (gradient in item) return true;
+		return false;
+	}
+}
+
+public enum Ease.BackgroundType
+{
+	COLOR,
+	GRADIENT,
+	IMAGE;
+	
+	public const BackgroundType[] TYPES = { COLOR, GRADIENT, IMAGE};
+	
+	/**
+	 * Returns a string representation of this BackgroundType.
+	 */
+	public string to_string()
+	{
+		switch (this)
+		{
+			case COLOR: return Theme.BACKGROUND_TYPE_COLOR;
+			case GRADIENT: return Theme.BACKGROUND_TYPE_GRADIENT;
+			case IMAGE: return Theme.BACKGROUND_TYPE_IMAGE;
+		}
+		return "undefined";
+	}
+	
+	/**
+	 * Creates a BackgroundType from a string representation.
+	 */
+	public static BackgroundType from_string(string str)
+	{
+		switch (str)
+		{
+			case Theme.BACKGROUND_TYPE_COLOR: return COLOR;
+			case Theme.BACKGROUND_TYPE_GRADIENT: return GRADIENT;
+			case Theme.BACKGROUND_TYPE_IMAGE: return IMAGE;
+		}
+		
+		warning("%s is not a gradient type", str);
+		return COLOR;
+	}
+	
+	/**
+	 * Returns a description of the BackgroundType.
+	 */
+	public string description()
+	{
+		switch (this)
+		{
+			case COLOR: return _("Solid Color");
+			case GRADIENT: return _("Gradient");
+			case IMAGE: return _("Image");
+		}
+		return "undefined";
+	}
+}
+
diff --git a/ease-core/ease-cairo-actor.vala b/ease-core/ease-cairo-actor.vala
new file mode 100644
index 0000000..78a1c6d
--- /dev/null
+++ b/ease-core/ease-cairo-actor.vala
@@ -0,0 +1,56 @@
+/*  Ease, a GTK presentation application
+    Copyright (C) 2010 Nate Stedman
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * An actor that automatically uses its { link Element}'s Cairo rendering.
+ */
+public class Ease.CairoActor : Actor
+{
+	private Clutter.CairoTexture tex;
+	
+	public CairoActor(Element e, ActorContext ctx)
+	{
+		base(e, ctx);
+		
+		tex = new Clutter.CairoTexture((uint)e.width, (uint)e.height);
+		add_actor(tex);
+		contents = tex;
+		contents.width = e.width;
+		contents.height = e.height;
+		x = e.x;
+		y = e.y;
+		e.changed.connect(draw);
+		
+		draw();
+	}
+	
+	internal void draw()
+	{
+		//debug("drawing");
+		tex.set_surface_size((uint)element.width, (uint)element.height);
+		tex.clear();
+		var cr = tex.create();
+		try
+		{
+			element.cairo_render(cr);
+		}
+		catch (Error e)
+		{
+			critical("Error rendering CairoActor: %s", e.message);
+		}
+	}
+}
diff --git a/ease-core/ease-cairo-element.vala b/ease-core/ease-cairo-element.vala
new file mode 100644
index 0000000..fb72566
--- /dev/null
+++ b/ease-core/ease-cairo-element.vala
@@ -0,0 +1,28 @@
+/*  Ease, a GTK presentation application
+    Copyright (C) 2010 Nate Stedman
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * An element that uses its { link Element.cairo_render} method to draw its
+ * { link Actor}s.
+ */
+public abstract class Ease.CairoElement : Element
+{
+	public override Actor actor(ActorContext ctx)
+	{
+		return new CairoActor(this, ctx);
+	}
+}
diff --git a/ease-core/ease-change-source.vala b/ease-core/ease-change-source.vala
new file mode 100644
index 0000000..834433b
--- /dev/null
+++ b/ease-core/ease-change-source.vala
@@ -0,0 +1,56 @@
+/*  Ease, a GTK presentation application
+    Copyright (C) 2010 Nate Stedman
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+public interface ChangeSource : GLib.Object
+{
+	/**
+	 * Classes that implement the ChangeSource interface should use this signal
+	 * to notify a parent of a new change.
+	 */
+	public signal void changed();
+	
+	/**
+	 * Emitted when a change is forwarded.
+	 */
+	protected signal void change_forwarded();
+	
+	/**
+	 * Forwards a change notification onwards.
+	 */
+	public void forward_changes()
+	{
+		changed();
+		change_forwarded();
+	}
+	
+	/**
+	 * Listens for incoming changes from the specified ChangeSource, and
+	 * forwards them onwards.
+	 */
+	protected void listen_changes(ChangeSource source)
+	{
+		source.changed.connect(forward_changes);
+	}
+	
+	/**
+	 * Stops listening to an ChangeSource.
+	 */
+	protected void silence_changes(ChangeSource source)
+	{
+		source.changed.disconnect(forward_changes);
+	}
+}
diff --git a/ease-core/ease-color.vala b/ease-core/ease-color.vala
index 4355918..1b5e540 100644
--- a/ease-core/ease-color.vala
+++ b/ease-core/ease-color.vala
@@ -61,11 +61,10 @@ public class Ease.Color : GLib.Object
 			}
 			else if (value > 1)
 			{
-				warning("red value must be <= 0, %f is not", value);
+				warning("red value must be <= 1, %f is not", value);
 				red_priv = 1;
 			}
 			else red_priv = value;
-			if (!silence_changed) changed(this);
 		}
 	}
 	private double red_priv;
@@ -85,11 +84,10 @@ public class Ease.Color : GLib.Object
 			}
 			else if (value > 1)
 			{
-				warning("green value must be <= 0, %f is not", value);
+				warning("green value must be <= 1, %f is not", value);
 				green_priv = 1;
 			}
 			else green_priv = value;
-			if (!silence_changed) changed(this);
 		}
 	}
 	private double green_priv;
@@ -109,11 +107,10 @@ public class Ease.Color : GLib.Object
 			}
 			else if (value > 1)
 			{
-				warning("blue value must be <= 0, %f is not", value);
+				warning("blue value must be <= 1, %f is not", value);
 				blue_priv = 1;
 			}
 			else blue_priv = value;
-			if (!silence_changed) changed(this);
 		}
 	}
 	private double blue_priv;
@@ -133,11 +130,10 @@ public class Ease.Color : GLib.Object
 			}
 			else if (value > 1)
 			{
-				warning("alpha value must be <= 0, %f is not", value);
+				warning("alpha value must be <= 1, %f is not", value);
 				alpha_priv = 1;
 			}
 			else alpha_priv = value;
-			if (!silence_changed) changed(this);
 		}
 	}
 	private double alpha_priv;
@@ -157,13 +153,10 @@ public class Ease.Color : GLib.Object
 		}
 		set
 		{
-			silence_changed = true;
 			red = value.red / 255f;
 			green = value.green / 255f;
 			blue = value.blue / 255f;
 			alpha = value.alpha / 255f;
-			silence_changed = false;
-			changed(this);
 		}
 	}
 	
@@ -184,23 +177,14 @@ public class Ease.Color : GLib.Object
 		}
 		set
 		{
-			silence_changed = true;
 			red = value.red / 65535f;
 			green = value.green / 65535f;
 			blue = value.blue / 65535f;
 			alpha = 1;
-			silence_changed = false;
-			changed(this);
 		}
 	}
 	
 	/**
-	 * Emitted when any of the color's properties is changed.
-	 */
-	public signal void changed(Color self);
-	private bool silence_changed;
-	
-	/**
 	 * Creates an opaque color.
 	 */
 	public Color.rgb(double r, double g, double b)
diff --git a/ease-core/ease-element.vala b/ease-core/ease-element.vala
index 006309f..a6f7473 100644
--- a/ease-core/ease-element.vala
+++ b/ease-core/ease-element.vala
@@ -37,7 +37,16 @@ public abstract class Ease.Element : GLib.Object, UndoSource
 	/**
 	 * The { link Slide} that this Element is a part of.
 	 */
-	internal Slide parent { get; set; }
+	internal Slide parent
+	{
+		get { return parent_priv; }
+		set
+		{
+			parent_priv = value;
+			changed.connect(() => parent.changed(parent));
+		}
+	}
+	private Slide parent_priv;
 	
 	/**
 	 * The { link Document} that this Element is part of. get-only.
@@ -45,10 +54,23 @@ public abstract class Ease.Element : GLib.Object, UndoSource
 	internal Document document { get { return parent.parent; } }
 	
 	/**
+	 * Notifies of a position change for this Element. Note that the "x" and "y"
+	 * properties do not emit the "changed" signal - only this signal.
+	 */
+	public signal void position_changed();
+	
+	/**
+	 * Notifies of a general change in the Element. Subclasses that play nice
+	 * should add handlers to trigger this signal.
+	 */
+	public signal void changed();
+	
+	/**
 	 * Creates an Element from a JsonObject
 	 */
-	internal Element.from_json(Json.Object obj)
+	public Element.from_json(Json.Object obj)
 	{
+		signals();
 		identifier = obj.get_string_member(Theme.E_IDENTIFIER);
 		x = (float)obj.get_string_member(Theme.X).to_double();
 		y = (float)obj.get_string_member(Theme.Y).to_double();
@@ -59,9 +81,20 @@ public abstract class Ease.Element : GLib.Object, UndoSource
 	}
 	
 	/**
+	 * Connects base Element signals.
+	 */
+	public virtual void signals()
+	{
+		notify["width"].connect((o, p) => changed());
+		notify["height"].connect((o, p) => changed());
+		notify["x"].connect((o, p) => position_changed());
+		notify["y"].connect((o, p) => position_changed());
+	}
+	
+	/**
 	 * Writes an Element to a JsonObject
 	 */
-	internal virtual Json.Object to_json()
+	public virtual Json.Object to_json()
 	{
 		var obj = new Json.Object();
 		
@@ -97,12 +130,12 @@ public abstract class Ease.Element : GLib.Object, UndoSource
 	 * @param exporter The { link HTMLExporter}, for the path and progress.
 	 * @param amount The amount progress should increase by when done.
 	 */
-	public virtual void to_html(ref string html,
-	                            HTMLExporter exporter,
-	                            double amount)
+	internal virtual void to_html(ref string html,
+	                              HTMLExporter exporter,
+	                              double amount)
 	{
 		// write the markup
-		write_html(ref html, exporter);
+		html += html_render(exporter);
 		
 		// advance the progress bar
 		exporter.add_progress(amount);
@@ -114,7 +147,7 @@ public abstract class Ease.Element : GLib.Object, UndoSource
 	 * @param html The HTML string in its current state.
 	 * @param exporter The { link HTMLExporter}, for its path.
 	 */
-	protected abstract void write_html(ref string html, HTMLExporter exporter);
+	protected abstract string html_render(HTMLExporter exporter);
 	
 	/**
 	 * Renders this Element to a CairoContext.
@@ -131,11 +164,23 @@ public abstract class Ease.Element : GLib.Object, UndoSource
 	public abstract Actor actor(ActorContext c);
 	
 	/**
-	 * Returns an Inspector widget for editing this Element.
+	 * Returns an Inspector widget for editing this Element. This is
+	 * a new widget - use { link get_inspector_widget} to retrieve the cached
+	 * widget (or automatically create a new one if needed.
 	 */
 	public abstract Gtk.Widget inspector_widget();
 	
 	/**
+	 * Returns the Element's inspector widget.
+	 */
+	public Gtk.Widget get_inspector_widget()
+	{
+		if (inspector_widg != null) return inspector_widg;
+		return inspector_widg = inspector_widget();
+	}
+	private Gtk.Widget inspector_widg;
+	
+	/**
 	 * Returns a GList of ToolItems to add to the main toolbar when this
 	 * Element is selected.
 	 */
@@ -203,13 +248,5 @@ public abstract class Ease.Element : GLib.Object, UndoSource
 	 * If the Element has been edited by the user in the past.
 	 */
 	public bool has_been_edited { get; set; }
-	
-	/**
-	 * Notifies of changes to the Element.
-	 */
-	public void changed()
-	{
-		if (parent != null) parent.changed(parent);
-	}
 }
 
diff --git a/ease-core/ease-gradient.vala b/ease-core/ease-gradient.vala
index 4395e37..46d72ef 100644
--- a/ease-core/ease-gradient.vala
+++ b/ease-core/ease-gradient.vala
@@ -33,57 +33,22 @@ public class Ease.Gradient : GLib.Object
 	/**
 	 * The starting { link Color} of the gradient.
 	 */
-	public Color start
-	{
-		get { return start_priv; }
-		set
-		{
-			if (start_priv != null)
-			{
-				start_priv.changed.disconnect(color_changed);
-			}
-			
-			start_priv = value;
-			start_priv.changed.connect(color_changed);
-			changed(this);
-		}
-	}
-	private Color start_priv;
+	public Color start { get; set; default = Color.white; }
 	
 	/**
 	 * The ending { link Color} of the gradient.
 	 */
-	public Color end
-	{
-		get { return end_priv; }
-		set
-		{
-			if (end_priv != null)
-			{
-				end_priv.changed.disconnect(color_changed);
-			}
-			
-			end_priv = value;
-			end_priv.changed.connect(color_changed);
-			changed(this);
-		}
-	}
-	private Color end_priv;
+	public Color end { get; set; default = Color.black; }
 	
 	/**
 	 * The { link GradientType} of the gradient.
 	 */
-	public GradientType mode { get; set; }
+	public GradientType mode { get; set; default = GradientType.LINEAR; }
 	
 	/**
 	 * The angle, in radians, of the gradient, if it is linear.
 	 */
-	public double angle { get; set; }
-	
-	/**
-	 * Emitted when the gradient's appearance is changed in any way.
-	 */
-	public signal void changed(Gradient self);
+	public double angle { get; set; default = 0; }
 	
 	/**
 	 * Returns a copy of the default background gradient.
@@ -102,9 +67,6 @@ public class Ease.Gradient : GLib.Object
 		end = end_color;
 		mode = GradientType.LINEAR;
 		angle = 0;
-		
-		notify["mode"].connect((a, b) => changed(this));
-		notify["angle"].connect((a, b) => changed(this));
 	}
 	
 	/**
@@ -115,9 +77,6 @@ public class Ease.Gradient : GLib.Object
 		this(start_color, end_color);
 		mode = GradientType.LINEAR_MIRRORED;
 		angle = 0;
-		
-		notify["mode"].connect((a, b) => changed(this));
-		notify["angle"].connect((a, b) => changed(this));
 	}
 	
 	/**
@@ -128,9 +87,6 @@ public class Ease.Gradient : GLib.Object
 		this(start_color, end_color);
 		mode = GradientType.RADIAL;
 		angle = 0;
-		
-		notify["mode"].connect((a, b) => changed(this));
-		notify["angle"].connect((a, b) => changed(this));
 	}
 	
 	/**
@@ -143,9 +99,6 @@ public class Ease.Gradient : GLib.Object
 		end = new Color.from_string(split[1]);
 		mode = GradientType.from_string(split[2]);
 		angle = split[3].to_double();
-		
-		notify["mode"].connect((a, b) => changed(this));
-		notify["angle"].connect((a, b) => changed(this));
 	}
 	
 	/**
@@ -189,7 +142,16 @@ public class Ease.Gradient : GLib.Object
 	{
 		cr.save();
 		cr.rectangle(0, 0, width, height);
-		
+		set_cairo(cr, width, height);
+		cr.fill();
+		cr.restore();
+	}
+	
+	/**
+	 * Sets a CairoContext's source to this gradient.
+	 */
+	public void set_cairo(Cairo.Context cr, int width, int height)
+	{
 		var x_orig = width / 2;
 		var y_orig = height / 2;
 		var dist_x = (int)(Math.cos(angle + Math.PI / 2) * y_orig);
@@ -198,7 +160,7 @@ public class Ease.Gradient : GLib.Object
 		Cairo.Pattern pattern;
 		switch (mode)
 		{
-			case GradientType.LINEAR:				
+			case GradientType.LINEAR:
 				pattern = new Cairo.Pattern.linear(x_orig - dist_x,
 				                                   y_orig - dist_y,
 				                                   x_orig + dist_x,
@@ -222,8 +184,8 @@ public class Ease.Gradient : GLib.Object
 				break;
 			default: // radial
 				pattern = new Cairo.Pattern.radial(width / 2, height / 2, 0,
-				                                       width / 2, height / 2,
-				                                       width / 2);
+				                                   width / 2, height / 2,
+				                                   width / 2);
 				pattern.add_color_stop_rgba(0, start.red, start.green,
 						                    start.blue, start.alpha);
 				pattern.add_color_stop_rgba(1, end.red, end.green,
@@ -232,17 +194,6 @@ public class Ease.Gradient : GLib.Object
 		}
 		
 		cr.set_source(pattern);
-		cr.fill();
-		
-		cr.restore();
-	}
-	
-	/**
-	 * Signal handler for { link Color.changed} signals.
-	 */
-	private void color_changed(Color change)
-	{
-		changed(this);
 	}
 }
 
diff --git a/ease-core/ease-image-element.vala b/ease-core/ease-image-element.vala
index b277cdf..75d430a 100644
--- a/ease-core/ease-image-element.vala
+++ b/ease-core/ease-image-element.vala
@@ -22,13 +22,13 @@
 public class Ease.ImageElement : MediaElement
 {
 	private const string UI_FILE_PATH = "inspector-element-image.ui";
-	private Gtk.Widget inspector_pane;
 	
 	/**
 	 * Create a new element.
 	 */
 	public ImageElement()
 	{
+		signals();
 	}
 	
 	internal ImageElement.from_json(Json.Object obj)
@@ -43,8 +43,6 @@ public class Ease.ImageElement : MediaElement
 	
 	public override Gtk.Widget inspector_widget()
 	{
-		if (inspector_pane != null) return inspector_pane;
-		
 		var builder = new Gtk.Builder();
 		try
 		{
@@ -81,13 +79,13 @@ public class Ease.ImageElement : MediaElement
 		});
 		
 		// return the root
-		return inspector_pane = builder.get_object("root") as Gtk.Widget;
+		return builder.get_object("root") as Gtk.Widget;
 	}
 	
-	public override void write_html(ref string html, HTMLExporter exporter)
+	public override string html_render(HTMLExporter exporter)
 	{
 		// open the img tag
-		html += "<img class=\"image element\" ";
+		string html = "<img class=\"image element\" ";
 		
 		// set the image's style
 		html += "style=\"";
@@ -103,6 +101,8 @@ public class Ease.ImageElement : MediaElement
 		
 		// copy the image file
 		exporter.copy_file(filename, parent.parent.path);
+		
+		return html;
 	}
 
 	/**
@@ -118,9 +118,9 @@ public class Ease.ImageElement : MediaElement
 		                                               (int)height,
 		                                               false);
 		
-		Gdk.cairo_set_source_pixbuf(context, pixbuf, x, y);
+		Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0);
 		
-		context.rectangle(x, y, width, height);
+		context.rectangle(0, 0, width, height);
 		context.fill();
 	}
 }
diff --git a/ease-core/ease-iterables.vala b/ease-core/ease-iterable-models.vala
similarity index 100%
rename from ease-core/ease-iterables.vala
rename to ease-core/ease-iterable-models.vala
diff --git a/ease-core/ease-media-element.vala b/ease-core/ease-media-element.vala
index aad95a7..d13b21e 100644
--- a/ease-core/ease-media-element.vala
+++ b/ease-core/ease-media-element.vala
@@ -40,6 +40,12 @@ public abstract class Ease.MediaElement : Element
 		return obj;
 	}
 	
+	public override void signals()
+	{
+		base.signals();
+		notify["filename"].connect((o, p) => changed());
+	}
+	
 	/**
 	 * The path to a media file.
 	 */
diff --git a/ease-core/ease-shape-element.vala b/ease-core/ease-shape-element.vala
new file mode 100644
index 0000000..4993e6e
--- /dev/null
+++ b/ease-core/ease-shape-element.vala
@@ -0,0 +1,180 @@
+/*  Ease, a GTK presentation application
+    Copyright (C) 2010 Nate Stedman
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+public class Ease.ShapeElement : CairoElement
+{
+	private const string UI_FILE = "inspector-element-shape.ui";
+	
+	/**
+	 * The shape of this ShapeElement.
+	 */
+	public ShapeType shape_type { get; private set; }
+	
+	/**
+	 * The background of this ShapeElement.
+	 */
+	private Background background { get; private set; }
+	
+	/**
+	 * Creates a new ShapeElement.
+	 *
+	 * @param type The shape of the element (rectangle, oval).
+	 */
+	public ShapeElement(ShapeType type)
+	{
+		signals();
+		
+		shape_type = type;
+		background = new Background.default_gradient();
+	}
+	
+	/**
+	 * Constructs a ShapeElement from a JsonObject.
+	 */
+	public ShapeElement.from_json(Json.Object obj)
+	{
+		base.from_json(obj);
+		shape_type =
+			ShapeType.from_string(obj.get_string_member(Theme.SHAPE_TYPE));
+		
+		// read the shapes's background properties
+		background = new Background.from_json(obj);
+	}
+	
+	/**
+	 * Serializes this ShapeElement to JSON.
+	 */
+	public override Json.Object to_json()
+	{
+		var obj = base.to_json();
+		obj.set_string_member(Theme.SHAPE_TYPE, shape_type.to_string());
+		
+		// write the shape's background properties
+		background.to_json(ref obj);
+		
+		return obj;
+	}
+	
+	/**
+	 * Renders (or doesn't, it isn't supported yet) this ShapeElement as HTML.
+	 * When implemented, this should be done in CairoElement probably, so it
+	 * can be generic to anything else Cairo-based.
+	 */
+	public override string html_render(HTMLExporter exporter)
+	{
+		warning("HTML Export not currently supported for shapes");
+		return "<!-- HTML Export not supported for shapes -->";
+	}
+	
+	/**
+	 * { inheritDoc}
+	 */
+	public override Gtk.Widget inspector_widget()
+	{
+		var builder = new Gtk.Builder();
+		try
+		{
+			builder.add_from_file(data_path(Path.build_filename(Temp.UI_DIR,
+				                                                UI_FILE)));
+		}
+		catch (Error e) { error("Error loading UI: %s", e.message); }
+		
+		var bg = new BackgroundWidget(background, this);
+		
+		(builder.get_object("rect") as Gtk.Button).clicked.connect(() => {
+			shape_type = ShapeType.RECTANGLE;
+		});
+		
+		(builder.get_object("oval") as Gtk.Button).clicked.connect(() => {
+			shape_type = ShapeType.OVAL;
+		});
+			
+		(builder.get_object("vbox-root") as Gtk.Box).pack_end(
+			bg, false, true, 0);
+			
+		bg.show();
+		
+		return builder.get_object("root") as Gtk.Widget;
+	}
+	
+	/**
+	 * Renders this ShapeElement to a CairoContext.
+	 *
+	 * @param cr The context to render to.
+	 */
+	public override void cairo_render(Cairo.Context cr)
+	{
+		background.set_cairo(cr, (int)width, (int)height, parent.parent.path);
+		
+		switch (shape_type)
+		{
+			case ShapeType.RECTANGLE:
+				cr.rectangle(0, 0, width, height);
+				break;
+			case ShapeType.OVAL:
+				cr.translate(width / 2, height / 2);
+				cr.scale(width / 2, height / 2);
+				cr.arc(0, 0, 1, 0, 2 * Math.PI);
+				break;
+		}
+		cr.fill();
+	}
+	
+	public override void signals()
+	{
+		base.signals();
+		
+		notify["shape-type"].connect((o, p) => changed());
+		
+		undo.connect((item) => {
+			if (background.owns_undoitem(item)) changed();
+		});
+	}
+}
+
+/**
+ * Enumerates the possible shapes of a ShapeElement.
+ */
+public enum Ease.ShapeType
+{
+	/**
+	 * A basic rectangle.
+	 */
+	RECTANGLE = 0,
+	
+	/**
+	 * An oval, not restricted in aspect ratio.
+	 */
+	OVAL = 1;
+	
+	/**
+	 * Parses a ShapeType from a string representation.
+	 */
+	internal static ShapeType from_string(string str)
+	{
+		switch (str)
+		{
+			case "EASE_SHAPE_TYPE_RECTANGLE":
+				return RECTANGLE;
+			case "EASE_SHAPE_TYPE_OVAL":
+				return OVAL;
+			default:
+				critical("Invalid shape type: %s", str);
+				return RECTANGLE;
+		}
+	}
+}
diff --git a/ease-core/ease-slide.vala b/ease-core/ease-slide.vala
index d227d0f..0e190d7 100644
--- a/ease-core/ease-slide.vala
+++ b/ease-core/ease-slide.vala
@@ -66,68 +66,9 @@ public class Ease.Slide : GLib.Object, UndoSource
 	public double advance_delay { get; set; }
 	
 	/**
-	 * The background type of this slide.
+	 * The background of this Slide.
 	 */
-	public BackgroundType background_type { get; set; }
-	
-	/**
-	 * The background color, if this slide uses a solid color for a background.
-	 *
-	 * To use this property, { link background_type} must also be set to
-	 * { link BackgroundType.COLOR}.
-	 */
-	public Color background_color
-	{
-		get { return background_color_priv; }
-		set
-		{
-			if (background_color_priv != null)
-			{
-				background_color_priv.changed.disconnect(bg_changed);
-			}
-			
-			background_color_priv = value;
-			background_color_priv.changed.connect(bg_changed);
-			changed(this);
-		}
-	}
-	private Color background_color_priv;
-	
-	/**
-	 * The background gradient, if this slide uses a gradient for a background.
-	 *
-	 * To use this property, { link background_type} must also be set to
-	 * { link BackgroundType.GRADIENT}.
-	 */
-	public Gradient background_gradient
-	{
-		get { return background_gradient_priv; }
-		set
-		{
-			if (background_gradient_priv != null)
-			{
-				background_gradient_priv.changed.disconnect(bg_changed);
-			}
-			
-			background_gradient_priv = value;
-			background_gradient_priv.changed.connect(bg_changed);
-			changed(this);
-		}
-	}
-	private Gradient background_gradient_priv;
-	
-	/**
-	 * The background image, if this slide uses an image for a background.
-	 *
-	 * To use this property, { link background_type} must also be set to
-	 * { link BackgroundType.IMAGE}.
-	 */
-	public string background_image { get; set; }
-	
-	/**
-	 * The original path to the background image. This path is used in the UI.
-	 */
-	public string background_image_source { get; set; }
+	public Background background { get; set; }
 	
 	/**
 	 * The absolute path of the background image, if one is set.
@@ -137,7 +78,7 @@ public class Ease.Slide : GLib.Object, UndoSource
 		owned get
 		{
 			string p = parent == null ? theme.path : parent.path;
-			return Path.build_filename(p, background_image);
+			return Path.build_filename(p, background.image);
 		}
 	}
 	
@@ -237,8 +178,12 @@ public class Ease.Slide : GLib.Object, UndoSource
 	 */
 	public Slide()
 	{
-		notify["background-type"].connect((a, b) => background_changed(this));
-		notify["background-image"].connect((a, b) => background_changed(this));
+		background = new Background();
+		
+		// inspect undo actions passed through the slide, check for bg changes
+		undo.connect((item) => {
+			if (background.owns_undoitem(item)) background_changed(this);
+		});
 	}
 	
 	/**
@@ -283,23 +228,23 @@ public class Ease.Slide : GLib.Object, UndoSource
 		// read the slide's background properties
 		if (obj.has_member(Theme.BACKGROUND_IMAGE))
 		{
-			background_image = obj.get_string_member(Theme.BACKGROUND_IMAGE);
-			background_image_source =
+			background.image = obj.get_string_member(Theme.BACKGROUND_IMAGE);
+			background.image_source =
 				obj.get_string_member("background-image-source");
 		}
 		if (obj.has_member(Theme.BACKGROUND_COLOR))
 		{
-			background_color =
+			background.color =
 				new Color.from_string(
 				obj.get_string_member(Theme.BACKGROUND_COLOR));
 		}
 		if (obj.has_member(Theme.BACKGROUND_GRADIENT))
 		{
-			background_gradient =
+			background.gradient =
 				new Gradient.from_string(
 				obj.get_string_member(Theme.BACKGROUND_GRADIENT));
 		}
-		background_type = BackgroundType.from_string(
+		background.background_type = BackgroundType.from_string(
 			obj.get_string_member(Theme.BACKGROUND_TYPE));
 		
 		// parse the elements
@@ -317,6 +262,10 @@ public class Ease.Slide : GLib.Object, UndoSource
 			{
 				e = new ImageElement.from_json(node);
 			}
+			else if (type == "EaseShapeElement")
+			{
+				e = new ShapeElement.from_json(node);
+			}
 			else
 			{
 				e = new TextElement.from_json(node);
@@ -341,24 +290,24 @@ public class Ease.Slide : GLib.Object, UndoSource
 		obj.set_string_member("title", title);
 		
 		// write the slide's background properties
-		if (background_image != null)
+		if (background.image != null)
 		{
-			obj.set_string_member(Theme.BACKGROUND_IMAGE, background_image);
+			obj.set_string_member(Theme.BACKGROUND_IMAGE, background.image);
 			obj.set_string_member("background-image-source",
-			                      background_image_source);
+			                      background.image_source);
 		}
-		if (background_color != null)
+		if (background.color != null)
 		{
 			obj.set_string_member(Theme.BACKGROUND_COLOR,
-			                      background_color.to_string());
+			                      background.color.to_string());
 		}
-		if (background_gradient != null)
+		if (background.gradient != null)
 		{
 			obj.set_string_member(Theme.BACKGROUND_GRADIENT,
-			                      background_gradient.to_string());
+			                      background.gradient.to_string());
 		}
 		obj.set_string_member(Theme.BACKGROUND_TYPE,
-		                      background_type.to_string());
+		                      background.background_type.to_string());
 		
 		// add the slide's elements
 		var json_elements = new Json.Array();
@@ -460,11 +409,16 @@ public class Ease.Slide : GLib.Object, UndoSource
 	public void cairo_render_sized(Cairo.Context context,
 	                               int w, int h) throws GLib.Error
 	{
+		context.save();
 		cairo_render_background(context, w, h);
+		context.restore();
 		
 		foreach (var e in elements)
 		{
+			context.save();
+			context.translate(e.x, e.y);
 			e.cairo_render(context);
+			context.restore();
 		}
 	}
 	
@@ -478,15 +432,15 @@ public class Ease.Slide : GLib.Object, UndoSource
 	public void cairo_render_background(Cairo.Context cr,
 	                                    int w, int h) throws GLib.Error
 	{
-		switch (background_type)
+		switch (background.background_type)
 		{
 			case BackgroundType.COLOR:
 				cr.rectangle(0, 0, w, h);
-				background_color.set_cairo(cr);
+				background.color.set_cairo(cr);
 				cr.fill();
 				break;
 			case BackgroundType.GRADIENT:
-				background_gradient.cairo_render_rect(cr, w, h);
+				background.gradient.cairo_render_rect(cr, w, h);
 				break;
 			case BackgroundType.IMAGE:
 				var pixbuf = new Gdk.Pixbuf.from_file_at_scale(background_abs,
@@ -518,11 +472,11 @@ public class Ease.Slide : GLib.Object, UndoSource
 		html += "<div class=\"slide\" id=\"slide" +
 		        index.to_string() + "\" ";
 		
-		if (background_image == null)
+		if (background.image == null)
 		{
 			// give the slide a background color
 			html += "style=\"background-color: " +
-			        background_color.clutter.to_string().
+			        background.color.clutter.to_string().
 			        substring(0, 7) + "\">";
 		}
 		else
@@ -531,13 +485,13 @@ public class Ease.Slide : GLib.Object, UndoSource
 			html += ">";
 			
 			// add the background image
-			html += "<img src=\"" + exporter.basename + " " + background_image +
+			html += "<img src=\"" + exporter.basename + " " + background.image +
 			        "\" alt=\"Background\" width=\"" +
 			        parent.width.to_string() + "\" height=\"" +
 			        parent.height.to_string() + "\"/>";
 
 			// copy the image file
-			exporter.copy_file(background_image, parent.path);
+			exporter.copy_file(background.image, parent.path);
 		}
 		
 		// add tags for each Element
@@ -548,12 +502,7 @@ public class Ease.Slide : GLib.Object, UndoSource
 		
 		html += "</div>\n";
 	}
-	
-	private void bg_changed(GLib.Object sender)
-	{
-		background_changed(this);
-	}
-	
+
 	// foreach iteration
 	
 	/**
@@ -590,56 +539,3 @@ public class Ease.Slide : GLib.Object, UndoSource
 	}
 }
 
-public enum Ease.BackgroundType
-{
-	COLOR,
-	GRADIENT,
-	IMAGE;
-	
-	public const BackgroundType[] TYPES = { COLOR, GRADIENT, IMAGE};
-	
-	/**
-	 * Returns a string representation of this BackgroundType.
-	 */
-	public string to_string()
-	{
-		switch (this)
-		{
-			case COLOR: return Theme.BACKGROUND_TYPE_COLOR;
-			case GRADIENT: return Theme.BACKGROUND_TYPE_GRADIENT;
-			case IMAGE: return Theme.BACKGROUND_TYPE_IMAGE;
-		}
-		return "undefined";
-	}
-	
-	/**
-	 * Creates a BackgroundType from a string representation.
-	 */
-	public static BackgroundType from_string(string str)
-	{
-		switch (str)
-		{
-			case Theme.BACKGROUND_TYPE_COLOR: return COLOR;
-			case Theme.BACKGROUND_TYPE_GRADIENT: return GRADIENT;
-			case Theme.BACKGROUND_TYPE_IMAGE: return IMAGE;
-		}
-		
-		warning("%s is not a gradient type", str);
-		return COLOR;
-	}
-	
-	/**
-	 * Returns a description of the BackgroundType.
-	 */
-	public string description()
-	{
-		switch (this)
-		{
-			case COLOR: return _("Solid Color");
-			case GRADIENT: return _("Gradient");
-			case IMAGE: return _("Image");
-		}
-		return "undefined";
-	}
-}
-
diff --git a/ease-core/ease-text-element.vala b/ease-core/ease-text-element.vala
index 2306e92..bcd24e6 100644
--- a/ease-core/ease-text-element.vala
+++ b/ease-core/ease-text-element.vala
@@ -22,12 +22,14 @@ public class Ease.TextElement : Element
 {
 	private const string UI_FILE_PATH = "inspector-element-text.ui";
 	private bool freeze = false;
-	private Gtk.Widget inspector_pane;
 	
 	/**
 	 * Creates a new TextElement.
 	 */
-	public TextElement() { }
+	public TextElement()
+	{
+		signals();
+	}
 	
 	/**
 	 * Create a TextElement from a JsonObject
@@ -88,8 +90,6 @@ public class Ease.TextElement : Element
 	
 	public override Gtk.Widget inspector_widget()
 	{
-		if (inspector_pane != null) return inspector_pane;
-		
 		var builder = new Gtk.Builder();
 		try
 		{
@@ -172,7 +172,7 @@ public class Ease.TextElement : Element
 		});
 		
 		// return the root
-		return inspector_pane = builder.get_object("root") as Gtk.Widget;
+		return builder.get_object("root") as Gtk.Widget;
 	}
 	
 	[CCode (instance_pos = -1)]
@@ -196,10 +196,10 @@ public class Ease.TextElement : Element
 		}
 	}
 
-	protected override void write_html(ref string html, HTMLExporter exporter)
+	protected override string html_render(HTMLExporter exporter)
 	{
 		// open the tag
-		html += "<div class=\"text element\" ";
+		string html = "<div class=\"text element\" ";
 		
 		// set the size and position of the element
 		html += "style=\"";
@@ -226,6 +226,8 @@ public class Ease.TextElement : Element
 		// write the actual content
 		html += ">" + text.replace("\n", "<br />") +
 		        "</div>";
+		
+		return html;
 	}
 
 	/**
@@ -242,15 +244,10 @@ public class Ease.TextElement : Element
 		layout.set_alignment(text_align);
 		
 		// render
-		context.save();
-		
 		color.set_cairo(context);
-		
 		Pango.cairo_update_layout(context, layout);
-		context.move_to((int)x, (int)y);
-		
+//		context.move_to((int)x, (int)y);
 		Pango.cairo_show_layout(context, layout);
-		context.restore();
 	}
 	
 	/**
diff --git a/ease-core/ease-theme.vala b/ease-core/ease-theme.vala
index 90be618..47322cc 100644
--- a/ease-core/ease-theme.vala
+++ b/ease-core/ease-theme.vala
@@ -59,6 +59,7 @@ public class Ease.Theme : GLib.Object
 	public const string BACKGROUND_COLOR = "background-color";
 	public const string BACKGROUND_GRADIENT = "background-gradient";
 	public const string BACKGROUND_IMAGE = "background-image";
+	public const string BACKGROUND_IMAGE_SOURCE = "background-image-source";
 	public const string S_IDENTIFIER = "slide-identifier";
 	
 	// background types
@@ -92,6 +93,9 @@ public class Ease.Theme : GLib.Object
 	public const string MEDIA_FILENAME = "media-filename";
 	public const string MEDIA_SOURCE_FILENAME = "media-source-filename";
 	
+	// shape properties
+	public const string SHAPE_TYPE = "shape-type";
+	
 	// gradient types
 	public const string GRAD_LINEAR = "linear";
 	public const string GRAD_LINEAR_MIRRORED = "linear-mirrored";
@@ -325,18 +329,18 @@ public class Ease.Theme : GLib.Object
 		switch (master_get(master, BACKGROUND_TYPE))
 		{
 			case BACKGROUND_TYPE_COLOR:
-				slide.background_color = new Color.
+				slide.background.color = new Color.
 					from_string(master_get(master, BACKGROUND_COLOR));
-				slide.background_type = BackgroundType.COLOR;
+				slide.background.background_type = BackgroundType.COLOR;
 				break;
 			case BACKGROUND_TYPE_GRADIENT:
-				slide.background_gradient = new Gradient.
+				slide.background.gradient = new Gradient.
 					from_string(master_get(master, BACKGROUND_GRADIENT));
-				slide.background_type = BackgroundType.GRADIENT;
+				slide.background.background_type = BackgroundType.GRADIENT;
 				break;
 			case BACKGROUND_TYPE_IMAGE:
-				slide.background_image = master_get(master, BACKGROUND_IMAGE);
-				slide.background_type = BackgroundType.IMAGE;
+				slide.background.image = master_get(master, BACKGROUND_IMAGE);
+				slide.background.background_type = BackgroundType.IMAGE;
 				break;
 				
 		}
diff --git a/ease-core/ease-undo-action.vala b/ease-core/ease-undo-action.vala
index 9b56cf7..689981c 100644
--- a/ease-core/ease-undo-action.vala
+++ b/ease-core/ease-undo-action.vala
@@ -71,14 +71,27 @@ public class Ease.UndoAction : UndoItem
 	}
 	
 	/**
+	 * Returns true if this action contains a property on the specified object.
+	 */
+	public override bool contains(GLib.Object? obj)
+	{
+		if (obj == null) return false;
+		foreach (var pair in pairs)
+		{
+			if (pair.object == obj) return true;
+		}
+		return false;
+	}
+	
+	/**
 	 * Embedded class for storing object/property pairs in undo actions.
 	 */
 	private class UndoPair
 	{
-		private string property;
-		private GLib.Object object;
-		private GLib.Value val;
-		private GLib.Type type;
+		public string property;
+		public GLib.Object object;
+		public GLib.Value val;
+		public GLib.Type type;
 		
 		public UndoPair(GLib.Object obj, string prop)
 		{
diff --git a/ease-core/ease-undo-item.vala b/ease-core/ease-undo-item.vala
index 9d6b49e..fa6b1db 100644
--- a/ease-core/ease-undo-item.vala
+++ b/ease-core/ease-undo-item.vala
@@ -39,4 +39,14 @@ public abstract class Ease.UndoItem : GLib.Object
 	 * Returns an UndoItem that will redo the undo action.
 	 */
 	public abstract UndoItem apply();
+	
+	/**
+	 * If the UndoItem contains the specified object. { link UndoAction}
+	 * overrides this in a useful way. In its base implementation, always
+	 * returns false.
+	 */
+	public virtual bool contains(GLib.Object? obj)
+	{
+		return false;
+	}
 }
diff --git a/ease-core/ease-undo-source.vala b/ease-core/ease-undo-source.vala
index 56dafeb..5ebd4f5 100644
--- a/ease-core/ease-undo-source.vala
+++ b/ease-core/ease-undo-source.vala
@@ -28,12 +28,18 @@ public interface Ease.UndoSource : GLib.Object
 	public signal void undo(UndoItem action);
 	
 	/**
+	 * Emitted when an UndoItem is forwarded.
+	 */
+	protected signal void forwarded(UndoItem action);
+	
+	/**
 	 * Forwards an { link UndoItem} downwards, to any object listening to this
 	 * UndoSource's "undo" signal".
 	 */
 	protected void forward(UndoItem action)
 	{
 		undo(action);
+		forwarded(action);
 	}
 	
 	/**
diff --git a/src/ease-editor-window.vala b/src/ease-editor-window.vala
index 246dbaa..7577ecf 100644
--- a/src/ease-editor-window.vala
+++ b/src/ease-editor-window.vala
@@ -158,7 +158,6 @@ public class Ease.EditorWindow : Gtk.Window
 		// the inspector
 		inspector = new Inspector(document);
 		(builder.get_object("Inspector Align") as Gtk.Alignment).add(inspector);
-		inspector.undo.connect(add_undo_action);
 		embed.element_selected.connect(
 			inspector.element_pane.on_element_selected);
 		embed.element_deselected.connect(
@@ -343,7 +342,6 @@ public class Ease.EditorWindow : Gtk.Window
 		update_undo();
 		embed.slide_actor.relayout();
 		embed.reposition_group();
-		slide.changed(slide);
 	}
 	
 	[CCode (instance_pos = -1)]
@@ -353,7 +351,6 @@ public class Ease.EditorWindow : Gtk.Window
 		update_undo();
 		embed.slide_actor.relayout();
 		embed.reposition_group();
-		slide.changed(slide);
 	}
 	
 	[CCode (instance_pos = -1)]
@@ -413,6 +410,32 @@ public class Ease.EditorWindow : Gtk.Window
 	}
 	
 	[CCode (instance_pos = -1)]
+	public void on_insert_rectangle(Gtk.Widget sender)
+	{
+		var rect = new ShapeElement(ShapeType.RECTANGLE);
+		rect.width = 400;
+		rect.height = 300;
+		rect.x = document.width / 2 - rect.width / 2;
+		rect.y = document.height / 2 - rect.height / 2;
+		slide.add(rect);
+		add_undo_action(new ElementAddUndoAction(rect));
+		embed.select_element(rect);
+	}
+	
+	[CCode (instance_pos = -1)]
+	public void on_insert_oval(Gtk.Widget sender)
+	{
+		var oval = new ShapeElement(ShapeType.OVAL);
+		oval.width = 300;
+		oval.height = 300;
+		oval.x = document.width / 2 - oval.width / 2;
+		oval.y = document.height / 2 - oval.height / 2;
+		slide.add(oval);
+		add_undo_action(new ElementAddUndoAction(oval));
+		embed.select_element(oval);
+	}
+	
+	[CCode (instance_pos = -1)]
 	public void insert_video(Gtk.Widget sender)
 	{
 		
diff --git a/src/ease-inspector-element-pane.vala b/src/ease-inspector-element-pane.vala
index 85a493f..0dd78b5 100644
--- a/src/ease-inspector-element-pane.vala
+++ b/src/ease-inspector-element-pane.vala
@@ -46,7 +46,7 @@ internal class Ease.InspectorElementPane : InspectorPane
 		 	if (current.get_parent() == this) remove(current);
 		}
 		else if (none.get_parent() == this) remove(none);
-		current = selected.inspector_widget();
+		current = selected.get_inspector_widget();
 		pack_start(current, false, false, 0);
 	}
 	
diff --git a/src/ease-inspector-pane.vala b/src/ease-inspector-pane.vala
index 587a880..b23e51a 100644
--- a/src/ease-inspector-pane.vala
+++ b/src/ease-inspector-pane.vala
@@ -18,7 +18,7 @@
 /**
  * Base class for inspector panes
  */
-public abstract class Ease.InspectorPane : Gtk.VBox, UndoSource
+public abstract class Ease.InspectorPane : Gtk.VBox
 {
 	public Slide slide { get; set; }
 	public Document document { get; set; }
diff --git a/src/ease-inspector-slide-pane.vala b/src/ease-inspector-slide-pane.vala
index d73f564..ba515d4 100644
--- a/src/ease-inspector-slide-pane.vala
+++ b/src/ease-inspector-slide-pane.vala
@@ -16,28 +16,14 @@
 */
 
 /**
- * The inspector pane concerning slides
+ * The inspector pane concerning slides.
  */
 public class Ease.InspectorSlidePane : InspectorPane
 {
 	private const string UI_FILE_PATH = "inspector-slide.ui";
-	private const string BG_DIALOG_TITLE = _("Select Background Image");
-	
-	private Gtk.ComboBox background;
-	private Gtk.ListStore store;
-	private Gtk.ComboBox gradient_type;
-	private Gtk.ListStore grad_store = GradientType.list_store();
-	private Gtk.VBox box_color;
-	private Gtk.VBox box_gradient;
-	private Gtk.VBox box_image;
-	private Gtk.HScale grad_angle;
-	
-	private Gtk.ColorButton bg_color;
-	private Gtk.ColorButton grad_color1;
-	private Gtk.ColorButton grad_color2;
-	private Gtk.FileChooserButton bg_image;
-	
-	private bool silence_undo;
+
+	private BackgroundWidget bg_widget;
+	private Gtk.Box box;
 
 	public InspectorSlidePane(Document d)
 	{	
@@ -52,321 +38,22 @@ public class Ease.InspectorSlidePane : InspectorPane
 		}
 		catch (Error e) { error("Error loading UI: %s", e.message); }
 		
+		// get the root box
+		box = builder.get_object("root-box") as Gtk.Box;
+		
 		// connect signals
 		builder.connect_signals(this);
 		
 		// add the root of the builder file to this widget
 		pack_start(builder.get_object("root") as Gtk.Widget, true, true, 0);
-		
-		// get controls
-		box_color = builder.get_object("vbox-color") as Gtk.VBox;
-		box_gradient = builder.get_object("vbox-gradient") as Gtk.VBox;
-		box_image = builder.get_object("vbox-image") as Gtk.VBox;
-		bg_color = builder.get_object("color-color") as Gtk.ColorButton;
-		grad_color1 =
-			builder.get_object("color-startgradient") as Gtk.ColorButton;
-		grad_color2 =
-			builder.get_object("color-endgradient") as Gtk.ColorButton;
-		bg_image =
-			builder.get_object("button-image") as Gtk.FileChooserButton;
-		gradient_type =
-			builder.get_object("combo-gradient") as Gtk.ComboBox;
-		grad_angle = builder.get_object("hscale-angle") as Gtk.HScale;
-		
-		// set up the gradient type combobox
-		gradient_type.model = grad_store;
-		
-		// get the combobox
-		background = builder.get_object("combobox-style") as Gtk.ComboBox;
-		
-		// build a liststore for the combobox
-		store = new Gtk.ListStore(2, typeof(string), typeof(BackgroundType));
-		Gtk.TreeIter iter;
-		foreach (var b in BackgroundType.TYPES)
-		{
-			store.append(out iter);
-			store.set(iter, 0, b.description(), 1, b);
-		}
-		
-		var render = new Gtk.CellRendererText();
-		
-		background.pack_start(render, true);
-		background.set_attributes(render, "text", 0);
-		
-		background.model = store;
-	}
-	
-	private void emit_undo(UndoAction action)
-	{
-		if (!silence_undo) undo(action);
-	}
-	
-	[CCode (instance_pos = -1)]
-	public void on_background_changed(Gtk.Widget? sender)
-	{
-		Gtk.TreeIter itr;
-		store.get_iter_first(out itr);
-		
-		// find the correct position
-		for (int i = 0; i < background.active; i++) store.iter_next(ref itr);
-		
-		// get the background type at that position
-		BackgroundType type;
-		store.get(itr, 1, out type);
-		
-		// create an undo action
-		var action = new UndoAction(slide, "background-type");
-		
-		// ease can't provide a default for images, so one must be requested
-		if (type == BackgroundType.IMAGE && slide.background_image == null)
-		{
-			var dialog = new Gtk.FileChooserDialog(BG_DIALOG_TITLE,
-			                                       widget_window(this),
-			                                       Gtk.FileChooserAction.OPEN,
-			                                       "gtk-cancel",
-			                                       Gtk.ResponseType.CANCEL,
-			                                       "gtk-open",
-			                                       Gtk.ResponseType.ACCEPT);
-			switch (dialog.run())
-			{
-				case Gtk.ResponseType.ACCEPT:
-					try
-					{
-						var fname = dialog.get_filename();
-						slide.background_image_source = fname;
-						var i = document.add_media_file(fname);
-						slide.background_image = i;
-					}
-					catch (GLib.Error e)
-					{
-						critical("Error adding background image: %s",
-						         e.message);
-					}
-					dialog.destroy();
-					break;
-				case Gtk.ResponseType.CANCEL:
-					action.apply();
-					dialog.destroy();
-					return;
-			}
-		}
-		
-		// when the action is applied, if the slide is still current, change ui
-		var local = slide;
-		action.applied.connect((a) => {
-			if (local == slide)
-			{
-				silence_undo = true;
-				background.set_active(slide.background_type);
-				display_bg_ui(slide.background_type);
-				silence_undo = false;
-			}
-		});
-		
-		// add properties to the UndoAction and report it to the controller
-		switch (type)
-		{
-			case BackgroundType.COLOR:
-				action.add(slide, "background-color");
-				break;
-			case BackgroundType.GRADIENT:
-				action.add(slide, "background-gradient");
-				break;
-			case BackgroundType.IMAGE:
-				action.add(slide, "background-image");
-				break;
-		}
-		emit_undo(action);
-		
-		// switch to that background type
-		display_bg_ui(type);
-	}
-	
-	[CCode (instance_pos = -1)]
-	public void on_gradient_type_changed(Gtk.ComboBox? sender)
-	{
-		emit_undo(new UndoAction(slide.background_gradient, "mode"));
-		slide.background_gradient.mode = (GradientType)sender.get_active();
-		slide.changed(slide);
-	}
-	
-	[CCode (instance_pos = -1)]
-	public void on_color_set(Gtk.ColorButton? sender)
-	{
-		if (sender == bg_color)
-		{
-			emit_undo(slide.background_color.undo_action());
-			slide.background_color.gdk = sender.color;
-		}
-		else if (sender == grad_color1)
-		{
-			emit_undo(slide.background_gradient.start.undo_action());
-			slide.background_gradient.start.gdk = sender.color;
-		}
-		else if (sender == grad_color2)
-		{
-			emit_undo(slide.background_gradient.end.undo_action());
-			slide.background_gradient.end.gdk = sender.color;
-		}
-		slide.changed(slide);
-	}
-	
-	[CCode (instance_pos = -1)]
-	public void on_file_set(Gtk.FileChooserButton? sender)
-	{
-		var action = new UndoAction(slide, "background-image");
-		action.add(slide, "background-image-source");
-		
-		// slide might change in the meantime
-		var local = slide;
-		
-		// set the button's filename when the action is applied
-		action.applied.connect((a) => {
-			// if slide changes, this is still ok
-			if (slide.background_image_source != null)
-			{
-				bg_image.set_filename(slide.background_image_source);
-			}
-			else
-			{
-				bg_image.unselect_all();
-			}
-			local.changed(local);
-			display_bg_ui(slide.background_type);
-		});
-		
-		try
-		{
-			slide.background_image_source = sender.get_filename();
-			var i = document.add_media_file(sender.get_filename());
-			slide.background_image = i;
-		}
-		catch (GLib.Error e)
-		{
-			critical("Error adding background image: %s", e.message);
-		}
-		
-		slide.changed(slide);
-		
-		emit_undo(action);
-	}
-	
-	[CCode (instance_pos = -1)]
-	public void on_reverse_gradient(Gtk.Widget? sender)
-	{
-		// create an undo action
-		var action = new UndoAction(slide.background_gradient, "start");
-		action.add(slide.background_gradient, "end");
-		
-		// flip the gradient
-		slide.background_gradient.flip();
-		
-		// update the ui
-		grad_color1.set_color(slide.background_gradient.start.gdk);
-		grad_color2.set_color(slide.background_gradient.end.gdk);
-		slide.changed(slide);
-		
-		// add the undo action
-		emit_undo(action);
-	}
-	
-	[CCode (instance_pos = -1)]
-	public void on_set_angle(Gtk.Widget? sender)
-	{
-		var action = new UndoAction(slide.background_gradient, "angle");
-		slide.background_gradient.angle =
-			(sender as Gtk.HScale).adjustment.value;
-		undo(action);
-		
-		slide.changed(slide);
-		
-		var local = slide;
-		action.applied.connect((a) => {
-			local.changed(local);
-			if (local == slide) display_bg_ui(slide.background_type);
-		});
 	}
 	
 	protected override void slide_updated()
 	{
-		silence_undo = true;
-		
-		// set the combo box to the slide's active background type
-		background.set_active(slide.background_type);
-		
-		// set the gradient box to the correct mode
-		if (slide.background_gradient != null)
-		{
-			gradient_type.set_active(slide.background_gradient.mode);
-		}
-		
-		display_bg_ui(slide.background_type);
-		
-		silence_undo = false;
-	}
-	
-	private void display_bg_ui(BackgroundType type)
-	{
-		switch (type)
-		{
-			case BackgroundType.COLOR:
-				box_color.show_all();
-				box_gradient.hide_all();
-				box_image.hide_all();
-				
-				if (slide.background_color == null)
-				{
-					slide.background_color = Color.white;
-				}
-				slide.background_type = BackgroundType.COLOR;
-				
-				bg_color.set_color(slide.background_color.gdk);
-				
-				slide.changed(slide);
-				
-				break;
-			
-			case BackgroundType.GRADIENT:
-				box_color.hide_all();
-				box_gradient.show_all();
-				box_image.hide_all();
-				
-				if (slide.background_gradient == null)
-				{
-					slide.background_gradient = new Gradient(Color.black,
-					                                         Color.white);
-					gradient_type.set_active(slide.background_gradient.mode);
-				}
-				slide.background_type = BackgroundType.GRADIENT;
-				
-				grad_color1.set_color(slide.background_gradient.start.gdk);
-				grad_color2.set_color(slide.background_gradient.end.gdk);
-				
-				grad_angle.adjustment.value = slide.background_gradient.angle;
-				
-				slide.changed(slide);
-				
-				break;
-			
-			case BackgroundType.IMAGE:
-				box_color.hide_all();
-				box_gradient.hide_all();
-				box_image.show_all();
-				
-				slide.background_type = BackgroundType.IMAGE;
-				if (slide.background_image_source != null)
-				{
-					bg_image.set_filename(slide.background_image_source);
-				}
-				else
-				{
-					bg_image.unselect_all();
-				}
-				
-				slide.changed(slide);
-				
-				break;
-		}
+		if (bg_widget != null) box.remove(bg_widget);
+		bg_widget = new BackgroundWidget.for_slide(slide);
+		box.pack_start(bg_widget, true, true, 0);
+		bg_widget.show();
 	}
 }
 
diff --git a/src/ease-inspector-transition-pane.vala b/src/ease-inspector-transition-pane.vala
index fdb9791..0e01cd4 100644
--- a/src/ease-inspector-transition-pane.vala
+++ b/src/ease-inspector-transition-pane.vala
@@ -141,7 +141,7 @@ public class Ease.InspectorTransitionPane : InspectorPane
 			// allow the user to undo the change
 			var action = new UndoAction(slide, "transition");
 			action.add(slide, "variant");
-			if (!silence_undo) undo(action);
+			if (!silence_undo) slide.undo(action);
 			
 			var already_silenced = silence_undo;
 			silence_undo = true;
@@ -188,7 +188,7 @@ public class Ease.InspectorTransitionPane : InspectorPane
 		
 		// allow the user to change the variant
 		variant.changed.connect((sender) => {
-			if (!silence_undo) undo(new UndoAction(slide, "variant"));
+			if (!silence_undo) slide.undo(new UndoAction(slide, "variant"));
 			
 			Gtk.TreeIter itr;
 			if (sender.get_active_iter(out itr))
@@ -204,7 +204,7 @@ public class Ease.InspectorTransitionPane : InspectorPane
 		});
 		
 		start_transition.changed.connect(() => {
-			if (!silence_undo) undo(new UndoAction(slide,
+			if (!silence_undo) slide.undo(new UndoAction(slide,
 			                                       "automatically-advance"));
 			if (start_transition.active == 0)
 			{
@@ -219,12 +219,14 @@ public class Ease.InspectorTransitionPane : InspectorPane
 		});
 		
 		transition_time.value_changed.connect(() => {
-			if (!silence_undo) undo(new UndoAction(slide, "transition-time"));
+			if (!silence_undo)
+				slide.undo(new UndoAction(slide, "transition-time"));
 			slide.transition_time = transition_time.get_value();
 		});
 		
 		delay.value_changed.connect(() => {
-			if (!silence_undo) undo(new UndoAction(slide, "advance-delay"));
+			if (!silence_undo)
+				slide.undo(new UndoAction(slide, "advance-delay"));
 			slide.advance_delay = delay.get_value();
 		});
 		
diff --git a/src/ease-inspector.vala b/src/ease-inspector.vala
index 13dcbc4..53fd475 100644
--- a/src/ease-inspector.vala
+++ b/src/ease-inspector.vala
@@ -18,7 +18,7 @@
 /**
  * Inspector widget for editing slide properties
  */
-public class Ease.Inspector : Gtk.Notebook, UndoSource
+public class Ease.Inspector : Gtk.Notebook
 {
 	private InspectorTransitionPane transition_pane;
 	private InspectorSlidePane slide_pane;
@@ -64,7 +64,6 @@ public class Ease.Inspector : Gtk.Notebook, UndoSource
 	{
 		append_page(i, new Gtk.Image.from_stock(stock_id,
 		                                        Gtk.IconSize.SMALL_TOOLBAR));
-		listen(i);
 	}
 }
 
diff --git a/src/ease-slide-button-panel.vala b/src/ease-slide-button-panel.vala
index e5ee8f1..380ea15 100644
--- a/src/ease-slide-button-panel.vala
+++ b/src/ease-slide-button-panel.vala
@@ -95,6 +95,7 @@ public class Ease.SlideButtonPanel : Gtk.ScrolledWindow
 			slide_redraw(itr);
 			document.slides.get(itr, Document.COL_SLIDE, out s);
 			s.changed.connect(on_slide_changed);
+			s.background_changed.connect(on_slide_changed);
 		}
 		
 		// switch slides when the selection changes
@@ -109,10 +110,12 @@ public class Ease.SlideButtonPanel : Gtk.ScrolledWindow
 		document.slide_added.connect((slide, index) => {
 			on_slide_changed(slide);
 			slide.changed.connect(on_slide_changed);
+			slide.background_changed.connect(on_slide_changed);
 		});
 		
 		document.slide_deleted.connect((slide, index) => {
 			slide.changed.disconnect(on_slide_changed);
+			slide.background_changed.disconnect(on_slide_changed);
 		});
 		
 		// redraw all slides when the size allocation changes



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]