[california] Create "Toolkit" namespace for widgetry



commit 8e8a7e8391151dda115850dd85736e65d2247cf3
Author: Jim Nelson <jim yorba org>
Date:   Thu Apr 3 18:27:30 2014 -0700

    Create "Toolkit" namespace for widgetry
    
    This also abolishes the old clunky Host.Interaction and makes all
    UI panes Toolkit.Cards hosted in Toolkit.Decks.  This is a more
    flexible system and allows for these cards to be strung together
    in single-window dialogs as well as GtkPopovers.
    
    Finally, the old color chooser popup was removed in favor of using
    GtkColorButton in the Calendar Manager.

 src/Makefile.am                                    |   17 +-
 src/activator/activator-instance-list.vala         |   10 +-
 src/activator/activator-instance.vala              |    2 +-
 src/activator/activator-window.vala                |    8 +-
 src/activator/activator.vala                       |    2 +
 .../activator-google-authenticating-pane.vala      |    4 +-
 .../activator-google-calendar-list-pane.vala       |   12 +-
 .../google/activator-google-login-pane.vala        |    4 +-
 src/activator/google/activator-google.vala         |    4 +-
 src/activator/webcal/activator-webcal-pane.vala    |    4 +-
 src/activator/webcal/activator-webcal.vala         |    4 +-
 src/collection/collection-iterable.vala            |  203 ++++++++++++++++++++
 src/host/host-color-chooser-popup.vala             |   36 ----
 src/host/host-create-update-event.vala             |   15 ++-
 src/host/host-interaction.vala                     |   52 -----
 src/host/host-main-window.vala                     |   19 +-
 src/host/host-modal-window.vala                    |   79 --------
 src/host/host-show-event.vala                      |   13 ++-
 src/host/host.vala                                 |    2 +
 src/manager/manager-calendar-list-item.vala        |   22 +--
 src/manager/manager-calendar-list.vala             |   13 ++-
 src/manager/manager-window.vala                    |    4 +-
 src/manager/manager.vala                           |    2 +
 src/rc/calendar-manager-list-item.ui               |    8 +-
 .../toolkit-calendar-popup.vala}                   |    2 +-
 src/toolkit/toolkit-card.vala                      |  119 ++++++++++++
 src/toolkit/toolkit-deck-window.vala               |   50 +++++
 .../util-deck.vala => toolkit/toolkit-deck.vala}   |  131 ++-----------
 .../toolkit-listbox-model.vala}                    |   19 +--
 .../host-popup.vala => toolkit/toolkit-popup.vala} |    2 +-
 src/toolkit/toolkit.vala                           |   29 +++
 src/util/util-interfaces.vala                      |   24 +++
 32 files changed, 541 insertions(+), 374 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 071c509..074b53d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -65,6 +65,7 @@ california_VALASOURCES = \
        calendar/calendar-week-span.vala \
        calendar/calendar-year.vala \
        \
+       collection/collection-iterable.vala \
        collection/collection-simple-iterator.vala \
        collection/collection-simple-iterable.vala \
        \
@@ -77,13 +78,8 @@ california_VALASOURCES = \
        component/component-vtype.vala \
        \
        host/host.vala \
-       host/host-calendar-popup.vala \
-       host/host-color-chooser-popup.vala \
        host/host-create-update-event.vala \
-       host/host-interaction.vala \
        host/host-main-window.vala \
-       host/host-modal-window.vala \
-       host/host-popup.vala \
        host/host-show-event.vala \
        \
        manager/manager.vala \
@@ -91,9 +87,16 @@ california_VALASOURCES = \
        manager/manager-calendar-list-item.vala \
        manager/manager-window.vala \
        \
-       util/util-deck.vala \
+       toolkit/toolkit.vala \
+       toolkit/toolkit-calendar-popup.vala \
+       toolkit/toolkit-card.vala \
+       toolkit/toolkit-deck.vala \
+       toolkit/toolkit-listbox-model.vala \
+       toolkit/toolkit-deck-window.vala \
+       toolkit/toolkit-popup.vala \
+       \
        util/util-gfx.vala \
-       util/util-listbox-model.vala \
+       util/util-interfaces.vala \
        util/util-memory.vala \
        util/util-string.vala \
        util/util-uri.vala \
diff --git a/src/activator/activator-instance-list.vala b/src/activator/activator-instance-list.vala
index f7c3bcb..e8583e1 100644
--- a/src/activator/activator-instance-list.vala
+++ b/src/activator/activator-instance-list.vala
@@ -7,7 +7,7 @@
 namespace California.Activator {
 
 [GtkTemplate (ui = "/org/yorba/california/rc/activator-list.ui")]
-public class InstanceList : Gtk.Grid, Card {
+public class InstanceList : Gtk.Grid, Toolkit.Card {
     public const string ID = "ActivatorInstanceList";
     
     public string card_id { get { return ID; } }
@@ -24,14 +24,14 @@ public class InstanceList : Gtk.Grid, Card {
     [GtkChild]
     private Gtk.Button add_button;
     
-    private ListBoxModel<Instance> model;
+    private Toolkit.ListBoxModel<Instance> model;
     
     public InstanceList() {
-        model = new ListBoxModel<Instance>(listbox, model_presentation, activator_comparator);
+        model = new Toolkit.ListBoxModel<Instance>(listbox, model_presentation, activator_comparator);
         model.add_many(activators);
         
         model.activated.connect(on_item_activated);
-        model.bind_property(ListBoxModel.PROP_SELECTED, add_button, "sensitive", BindingFlags.SYNC_CREATE,
+        model.bind_property(Toolkit.ListBoxModel.PROP_SELECTED, add_button, "sensitive", 
BindingFlags.SYNC_CREATE,
             selected_to_sensitive);
         
         show_all();
@@ -43,7 +43,7 @@ public class InstanceList : Gtk.Grid, Card {
         return true;
     }
     
-    public void jumped_to(Card? from, Value? message) {
+    public void jumped_to(Toolkit.Card? from, Value? message) {
     }
     
     private void on_item_activated(Instance activator) {
diff --git a/src/activator/activator-instance.vala b/src/activator/activator-instance.vala
index ae9393b..5926d25 100644
--- a/src/activator/activator-instance.vala
+++ b/src/activator/activator-instance.vala
@@ -49,7 +49,7 @@ public abstract class Instance : BaseObject {
      *
      * The first Card will be jumped to initially.
      */
-    public abstract Gee.List<Card> create_cards(Soup.URI? supplied_uri);
+    public abstract Gee.List<Toolkit.Card> create_cards(Soup.URI? supplied_uri);
     
     public override string to_string() {
         return title;
diff --git a/src/activator/activator-window.vala b/src/activator/activator-window.vala
index ed14006..7df1c2b 100644
--- a/src/activator/activator-window.vala
+++ b/src/activator/activator-window.vala
@@ -10,24 +10,20 @@ namespace California.Activator {
  * A modal window for selecting and managing { link Activator.Instance} workflows.
  */
 
-public class Window : Host.ModalWindow {
+public class Window : Toolkit.DeckWindow {
     private static Activator.Window? instance = null;
     
-    private Deck deck = new Deck();
-    
     private Window(Gtk.Window? parent) {
         base (parent);
         
         // The Deck is pre-populated with each of their Cards, with the InstanceList jumping to
         // the right set when asked to (and acting as home)
-        Gee.List<Card> cards = new Gee.ArrayList<Card>();
+        Gee.List<Toolkit.Card> cards = new Gee.ArrayList<Toolkit.Card>();
         cards.add(new InstanceList());
         foreach (Instance activator in activators)
             cards.add_all(activator.create_cards(null));
         
         deck.add_cards(cards);
-        
-        content_area.add(deck);
     }
     
     public static void display(Gtk.Window? parent) {
diff --git a/src/activator/activator.vala b/src/activator/activator.vala
index 27bddf7..e32d619 100644
--- a/src/activator/activator.vala
+++ b/src/activator/activator.vala
@@ -25,6 +25,7 @@ public void init() throws Error {
         return;
     
     Backing.init();
+    Toolkit.init();
     
     activators = new Gee.ArrayList<Instance>();
     
@@ -43,6 +44,7 @@ public void terminate() {
     activators = null;
     
     Backing.terminate();
+    Toolkit.terminate();
 }
 
 }
diff --git a/src/activator/google/activator-google-authenticating-pane.vala 
b/src/activator/google/activator-google-authenticating-pane.vala
index ca9800f..5914081 100644
--- a/src/activator/google/activator-google-authenticating-pane.vala
+++ b/src/activator/google/activator-google-authenticating-pane.vala
@@ -7,7 +7,7 @@
 namespace California.Activator {
 
 [GtkTemplate (ui = "/org/yorba/california/rc/google-authenticating.ui")]
-public class GoogleAuthenticatingPane : Gtk.Grid, Card {
+public class GoogleAuthenticatingPane : Gtk.Grid, Toolkit.Card {
     public const string ID = "GoogleAuthenticatingPane";
     
     private const int SUCCESS_DELAY_MSEC = 1500;
@@ -55,7 +55,7 @@ public class GoogleAuthenticatingPane : Gtk.Grid, Card {
             app_id = "yorba-california-%s".printf(Application.VERSION);
     }
     
-    public void jumped_to(Card? from, Value? message) {
+    public void jumped_to(Toolkit.Card? from, Value? message) {
         Message? credentials = message as Message;
         assert(credentials != null);
         
diff --git a/src/activator/google/activator-google-calendar-list-pane.vala 
b/src/activator/google/activator-google-calendar-list-pane.vala
index f50afa7..31643e4 100644
--- a/src/activator/google/activator-google-calendar-list-pane.vala
+++ b/src/activator/google/activator-google-calendar-list-pane.vala
@@ -7,7 +7,7 @@
 namespace California.Activator {
 
 [GtkTemplate (ui = "/org/yorba/california/rc/google-calendar-list.ui")]
-public class GoogleCalendarListPane : Gtk.Grid, Card {
+public class GoogleCalendarListPane : Gtk.Grid, Toolkit.Card {
     public const string ID = "GoogleCalendarListPane";
     
     public class Message : BaseObject {
@@ -45,8 +45,8 @@ public class GoogleCalendarListPane : Gtk.Grid, Card {
     
     private Backing.CalDAVSubscribable store;
     private string? username = null;
-    private ListBoxModel<GData.CalendarCalendar> own_calendars_model;
-    private ListBoxModel<GData.CalendarCalendar> unowned_calendars_model;
+    private Toolkit.ListBoxModel<GData.CalendarCalendar> own_calendars_model;
+    private Toolkit.ListBoxModel<GData.CalendarCalendar> unowned_calendars_model;
     
     public GoogleCalendarListPane(Backing.CalDAVSubscribable store) {
         this.store = store;
@@ -54,9 +54,9 @@ public class GoogleCalendarListPane : Gtk.Grid, Card {
         own_calendars_listbox.set_placeholder(create_placeholder());
         unowned_calendars_listbox.set_placeholder(create_placeholder());
         
-        own_calendars_model = new ListBoxModel<GData.CalendarCalendar>(own_calendars_listbox,
+        own_calendars_model = new Toolkit.ListBoxModel<GData.CalendarCalendar>(own_calendars_listbox,
             entry_to_widget, entry_comparator);
-        unowned_calendars_model = new ListBoxModel<GData.CalendarCalendar>(unowned_calendars_listbox,
+        unowned_calendars_model = new Toolkit.ListBoxModel<GData.CalendarCalendar>(unowned_calendars_listbox,
             entry_to_widget, entry_comparator);
     }
     
@@ -67,7 +67,7 @@ public class GoogleCalendarListPane : Gtk.Grid, Card {
         return label;
     }
     
-    public void jumped_to(Card? from, Value? message) {
+    public void jumped_to(Toolkit.Card? from, Value? message) {
         Message? feeds = message as Message;
         assert(feeds != null);
         
diff --git a/src/activator/google/activator-google-login-pane.vala 
b/src/activator/google/activator-google-login-pane.vala
index 438af3e..856db52 100644
--- a/src/activator/google/activator-google-login-pane.vala
+++ b/src/activator/google/activator-google-login-pane.vala
@@ -7,7 +7,7 @@
 namespace California.Activator {
 
 [GtkTemplate (ui = "/org/yorba/california/rc/google-login.ui")]
-internal class GoogleLoginPane : Gtk.Grid, Card {
+internal class GoogleLoginPane : Gtk.Grid, Toolkit.Card {
     public const string ID = "GoogleLoginPane";
     
     public string card_id { get { return ID; } }
@@ -34,7 +34,7 @@ internal class GoogleLoginPane : Gtk.Grid, Card {
             BindingFlags.SYNC_CREATE, on_entry_changed);
     }
     
-    public void jumped_to(Card? from, Value? msg) {
+    public void jumped_to(Toolkit.Card? from, Value? msg) {
         password_entry.text = "";
     }
     
diff --git a/src/activator/google/activator-google.vala b/src/activator/google/activator-google.vala
index 973c3aa..e667e49 100644
--- a/src/activator/google/activator-google.vala
+++ b/src/activator/google/activator-google.vala
@@ -17,8 +17,8 @@ internal class GoogleActivator : Instance {
         caldav_store = store;
     }
     
-    public override Gee.List<Card> create_cards(Soup.URI? supplied_uri) {
-        Gee.List<Card> cards = new Gee.ArrayList<Card>();
+    public override Gee.List<Toolkit.Card> create_cards(Soup.URI? supplied_uri) {
+        Gee.List<Toolkit.Card> cards = new Gee.ArrayList<Toolkit.Card>();
         cards.add(new GoogleLoginPane());
         cards.add(new GoogleAuthenticatingPane());
         cards.add(new GoogleCalendarListPane(caldav_store));
diff --git a/src/activator/webcal/activator-webcal-pane.vala b/src/activator/webcal/activator-webcal-pane.vala
index b1cb10e..20d4d83 100644
--- a/src/activator/webcal/activator-webcal-pane.vala
+++ b/src/activator/webcal/activator-webcal-pane.vala
@@ -7,7 +7,7 @@
 namespace California.Activator {
 
 [GtkTemplate (ui = "/org/yorba/california/rc/webcal-subscribe.ui")]
-internal class WebCalActivatorPane : Gtk.Grid, Card {
+internal class WebCalActivatorPane : Gtk.Grid, Toolkit.Card {
     public const string ID = "WebCalActivatorPane";
     
     public string card_id { get { return ID; } }
@@ -46,7 +46,7 @@ internal class WebCalActivatorPane : Gtk.Grid, Card {
             BindingFlags.SYNC_CREATE, on_entry_changed);
     }
     
-    public void jumped_to(Card? from, Value? message) {
+    public void jumped_to(Toolkit.Card? from, Value? message) {
     }
     
     private bool on_entry_changed(Binding binding, Value source_value, ref Value target_value) {
diff --git a/src/activator/webcal/activator-webcal.vala b/src/activator/webcal/activator-webcal.vala
index 9d91dda..a74b301 100644
--- a/src/activator/webcal/activator-webcal.vala
+++ b/src/activator/webcal/activator-webcal.vala
@@ -17,8 +17,8 @@ internal class WebCalActivator : Instance {
         webcal_store = store;
     }
     
-    public override Gee.List<Card> create_cards(Soup.URI? supplied_uri) {
-        Gee.List<Card> cards = new Gee.ArrayList<Card>();
+    public override Gee.List<Toolkit.Card> create_cards(Soup.URI? supplied_uri) {
+        Gee.List<Toolkit.Card> cards = new Gee.ArrayList<Toolkit.Card>();
         cards.add(new WebCalActivatorPane(webcal_store, supplied_uri));
         
         return cards;
diff --git a/src/collection/collection-iterable.vala b/src/collection/collection-iterable.vala
new file mode 100644
index 0000000..1bd9cbf
--- /dev/null
+++ b/src/collection/collection-iterable.vala
@@ -0,0 +1,203 @@
+/* Copyright 2013-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California {
+
+/**
+ * Take a Gee object and return a California.Iterable for convenience.
+ */
+public California.Iterable<G> traverse<G>(Gee.Iterable<G> i) {
+    return new California.Iterable<G>(i.iterator());
+}
+
+/**
+ * Take some non-null items (all must be of type G) and return a
+ * California.Iterable for convenience.
+ */
+public California.Iterable<G> iterate<G>(G g, ...) {
+    va_list args = va_list();
+    G arg = g;
+    
+    Gee.ArrayList<G> list = new Gee.ArrayList<G>();
+    do {
+        list.add(arg);
+    } while((arg = args.arg()) != null);
+    
+    return California.traverse<G>(list);
+}
+
+/**
+ * An Iterable that simply wraps an existing Iterator.  You get one iteration,
+ * and only one iteration.  Basically every method triggers one iteration and
+ * returns a new object.
+ *
+ * Note that this can't inherit from Gee.Iterable because its interface
+ * requires that map/filter/etc. return Iterators, not Iterables.  It still
+ * works in foreach.
+ */
+
+public class Iterable<G> : BaseObject {
+    /**
+     * A private class that lets us take a California.Iterable and convert it back
+     * into a Gee.Iterable.
+     */
+    private class GeeIterable<G> : Gee.Traversable<G>, Gee.Iterable<G>, BaseObject {
+        private Gee.Iterator<G> i;
+        
+        public GeeIterable(Gee.Iterator<G> iterator) {
+            i = iterator;
+        }
+        
+        public Gee.Iterator<G> iterator() {
+            return i;
+        }
+        
+        // Unfortunately necessary for Gee.Traversable.
+        public virtual bool @foreach(Gee.ForallFunc<G> f) {
+            foreach (G g in this) {
+                if (!f(g))
+                    return false;
+            }
+            return true;
+        }
+        
+        public override string to_string() {
+            return "GeeIterable";
+        }
+    }
+    
+    private Gee.Iterator<G> i;
+    
+    public Iterable(Gee.Iterator<G> iterator) {
+        i = iterator;
+    }
+    
+    public virtual Gee.Iterator<G> iterator() {
+        return i;
+    }
+    
+    public Iterable<A> map<A>(Gee.MapFunc<A, G> f) {
+        return new Iterable<A>(i.map<A>(f));
+    }
+    
+    public Iterable<A> scan<A>(Gee.FoldFunc<A, G> f, owned A seed) {
+        return new Iterable<A>(i.scan<A>(f, seed));
+    }
+    
+    public Iterable<G> filter(owned Gee.Predicate<G> f) {
+        return new Iterable<G>(i.filter((owned) f));
+    }
+    
+    public Iterable<G> chop(int offset, int length = -1) {
+        return new Iterable<G>(i.chop(offset, length));
+    }
+    
+    public Iterable<A> map_nonnull<A>(Gee.MapFunc<A, G> f) {
+        return new Iterable<A>(i.map<A>(f).filter(g => g != null));
+    }
+    
+    /**
+     * Return only objects of the destination type, as the destination type.
+     * Only works on types derived from Object.
+     */
+    public Iterable<A> cast_object<A>() {
+        return new Iterable<G>(
+            // This would be a lot simpler if valac didn't barf on the shorter,
+            // more obvious syntax for each of these delegates here.
+            i.filter(g => ((Object) g).get_type().is_a(typeof(A)))
+            .map<A>(g => { return (A) g; }));
+    }
+    
+    public G? first() {
+        return (i.next() ? i  get() : null);
+    }
+    
+    public G? first_matching(owned Gee.Predicate<G> f) {
+        foreach (G g in this) {
+            if (f(g))
+                return g;
+        }
+        return null;
+    }
+    
+    public bool any(owned Gee.Predicate<G> f) {
+        foreach (G g in this) {
+            if (f(g))
+                return true;
+        }
+        return false;
+    }
+    
+    public bool all(owned Gee.Predicate<G> f) {
+        foreach (G g in this) {
+            if (!f(g))
+                return false;
+        }
+        return true;
+    }
+    
+    public int count_matching(owned Gee.Predicate<G> f) {
+        int count = 0;
+        foreach (G g in this) {
+            if (f(g))
+                count++;
+        }
+        return count;
+    }
+    
+    /**
+     * The resulting Gee.Iterable comes with the same caveat that you may only
+     * iterate over it once.
+     */
+    public Gee.Iterable<G> to_gee_iterable() {
+        return new GeeIterable<G>(i);
+    }
+    
+    public Gee.Collection<G> add_all_to(Gee.Collection<G> c) {
+        while (i.next())
+            c.add(i  get());
+        return c;
+    }
+    
+    public Gee.ArrayList<G> to_array_list(owned Gee.EqualDataFunc<G>? equal_func = null) {
+        return (Gee.ArrayList<G>) add_all_to(new Gee.ArrayList<G>((owned) equal_func));
+    }
+    
+    public Gee.LinkedList<G> to_linked_list(owned Gee.EqualDataFunc<G>? equal_func = null) {
+        return (Gee.LinkedList<G>) add_all_to(new Gee.LinkedList<G>((owned) equal_func));
+    }
+    
+    public Gee.HashSet<G> to_hash_set(owned Gee.HashDataFunc<G>? hash_func = null,
+        owned Gee.EqualDataFunc<G>? equal_func = null) {
+        return (Gee.HashSet<G>) add_all_to(new Gee.HashSet<G>((owned) hash_func, (owned) equal_func));
+    }
+    
+    public Gee.TreeSet<G> to_tree_set(owned CompareDataFunc<G>? compare_func = null) {
+        return (Gee.TreeSet<G>) add_all_to(new Gee.TreeSet<G>((owned) compare_func));
+    }
+    
+    public Gee.Map<K, G> add_all_to_map<K>(Gee.Map<K, G> c, Gee.MapFunc<K, G> key_func) {
+        while (i.next()) {
+            G g = i  get();
+            c  set(key_func(g), g);
+        }
+        return c;
+    }
+    
+    public Gee.HashMap<K, G> to_hash_map<K>(Gee.MapFunc<K, G> key_func,
+        owned Gee.HashDataFunc<K>? key_hash_func = null,
+        owned Gee.EqualDataFunc<K>? key_equal_func = null,
+        owned Gee.EqualDataFunc<G>? value_equal_func = null) {
+        return (Gee.HashMap<K, G>) add_all_to_map<K>(new Gee.HashMap<K, G>(
+            (owned) key_hash_func, (owned) key_equal_func, (owned) value_equal_func), key_func);
+    }
+    
+    public override string to_string() {
+        return "Iterable";
+    }
+}
+
+}
diff --git a/src/host/host-create-update-event.vala b/src/host/host-create-update-event.vala
index 17ed8c4..7e8530a 100644
--- a/src/host/host-create-update-event.vala
+++ b/src/host/host-create-update-event.vala
@@ -11,15 +11,23 @@ namespace California.Host {
  */
 
 [GtkTemplate (ui = "/org/yorba/california/rc/create-update-event.ui")]
-public class CreateUpdateEvent : Gtk.Grid, Interaction {
+public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
+    public const string ID = "CreateUpdateEvent";
+    
     public const string PROP_SELECTED_DATE_SPAN = "selected-date-span";
     
     private const int START_HOUR = 0;
     private const int END_HOUR = 23;
     private const int MIN_DIVISIONS = 15;
     
+    public string card_id { get { return ID; } }
+    
+    public string? title { get { return null; } }
+    
     public Gtk.Widget? default_widget { get { return accept_button; } }
     
+    public Gtk.Widget? initial_focus { get { return summary_entry; } }
+    
     [GtkChild]
     private Gtk.Entry summary_entry;
     
@@ -198,6 +206,9 @@ public class CreateUpdateEvent : Gtk.Grid, Interaction {
             BindingFlags.SYNC_CREATE);
     }
     
+    public void jumped_to(Toolkit.Card? from, Value? message) {
+    }
+    
     [GtkCallback]
     private void on_date_button_clicked(Gtk.Button button) {
         bool is_dtstart = (button == dtstart_date_button);
@@ -208,7 +219,7 @@ public class CreateUpdateEvent : Gtk.Grid, Interaction {
             both_date_buttons_touched
             || (last_date_button_touched != null && last_date_button_touched != button);
         
-        CalendarPopup popup = new CalendarPopup(button,
+        Toolkit.CalendarPopup popup = new Toolkit.CalendarPopup(button,
             is_dtstart ? selected_date_span.start_date : selected_date_span.end_date);
         
         popup.date_selected.connect((date) => {
diff --git a/src/host/host-main-window.vala b/src/host/host-main-window.vala
index 96b072d..1706122 100644
--- a/src/host/host-main-window.vala
+++ b/src/host/host-main-window.vala
@@ -124,18 +124,17 @@ public class MainWindow : Gtk.ApplicationWindow {
         add(layout);
     }
     
-    private void show_interaction(Gtk.Widget relative_to, Gdk.Point? for_location,
-        Interaction child) {
-        ModalWindow modal_window = new ModalWindow(this);
-        modal_window.content_area.add(child);
+    private void show_deck(Gtk.Widget relative_to, Gdk.Point? for_location, Gee.List<Toolkit.Card> cards) {
+        Toolkit.DeckWindow deck_window = new Toolkit.DeckWindow(this);
+        deck_window.deck.add_cards(cards);
         
         // when the dialog closes, reset View.Controllable state (selection is maintained while
         // use is viewing/editing Interaction) and destroy widgets
-        child.dismissed.connect(() => current_view.unselect_all());
+        deck_window.deck.dismissed.connect(() => current_view.unselect_all());
         
-        modal_window.show_all();
-        modal_window.run();
-        modal_window.destroy();
+        deck_window.show_all();
+        deck_window.run();
+        deck_window.destroy();
     }
     
     private void on_new_event() {
@@ -189,7 +188,7 @@ public class MainWindow : Gtk.ApplicationWindow {
             update_event_async.begin(event, null);
         });
         
-        show_interaction(relative_to, for_location, create_update_event);
+        show_deck(relative_to, for_location, iterate<Toolkit.Card>(create_update_event).to_array_list());
     }
     
     private async void create_event_async(Component.Event event, Cancellable? cancellable) {
@@ -226,7 +225,7 @@ public class MainWindow : Gtk.ApplicationWindow {
             create_event(null, null, event, relative_to, for_location);
         });
         
-        show_interaction(relative_to, for_location, show_event);
+        show_deck(relative_to, for_location, iterate<Toolkit.Card>(show_event).to_array_list());
     }
     
     private async void remove_event_async(Component.Event event, Cancellable? cancellable) {
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index af612c1..169d8c7 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -7,9 +7,17 @@
 namespace California.Host {
 
 [GtkTemplate (ui = "/org/yorba/california/rc/show-event.ui")]
-public class ShowEvent : Gtk.Grid, Interaction {
+public class ShowEvent : Gtk.Grid, Toolkit.Card {
+    public const string ID = "ShowEvent";
+    
+    public string card_id { get { return ID; } }
+    
+    public string? title { get { return null; } }
+    
     public Gtk.Widget? default_widget { get { return close_button; } }
     
+    public Gtk.Widget? initial_focus { get { return close_button; } }
+    
     [GtkChild]
     private Gtk.Label text_label;
     
@@ -41,6 +49,9 @@ public class ShowEvent : Gtk.Grid, Interaction {
         Calendar.System.instance.today_changed.disconnect(build_display);
     }
     
+    public void jumped_to(Toolkit.Card? from, Value? message) {
+    }
+    
     private void build_display() {
         // Each string should end without whitespace; add_lf_lf will ensure each section is
         // separated as long as there's preceding text
diff --git a/src/host/host.vala b/src/host/host.vala
index 82a8dcb..c11f7ba 100644
--- a/src/host/host.vala
+++ b/src/host/host.vala
@@ -22,6 +22,7 @@ public void init() throws Error {
     View.init();
     Backing.init();
     Calendar.init();
+    Toolkit.init();
 }
 
 public void terminate() {
@@ -31,6 +32,7 @@ public void terminate() {
     View.terminate();
     Backing.terminate();
     Calendar.terminate();
+    Toolkit.terminate();
 }
 
 }
diff --git a/src/manager/manager-calendar-list-item.vala b/src/manager/manager-calendar-list-item.vala
index 11a3bb2..14983d5 100644
--- a/src/manager/manager-calendar-list-item.vala
+++ b/src/manager/manager-calendar-list-item.vala
@@ -23,7 +23,7 @@ public class CalendarListItem : Gtk.Grid {
     private Gtk.Label title_label;
     
     [GtkChild]
-    private Gtk.Button color_button;
+    private Gtk.ColorButton color_button;
     
     public CalendarListItem(Backing.CalendarSource source) {
         this.source = source;
@@ -38,25 +38,7 @@ public class CalendarListItem : Gtk.Grid {
     }
     
     private void on_color_changed() {
-        Gdk.Pixbuf pixbuf = new Gdk.Pixbuf(Gdk.Colorspace.RGB, false, 8, COLOR_DIM, COLOR_DIM);
-        pixbuf.fill(Gfx.rgba_to_pixel(source.color_as_rgba()));
-        
-        color_button.set_image(new Gtk.Image.from_pixbuf(pixbuf));
-    }
-    
-    [GtkCallback]
-    private void on_color_button_clicked() {
-        Host.ColorChooserPopup popup = new Host.ColorChooserPopup(color_button, source.color_as_rgba());
-        
-        popup.selected.connect((rgba) => {
-            source.set_color_to_rgba(rgba);
-        });
-        
-        popup.dismissed.connect(() => {
-            popup.destroy();
-        });
-        
-        popup.show_all();
+        color_button.set_color(source.color_as_rgb());
     }
 }
 
diff --git a/src/manager/manager-calendar-list.vala b/src/manager/manager-calendar-list.vala
index 4f396a9..5d1ce98 100644
--- a/src/manager/manager-calendar-list.vala
+++ b/src/manager/manager-calendar-list.vala
@@ -11,9 +11,17 @@ namespace California.Manager {
  */
 
 [GtkTemplate (ui = "/org/yorba/california/rc/calendar-manager-list.ui")]
-public class CalendarList : Gtk.Grid, Host.Interaction {
+public class CalendarList : Gtk.Grid, Toolkit.Card {
+    public const string ID = "CalendarList";
+    
+    public string card_id { get { return ID; } }
+    
+    public string? title { get { return null; } }
+    
     public Gtk.Widget? default_widget { get { return null; } }
     
+    public Gtk.Widget? initial_focus { get { return calendar_list_box; } }
+    
     [GtkChild]
     private Gtk.ListBox calendar_list_box;
     
@@ -30,6 +38,9 @@ public class CalendarList : Gtk.Grid, Host.Interaction {
         Backing.Manager.instance.notify[Backing.Manager.PROP_IS_OPEN].disconnect(on_manager_opened_closed);
     }
     
+    public void jumped_to(Toolkit.Card? from, Value? message) {
+    }
+    
     private void on_manager_opened_closed() {
         if (Backing.Manager.instance.is_open)
             init();
diff --git a/src/manager/manager-window.vala b/src/manager/manager-window.vala
index a910c6f..57b3855 100644
--- a/src/manager/manager-window.vala
+++ b/src/manager/manager-window.vala
@@ -10,13 +10,13 @@ namespace California.Manager {
  * The Calendar Manager main window.
  */
 
-public class Window : Host.ModalWindow {
+public class Window : Toolkit.DeckWindow {
     private static Manager.Window? instance = null;
     
     private Window(Gtk.Window? parent) {
         base (parent);
         
-        content_area.add(new CalendarList());
+        deck.add_cards(iterate<Toolkit.Card>(new CalendarList()).to_array_list());
     }
     
     public static void display(Gtk.Window? parent) {
diff --git a/src/manager/manager.vala b/src/manager/manager.vala
index daf174d..bdac485 100644
--- a/src/manager/manager.vala
+++ b/src/manager/manager.vala
@@ -18,6 +18,7 @@ public void init() throws Error {
         return;
     
     Backing.init();
+    Toolkit.init();
 }
 
 public void terminate() {
@@ -25,6 +26,7 @@ public void terminate() {
         return;
     
     Backing.terminate();
+    Toolkit.terminate();
 }
 
 }
diff --git a/src/rc/calendar-manager-list-item.ui b/src/rc/calendar-manager-list-item.ui
index 0a8647e..a636784 100644
--- a/src/rc/calendar-manager-list-item.ui
+++ b/src/rc/calendar-manager-list-item.ui
@@ -47,17 +47,13 @@
       </packing>
     </child>
     <child>
-      <object class="GtkButton" id="color_button">
+      <object class="GtkColorButton" id="color_button">
         <property name="visible">True</property>
         <property name="can_focus">True</property>
         <property name="receives_default">True</property>
-        <property name="tooltip_text" translatable="yes">Calendar color</property>
         <property name="halign">center</property>
         <property name="valign">center</property>
-        <signal name="clicked" handler="on_color_button_clicked" object="CaliforniaManagerCalendarListItem" 
swapped="no"/>
-        <child>
-          <placeholder/>
-        </child>
+        <property name="title" translatable="yes">Calendar color</property>
       </object>
       <packing>
         <property name="left_attach">1</property>
diff --git a/src/host/host-calendar-popup.vala b/src/toolkit/toolkit-calendar-popup.vala
similarity index 98%
rename from src/host/host-calendar-popup.vala
rename to src/toolkit/toolkit-calendar-popup.vala
index 4cd47d9..391dab0 100644
--- a/src/host/host-calendar-popup.vala
+++ b/src/toolkit/toolkit-calendar-popup.vala
@@ -4,7 +4,7 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
-namespace California.Host {
+namespace California.Toolkit {
 
 /**
  * A simple { link Popup} window featuring only a GtkCalendar.
diff --git a/src/toolkit/toolkit-card.vala b/src/toolkit/toolkit-card.vala
new file mode 100644
index 0000000..fb20277
--- /dev/null
+++ b/src/toolkit/toolkit-card.vala
@@ -0,0 +1,119 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Toolkit {
+
+/**
+ * A Card is a single pane of widget(s) in a { link Deck}.
+ *
+ * The navigation of Cards is tracked within their Deck, and Cards can request navigation via their
+ * various signals.  They're also notified when nevigation which affects them is made.
+ */
+
+public interface Card : Gtk.Widget {
+    /**
+     * Each { link Card} has its own identifier that should be unique within the { link Deck}.
+     *
+     * In the Gtk.Stack, this is its name.
+     */
+    public abstract string card_id { get; }
+    
+    /**
+     * A user-visible string that may be used elsewhere in the application.
+     *
+     * Gtk.StackSwitcher uses this title.  { link Deck} does not use the title in any way.
+     */
+    public abstract string? title { get; }
+    
+    /** 
+     * The widget the { link Card} wants to be default when navigated to.
+     *
+     * The widget must have can-default set to true.
+     */
+    public abstract Gtk.Widget? default_widget { get; }
+    
+    /**
+     * The widget the { link Card} wants to have initial focus when navigated to.
+     *
+     * Focus is set after { link default_widget} is handled, so if this widget has receives-default
+     * set to true, it will get the default as well.
+     *
+     * The widget must have can-focus set to true.
+     */
+    public abstract Gtk.Widget? initial_focus { get; }
+    
+    /**
+     * Returns the { link Deck} this { link Card} is registered to, if any.
+     */
+    public Deck? deck { get { return parent as Deck; } }
+    
+    /**
+     * Fired when the { link Card} wishes to jump to another Card in the same { link Deck.}
+     *
+     * Each Card can accept a message which parameterizes its activation.  It's up to Cards
+     * navigating to the new one to construct and pass an appropriate message.
+     *
+     * @see jump_to_card_by_name
+     */
+    public signal void jump_to_card(Card next, Value? message);
+    
+    /**
+     * Fired when the { link Card} wishes to jump to another Card by its name.
+     *
+     * @see jump_to_card
+     */
+    public signal void jump_to_card_by_name(string name, Value? message);
+    
+    /**
+     * Fired when the { link Card} wishes to jump to the previous Card in the { link Deck}.
+     *
+     * Note that this Card's position in the navigation stack is lost; there is no "jump forward".
+     */
+    public signal void jump_back();
+    
+    /**
+     * Fired when the { link Card} wishes to jump to the first Card in the { link Deck}.
+     *
+     * This clears the Deck's navigation stack, meaning { link jump_back} will not return to
+     * this Card.
+     */
+    public signal void jump_home();
+    
+    /**
+     * Fired when the { link Deck}'s work is cancelled, closed, or dismissed, whether due to
+     * programmatic reasons or by user request.
+     *
+     * Implementing classes should fire this after firing the { link completed signal} so
+     * subscribers can maintain their cleanup in a single handler.
+     */
+    public signal void dismissed(bool user_request);
+    
+    /**
+     * Fired when the { link Deck}'s work has completed successfully.
+     *
+     * This should only be fired if the Deck requires valid input from the user to perform
+     * some intensive operation.  Merely displaying information and closing the Deck
+     * should simply fire { link dismissed}.
+     *
+     * "completed" implies that dismissed will be called shortly thereafter, meaning all
+     * cleanup can be handled there.
+     */
+    public signal void completed();
+    
+    /**
+     * Called by { link Deck} when the { link Card} has been activated, i.e. put to the "top" of
+     * the Deck.
+     *
+     * message may be null even if the Card expects one; generally this means { link jump_back}
+     * or { link jump_home} was invoked, resulting in this Card being activated.
+     *
+     * This is called before dealing with { link default_widget} and { link initial_focus}, so
+     * changes to those properties in this call, if need be.
+     */
+    public abstract void jumped_to(Card? from, Value? message);
+}
+
+}
diff --git a/src/toolkit/toolkit-deck-window.vala b/src/toolkit/toolkit-deck-window.vala
new file mode 100644
index 0000000..333f709
--- /dev/null
+++ b/src/toolkit/toolkit-deck-window.vala
@@ -0,0 +1,50 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Toolkit {
+
+/**
+ * A GtkDialog with no visible action area.
+ *
+ * This is designed for UI panes that want to control their own interaction with the user (in
+ * particular, button placement) but need all the benefits interaction-wise of GtkDialog.
+ *
+ * It's expected this will go away when we move to GTK+ 3.12 and can use GtkPopovers for these
+ * interactions.
+ */
+
+public class DeckWindow : Gtk.Dialog {
+    public Deck deck { get; private set; default = new Deck(); }
+    
+    private Gtk.ResponseType response_type = Gtk.ResponseType.CLOSE;
+    
+    public DeckWindow(Gtk.Window? parent) {
+        transient_for = parent;
+        modal = true;
+        resizable = false;
+        
+        deck.dismissed.connect(on_deck_dismissed);
+        deck.completed.connect(on_deck_completed);
+        
+        Gtk.Box content_area = (Gtk.Box) get_content_area();
+        content_area.margin = 8;
+        content_area.add(deck);
+        
+        get_action_area().visible = false;
+        get_action_area().no_show_all = true;
+    }
+    
+    private void on_deck_completed() {
+        response_type = Gtk.ResponseType.OK;
+    }
+    
+    private void on_deck_dismissed() {
+        response(response_type);
+    }
+}
+
+}
+
diff --git a/src/util/util-deck.vala b/src/toolkit/toolkit-deck.vala
similarity index 70%
rename from src/util/util-deck.vala
rename to src/toolkit/toolkit-deck.vala
index c564152..20c587d 100644
--- a/src/util/util-deck.vala
+++ b/src/toolkit/toolkit-deck.vala
@@ -4,97 +4,58 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
-namespace California {
+namespace California.Toolkit {
 
 /**
- * A Card is a single pane of widget(s) in a { link Deck}.
+ * A Deck is a collection of { link Card}s maintained within a Gtk.Stack.
  *
- * The navigation of Cards is tracked within their Deck, and Cards can request navigation via their
- * various signals.  They're also notified when nevigation which affects them is made.
+ * Cards control navigation through their various signals, which Deck monitors and acts upon.
+ * It also notifies Cards of nagivation changes which affect them via their abstract methods.
  */
 
-public interface Card : Gtk.Widget {
-    /**
-     * Each { link Card} has its own identifier that should be unique within the { link Deck}.
-     *
-     * In the Gtk.Stack, this is its name.
-     */
-    public abstract string card_id { get; }
-    
-    /**
-     * A user-visible string that may be used elsewhere in the application.
-     *
-     * Gtk.StackSwitcher uses this title.  { link Deck} does not use the title in any way.
-     */
-    public abstract string? title { get; }
-    
-    /** 
-     * The widget the { link Card} wants to be default when navigated to.
-     *
-     * The widget must have can-default set to true.
-     */
-    public abstract Gtk.Widget? default_widget { get; }
-    
+public class Deck : Gtk.Stack {
     /**
-     * The widget the { link Card} wants to have initial focus when navigated to.
-     *
-     * Focus is set after { link default_widget} is handled, so if this widget has receives-default
-     * set to true, it will get the default as well.
-     *
-     * The widget must have can-focus set to true.
+     * @inheritedDoc
      */
-    public abstract Gtk.Widget? initial_focus { get; }
+    public Gtk.Widget? default_widget { get { return null; } }
     
     /**
-     * Returns the { link Deck} this { link Card} is registered to, if any.
+     * The number of { link Card}s registered to the { link Deck}.
      */
-    public Deck? deck { get { return parent as Deck; } }
+    public int size { get { return list.size; } }
     
     /**
-     * Fired when the { link Card} wishes to jump to another Card in the same { link Deck.}
-     *
-     * Each Card can accept a message which parameterizes its activation.  It's up to Cards
-     * navigating to the new one to construct and pass an appropriate message.
-     *
-     * @see jump_to_card_by_name
+     * All registered { link Card}s returned as a read-only List.
      */
-    public signal void jump_to_card(Card next, Value? message);
+    public Gee.List<Card> cards { owned get { return list.read_only_view; } }
     
     /**
-     * Fired when the { link Card} wishes to jump to another Card by its name.
-     *
-     * @see jump_to_card
+     * The home { link Card}.
      */
-    public signal void jump_to_card_by_name(string name, Value? message);
+    public Card? home { owned get { return (list.size > 0) ? list[0] : null; } }
     
     /**
-     * Fired when the { link Card} wishes to jump to the previous Card in the { link Deck}.
-     *
-     * Note that this Card's position in the navigation stack is lost; there is no "jump forward".
+     * The current displayed { link Card}.
      */
-    public signal void jump_back();
+    public Card? top { get; private set; default = null; }
     
-    /**
-     * Fired when the { link Card} wishes to jump to the first Card in the { link Deck}.
-     *
-     * This clears the Deck's navigation stack, meaning { link jump_back} will not return to
-     * this Card.
-     */
-    public signal void jump_home();
+    private Gee.List<Card> list = new Gee.LinkedList<Card>();
+    private Gee.Deque<Card> navigation_stack = new Gee.LinkedList<Card>();
+    private Gee.HashMap<string, Card> names = new Gee.HashMap<string, Card>();
     
     /**
      * Fired when the { link Deck}'s work is cancelled, closed, or dismissed, whether due to
      * programmatic reasons or by user request.
      *
-     * Implementing classes should fire this after firing the { link completed signal} so
-     * subscribers can maintain their cleanup in a single handler.
+     * This will be fired after firing the { link completed signal} so subscribers can maintain
+     * their cleanup in a single handler.
      */
     public signal void dismissed(bool user_request);
     
     /**
      * Fired when the { link Deck}'s work has completed successfully.
      *
-     * This should only be fired if the Deck requires valid input from the user to perform
+     * This will only be fired if the Deck requires valid input from the user to perform
      * some intensive operation.  Merely displaying information and closing the Deck
      * should simply fire { link dismissed}.
      *
@@ -104,56 +65,6 @@ public interface Card : Gtk.Widget {
     public signal void completed();
     
     /**
-     * Called by { link Deck} when the { link Card} has been activated, i.e. put to the "top" of
-     * the Deck.
-     *
-     * message may be null even if the Card expects one; generally this means { link jump_back}
-     * or { link jump_home} was invoked, resulting in this Card being activated.
-     *
-     * This is called before dealing with { link default_widget} and { link initial_focus}, so
-     * changes to those properties in this call, if need be.
-     */
-    public abstract void jumped_to(Card? from, Value? message);
-}
-
-/**
- * A Deck is a collection of { link Card}s maintained within a Gtk.Stack.
- *
- * Cards control navigation through their various signals, which Deck monitors and acts upon.
- * It also notifies Cards of nagivation changes which affect them via their abstract methods.
- */
-
-public class Deck : Gtk.Stack, Host.Interaction {
-    /**
-     * @inheritedDoc
-     */
-    public Gtk.Widget? default_widget { get { return null; } }
-    
-    /**
-     * The number of { link Card}s registered to the { link Deck}.
-     */
-    public int size { get { return list.size; } }
-    
-    /**
-     * All registered { link Card}s returned as a read-only List.
-     */
-    public Gee.List<Card> cards { owned get { return list.read_only_view; } }
-    
-    /**
-     * The home { link Card}.
-     */
-    public Card? home { owned get { return (list.size > 0) ? list[0] : null; } }
-    
-    /**
-     * The current displayed { link Card}.
-     */
-    public Card? top { get; private set; default = null; }
-    
-    private Gee.List<Card> list = new Gee.LinkedList<Card>();
-    private Gee.Deque<Card> navigation_stack = new Gee.LinkedList<Card>();
-    private Gee.HashMap<string, Card> names = new Gee.HashMap<string, Card>();
-    
-    /**
      * Create a new { link Deck}.
      *
      * By default the Deck configures the underlying Gtk.Stack to slide left and right, depending
diff --git a/src/util/util-listbox-model.vala b/src/toolkit/toolkit-listbox-model.vala
similarity index 93%
rename from src/util/util-listbox-model.vala
rename to src/toolkit/toolkit-listbox-model.vala
index fb8f0d5..ebbd947 100644
--- a/src/util/util-listbox-model.vala
+++ b/src/toolkit/toolkit-listbox-model.vala
@@ -4,24 +4,7 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
-namespace California {
-
-/**
- * A { link Mutable} is an Object which can internally change state (i.e. is no immutable).
- *
- * { link ListBoxModel} recognizes when an Object supports this interface and will monitor its
- * { link mutated} signal.
- */
-
-public interface Mutable : Object {
-    /**
-     * Fired when important internal state has changed.
-     *
-     * This can be used by collections and other containers to update their own state, such as
-     * re-sorting or re-applying filters.
-     */
-    public signal void mutated();
-}
+namespace California.Toolkit {
 
 /**
  * A simple model for Gtk.ListBox.
diff --git a/src/host/host-popup.vala b/src/toolkit/toolkit-popup.vala
similarity index 99%
rename from src/host/host-popup.vala
rename to src/toolkit/toolkit-popup.vala
index 5219753..beb76b8 100644
--- a/src/host/host-popup.vala
+++ b/src/toolkit/toolkit-popup.vala
@@ -4,7 +4,7 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
-namespace California.Host {
+namespace California.Toolkit {
 
 /**
  * A Popup is a single GtkWindow that grabs the application focus and dismisses itself if it
diff --git a/src/toolkit/toolkit.vala b/src/toolkit/toolkit.vala
new file mode 100644
index 0000000..7349b94
--- /dev/null
+++ b/src/toolkit/toolkit.vala
@@ -0,0 +1,29 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * A grab bag of utility classes for working with GTK.
+ */
+
+namespace California.Toolkit {
+
+private int init_count = 0;
+
+public void init() throws Error {
+    if (!Unit.do_init(ref init_count))
+        return;
+    
+    Calendar.init();
+}
+
+public void terminate() {
+    if (!Unit.do_terminate(ref init_count))
+        return;
+    
+    Calendar.terminate();
+}
+
+}
diff --git a/src/util/util-interfaces.vala b/src/util/util-interfaces.vala
new file mode 100644
index 0000000..9c98230
--- /dev/null
+++ b/src/util/util-interfaces.vala
@@ -0,0 +1,24 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California {
+
+/**
+ * A { link Mutable} is an Object which can internally change state (i.e. is no immutable).
+ */
+
+public interface Mutable : Object {
+    /**
+     * Fired when important internal state has changed.
+     *
+     * This can be used by collections and other containers to update their own state, such as
+     * re-sorting or re-applying filters.
+     */
+    public signal void mutated();
+}
+
+}
+



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