[california/wip/740088-invite] Organizer can now be added/edited, UI tweaks



commit a0e7670e11ca9d40ba64ae2d9609f103ee21965a
Author: Jim Nelson <jim yorba org>
Date:   Wed Nov 19 16:27:00 2014 -0800

    Organizer can now be added/edited, UI tweaks

 src/backing/backing-source.vala        |   12 ++
 src/component/component-instance.vala  |   22 ++++
 src/host/host-attendees-editor.vala    |   63 ++++++++++--
 src/host/host-create-update-event.vala |   54 ++++++++--
 src/host/host-show-event.vala          |   11 +--
 src/rc/attendees-editor.ui             |  175 ++++++++++++++++++++++----------
 src/rc/create-update-event.ui          |   43 +++++++--
 src/toolkit/toolkit-listbox-model.vala |   11 ++-
 vapi/libecal-1.2.vapi                  |    6 +-
 9 files changed, 302 insertions(+), 95 deletions(-)
---
diff --git a/src/backing/backing-source.vala b/src/backing/backing-source.vala
index 26a889f..286e848 100644
--- a/src/backing/backing-source.vala
+++ b/src/backing/backing-source.vala
@@ -22,6 +22,8 @@ public abstract class Source : BaseObject, Gee.Comparable<Source> {
     public const string PROP_VISIBLE = "visible";
     public const string PROP_READONLY = "read-only";
     public const string PROP_COLOR = "color";
+    public const string PROP_MAILBOX_NAME = "mailbox-name";
+    public const string PROP_MAILBOX = "mailbox";
     
     /**
      * A unique identifier for the { link Source}.
@@ -96,6 +98,16 @@ public abstract class Source : BaseObject, Gee.Comparable<Source> {
      */
     public string color { get; set; }
     
+    /**
+     * The user name associated with the { link mailbox}.
+     */
+    public string mailbox_name { get; protected set; default = null; }
+    
+    /**
+     * The mailbox (email address) associated with this { link Source}.
+     */
+    public string? mailbox { get; protected set; default = null; }
+    
     protected Source(Store store, string id, string title) {
         this.store = store;
         this.id = id;
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index 1334ddc..45f8ee4 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -562,6 +562,28 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
     }
     
     /**
+     * Export this { link Instance} as an iCalendar.
+     *
+     * @see export_master
+     * @see is_generated_instance
+     */
+    public iCalendar export(iCal.icalproperty_method method) {
+        return new iCalendar(method, ICAL_PRODID, ICAL_VERSION, null,
+            iterate<Instance>(this).to_array_list());
+    }
+    
+    /**
+     * Export this { link Instance}'s master as an iCalendar.
+     *
+     * @see export
+     * @see is_master
+     */
+    public iCalendar export_master(iCal.icalproperty_method method) {
+        return new iCalendar(method, ICAL_PRODID, ICAL_VERSION, null,
+            iterate<Instance>(master).to_array_list());
+    }
+    
+    /**
      * Returns an appropriate { link Component} instance for the iCalendar component.
      *
      * VCALENDARs should use { link Component.iCalendar}.
diff --git a/src/host/host-attendees-editor.vala b/src/host/host-attendees-editor.vala
index 74d465b..f52e827 100644
--- a/src/host/host-attendees-editor.vala
+++ b/src/host/host-attendees-editor.vala
@@ -16,7 +16,10 @@ public class AttendeesEditor : Gtk.Box, Toolkit.Card {
     
     public Gtk.Widget? default_widget { get { return accept_button; } }
     
-    public Gtk.Widget? initial_focus { get { return add_guest_entry; } }
+    public Gtk.Widget? initial_focus { get { return organizer_entry; } }
+    
+    [GtkChild]
+    private Gtk.Entry organizer_entry;
     
     [GtkChild]
     private Gtk.Entry add_guest_entry;
@@ -35,14 +38,33 @@ public class AttendeesEditor : Gtk.Box, Toolkit.Card {
     
     private new Component.Event? event = null;
     private Toolkit.ListBoxModel<Component.Person> guest_model;
+    private Toolkit.EntryClearTextConnector entry_clear_connector = new Toolkit.EntryClearTextConnector();
     
     public AttendeesEditor() {
         guest_model = new Toolkit.ListBoxModel<Component.Person>(guest_listbox, model_presentation);
         
+        organizer_entry.bind_property("text", accept_button, "sensitive", BindingFlags.SYNC_CREATE,
+            transform_to_accept_sensitive);
+        guest_model.bind_property(Toolkit.ListBoxModel.PROP_SIZE, accept_button, "sensitive",
+            BindingFlags.SYNC_CREATE, transform_to_accept_sensitive);
+        
         add_guest_entry.bind_property("text", add_guest_button, "sensitive", BindingFlags.SYNC_CREATE,
             transform_add_guest_text_to_button);
+        
         guest_model.bind_property(Toolkit.ListBoxModel.PROP_SELECTED, remove_guest_button, "sensitive",
             BindingFlags.SYNC_CREATE, transform_list_selected_to_button);
+        
+        entry_clear_connector.connect_to(organizer_entry);
+        entry_clear_connector.connect_to(add_guest_entry);
+    }
+    
+    private bool transform_to_accept_sensitive(Binding binding, Value source_value, ref Value target_value) {
+        if (guest_model.size > 0 || !String.is_empty(organizer_entry.text))
+            target_value = Email.is_valid_mailbox(organizer_entry.text);
+        else
+            target_value = true;
+        
+        return true;
     }
     
     private bool transform_add_guest_text_to_button(Binding binding, Value source_value,
@@ -70,6 +92,10 @@ public class AttendeesEditor : Gtk.Box, Toolkit.Card {
             .filter(attendee => !event.organizers.contains(attendee))
             .to_array_list()
         );
+        
+        // we only support one organizer, so use first one in form
+        if (!event.organizers.is_empty)
+            organizer_entry.text = traverse<Component.Person>(event.organizers).first().mailbox;
     }
     
     [GtkCallback]
@@ -88,23 +114,31 @@ public class AttendeesEditor : Gtk.Box, Toolkit.Card {
         return false;
     }
     
-    [GtkCallback]
-    private void on_add_guest_button_clicked() {
-        string mailbox = add_guest_entry.text.strip();
+    private Component.Person? make_person(string text, Component.Person.Relationship relationship) {
+        string mailbox = text.strip();
         if (!Email.is_valid_mailbox(mailbox))
-            return;
+            return null;
         
         try {
-            // add to model (which adds to listbox) and clear entry
-            guest_model.add(new Component.Person(Component.Person.Relationship.ATTENDEE,
-                Email.generate_mailto_uri(mailbox)));
-            add_guest_entry.text = "";
+            return new Component.Person(relationship, Email.generate_mailto_uri(mailbox));
         } catch (Error err) {
             debug("Unable to generate mailto from \"%s\": %s", mailbox, err.message);
+            
+            return null;
         }
     }
     
     [GtkCallback]
+    private void on_add_guest_button_clicked() {
+        // add to model (which adds to listbox) and clear entry
+        Component.Person? attendee = make_person(add_guest_entry.text, 
Component.Person.Relationship.ATTENDEE);
+        if (attendee != null)
+            guest_model.add(attendee);
+        
+        add_guest_entry.text = "";
+    }
+    
+    [GtkCallback]
     private void on_remove_guest_button_clicked() {
         if (guest_model.selected != null)
             guest_model.remove(guest_model.selected);
@@ -112,6 +146,17 @@ public class AttendeesEditor : Gtk.Box, Toolkit.Card {
     
     [GtkCallback]
     private void on_accept_button_clicked() {
+        Component.Person? organizer = null;
+        if (guest_model.size > 0) {
+            organizer = make_person(organizer_entry.text, Component.Person.Relationship.ORGANIZER);
+            if (organizer == null)
+                return;
+        }
+        
+        event.clear_organizers();
+        if (organizer != null)
+            event.add_organizers(iterate<Component.Person>(organizer).to_array_list());
+        
         event.clear_attendees();
         event.add_attendees(guest_model.all());
         
diff --git a/src/host/host-create-update-event.vala b/src/host/host-create-update-event.vala
index 516c411..0203f00 100644
--- a/src/host/host-create-update-event.vala
+++ b/src/host/host-create-update-event.vala
@@ -42,6 +42,12 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
     private Gtk.Entry location_entry;
     
     [GtkChild]
+    private Gtk.Label organizer_label;
+    
+    [GtkChild]
+    private Gtk.Label organizer_text;
+    
+    [GtkChild]
     private Gtk.Label attendees_text;
     
     [GtkChild]
@@ -93,6 +99,9 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
         update_this_button.clicked.connect(on_update_this_button_clicked);
         cancel_recurring_button.clicked.connect(on_cancel_recurring_button_clicked);
         
+        organizer_text.query_tooltip.connect(on_organizer_text_query_tooltip);
+        organizer_text.has_tooltip = true;
+        
         attendees_text.query_tooltip.connect(on_attendees_text_query_tooltip);
         attendees_text.has_tooltip = true;
         
@@ -160,10 +169,20 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
         
         location_entry.text = event.location ?? "";
         description_textview.buffer.text = event.description ?? "";
+        
+        // Only show "Organizer" and associated text if something to show
+        organizer_text.label = traverse<Component.Person>(event.organizers)
+            .sort()
+            .to_string(stringify_persons);
+        bool has_organizer = !String.is_empty(organizer_text.label);
+        organizer_label.visible = organizer_text.visible = has_organizer;
+        organizer_label.no_show_all = organizer_text.no_show_all = !has_organizer;
+        
+        // Don't count organizers as attendees
         attendees_text.label = traverse<Component.Person>(event.attendees)
             .filter(attendee => !event.organizers.contains(attendee))
             .sort()
-            .to_string(stringify_attendees);
+            .to_string(stringify_persons);
         if (String.is_empty(attendees_text.label)) {
             // "None" as in "no people"
             attendees_text.label = _("None");
@@ -188,6 +207,18 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
         rotating_button_box.family = FAMILY_NORMAL;
     }
     
+    private bool on_organizer_text_query_tooltip(Gtk.Widget widget, int x, int y, bool keyboard,
+        Gtk.Tooltip tooltip) {
+        if (!organizer_text.get_layout().is_ellipsized())
+            return false;
+        
+        tooltip.set_text(traverse<Component.Person>(event.organizers)
+            .sort()
+            .to_string(stringify_persons_tooltip));
+        
+        return true;
+    }
+    
     private bool on_attendees_text_query_tooltip(Gtk.Widget widget, int x, int y, bool keyboard,
         Gtk.Tooltip tooltip) {
         if (!attendees_text.get_layout().is_ellipsized())
@@ -196,17 +227,17 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
         tooltip.set_text(traverse<Component.Person>(event.attendees)
             .filter(attendee => !event.organizers.contains(attendee))
             .sort()
-            .to_string(stringify_attendees_tooltip));
+            .to_string(stringify_persons_tooltip));
         
         return true;
     }
     
-    private string? stringify_attendees(Component.Person person, bool is_first, bool is_last) {
+    private string? stringify_persons(Component.Person person, bool is_first, bool is_last) {
         // Email address followed by common separator, i.e. "alice example com, bob example com"
         return !is_last ? _("%s, ").printf(person.full_mailbox) : person.full_mailbox;
     }
     
-    private string? stringify_attendees_tooltip(Component.Person person, bool is_first, bool is_last) {
+    private string? stringify_persons_tooltip(Component.Person person, bool is_first, bool is_last) {
         return !is_last ? "%s\n".printf(person.full_mailbox) : person.full_mailbox;
     }
     
@@ -338,7 +369,8 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
         
         Toolkit.set_unbusy(this, cursor);
         
-        invite_attendees(target);
+        // TODO: PUBLISH or REQUEST?
+        invite_attendees(target, iCal.icalproperty_method.REQUEST);
         
         if (create_err == null)
             notify_success();
@@ -391,7 +423,8 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
         
         Toolkit.set_unbusy(this, cursor);
         
-        invite_attendees(target);
+        // PUBLISH is used to update an existing event
+        invite_attendees(target, iCal.icalproperty_method.PUBLISH);
         
         if (update_err == null)
             notify_success();
@@ -399,9 +432,12 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
             report_error(_("Unable to update event: %s").printf(update_err.message));
     }
     
-    private void invite_attendees(Component.Event event) {
-        Component.iCalendar ics = new Component.iCalendar(iCal.icalproperty_method.REQUEST,
-            Component.ICAL_PRODID, Component.ICAL_VERSION, null, event.attendees);
+    private void invite_attendees(Component.Event event, iCal.icalproperty_method method) {
+        // no attendees, no invites
+        if (event.attendees.size == 0)
+            return;
+        
+        Component.iCalendar ics = event.export_master(method);
         
         // export .ics to temporary directory so the filename is a pristine "invite.ics"
         string temporary_filename;
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index f5cd1d0..c826247 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -322,14 +322,9 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
             return;
         
         // if switch available and active, export master not the generated instance
-        Component.Instance to_export = (export_master_checkbutton != null && 
export_master_checkbutton.active)
-            ? event.master
-            : event;
-        
-        // Export as a self-contained iCalendar
-        Component.iCalendar icalendar = new Component.iCalendar(iCal.icalproperty_method.PUBLISH,
-            Component.ICAL_PRODID, Component.ICAL_VERSION, null,
-            iterate<Component.Instance>(to_export).to_array_list());
+        Component.iCalendar icalendar = (export_master_checkbutton != null && 
export_master_checkbutton.active)
+            ? event.export_master(iCal.icalproperty_method.PUBLISH)
+            : event.export(iCal.icalproperty_method.PUBLISH);
         
         try {
             FileUtils.set_contents(filename, icalendar.source);
diff --git a/src/rc/attendees-editor.ui b/src/rc/attendees-editor.ui
index cfb058c..5ee9186 100644
--- a/src/rc/attendees-editor.ui
+++ b/src/rc/attendees-editor.ui
@@ -24,94 +24,157 @@
       </packing>
     </child>
     <child>
-      <object class="GtkBox" id="box2">
+      <object class="GtkGrid" id="grid1">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
-        <property name="spacing">4</property>
+        <property name="row_spacing">4</property>
+        <property name="column_spacing">6</property>
         <child>
-          <object class="GtkEntry" id="add_guest_entry">
+          <object class="GtkLabel" id="organizer_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="margin_bottom">4</property>
+            <property name="xalign">1</property>
+            <property name="label" translatable="yes">Organizer</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="guest_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="xalign">1</property>
+            <property name="label" translatable="yes">Guests</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="organizer_entry">
             <property name="visible">True</property>
             <property name="can_focus">True</property>
-            <property name="tooltip_text" translatable="yes">For example, bob example com</property>
+            <property name="tooltip_text" translatable="yes">For example, alice example com</property>
+            <property name="margin_bottom">4</property>
+            <property name="hexpand">True</property>
             <property name="activates_default">True</property>
-            <property name="placeholder_text" translatable="yes">Email address</property>
+            <property name="placeholder_text" translatable="yes">Email address (required if guests are 
invited)</property>
             <property name="input_purpose">email</property>
-            <signal name="focus-in-event" handler="on_add_guest_entry_focus_in_event" 
object="CaliforniaHostAttendeesEditor" swapped="no"/>
-            <signal name="focus-out-event" handler="on_add_guest_entry_focus_out_event" 
object="CaliforniaHostAttendeesEditor" swapped="no"/>
           </object>
           <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">0</property>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
           </packing>
         </child>
         <child>
-          <object class="GtkButton" id="add_guest_button">
-            <property name="label" translatable="yes">A_dd Guest</property>
+          <object class="GtkBox" id="box2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">4</property>
+            <child>
+              <object class="GtkEntry" id="add_guest_entry">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="tooltip_text" translatable="yes">For example, bob example com</property>
+                <property name="activates_default">True</property>
+                <property name="placeholder_text" translatable="yes">Email address</property>
+                <property name="input_purpose">email</property>
+                <signal name="focus-in-event" handler="on_add_guest_entry_focus_in_event" 
object="CaliforniaHostAttendeesEditor" swapped="no"/>
+                <signal name="focus-out-event" handler="on_add_guest_entry_focus_out_event" 
object="CaliforniaHostAttendeesEditor" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="add_guest_button">
+                <property name="label" translatable="yes">A_dd Guest</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="has_default">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_underline">True</property>
+                <property name="xalign">0.60000002384185791</property>
+                <signal name="clicked" handler="on_add_guest_button_clicked" 
object="CaliforniaHostAttendeesEditor" swapped="no"/>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="pack_type">end</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="remove_guest_button">
+            <property name="label" translatable="yes">_Remove Guest</property>
             <property name="visible">True</property>
             <property name="can_focus">True</property>
-            <property name="can_default">True</property>
-            <property name="has_default">True</property>
             <property name="receives_default">True</property>
+            <property name="halign">end</property>
             <property name="use_underline">True</property>
-            <property name="xalign">0.60000002384185791</property>
-            <signal name="clicked" handler="on_add_guest_button_clicked" 
object="CaliforniaHostAttendeesEditor" swapped="no"/>
+            <signal name="clicked" handler="on_remove_guest_button_clicked" 
object="CaliforniaHostAttendeesEditor" swapped="no"/>
           </object>
           <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="pack_type">end</property>
-            <property name="position">1</property>
+            <property name="left_attach">1</property>
+            <property name="top_attach">3</property>
           </packing>
         </child>
-      </object>
-      <packing>
-        <property name="expand">False</property>
-        <property name="fill">True</property>
-        <property name="position">1</property>
-      </packing>
-    </child>
-    <child>
-      <object class="GtkScrolledWindow" id="scrolledwindow1">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="shadow_type">in</property>
         <child>
-          <object class="GtkViewport" id="viewport1">
+          <object class="GtkScrolledWindow" id="scrolledwindow1">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
+            <property name="can_focus">True</property>
+            <property name="shadow_type">in</property>
             <child>
-              <object class="GtkListBox" id="guest_listbox">
+              <object class="GtkViewport" id="viewport1">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
-                <property name="hexpand">True</property>
-                <property name="vexpand">True</property>
-                <property name="activate_on_single_click">False</property>
+                <child>
+                  <object class="GtkListBox" id="guest_listbox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="vexpand">True</property>
+                    <property name="activate_on_single_click">False</property>
+                  </object>
+                </child>
               </object>
             </child>
           </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">2</property>
+          </packing>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+        <child>
+          <placeholder/>
         </child>
-      </object>
-      <packing>
-        <property name="expand">True</property>
-        <property name="fill">True</property>
-        <property name="position">2</property>
-      </packing>
-    </child>
-    <child>
-      <object class="GtkButton" id="remove_guest_button">
-        <property name="label" translatable="yes">_Remove Guest</property>
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="receives_default">True</property>
-        <property name="halign">end</property>
-        <property name="use_underline">True</property>
-        <signal name="clicked" handler="on_remove_guest_button_clicked" 
object="CaliforniaHostAttendeesEditor" swapped="no"/>
       </object>
       <packing>
         <property name="expand">False</property>
         <property name="fill">True</property>
-        <property name="position">3</property>
+        <property name="position">2</property>
       </packing>
     </child>
     <child>
@@ -161,7 +224,7 @@
       <packing>
         <property name="expand">False</property>
         <property name="fill">True</property>
-        <property name="position">4</property>
+        <property name="position">5</property>
       </packing>
     </child>
   </template>
diff --git a/src/rc/create-update-event.ui b/src/rc/create-update-event.ui
index 43fb9d0..0761f72 100644
--- a/src/rc/create-update-event.ui
+++ b/src/rc/create-update-event.ui
@@ -175,7 +175,7 @@
       </object>
       <packing>
         <property name="left_attach">0</property>
-        <property name="top_attach">7</property>
+        <property name="top_attach">8</property>
         <property name="width">2</property>
       </packing>
     </child>
@@ -191,7 +191,7 @@
       </object>
       <packing>
         <property name="left_attach">0</property>
-        <property name="top_attach">6</property>
+        <property name="top_attach">7</property>
       </packing>
     </child>
     <child>
@@ -202,7 +202,7 @@
       </object>
       <packing>
         <property name="left_attach">1</property>
-        <property name="top_attach">6</property>
+        <property name="top_attach">7</property>
       </packing>
     </child>
     <child>
@@ -221,7 +221,7 @@
       </object>
       <packing>
         <property name="left_attach">0</property>
-        <property name="top_attach">5</property>
+        <property name="top_attach">6</property>
       </packing>
     </child>
     <child>
@@ -248,7 +248,7 @@
       </object>
       <packing>
         <property name="left_attach">1</property>
-        <property name="top_attach">5</property>
+        <property name="top_attach">6</property>
       </packing>
     </child>
     <child>
@@ -265,7 +265,7 @@
       </object>
       <packing>
         <property name="left_attach">0</property>
-        <property name="top_attach">4</property>
+        <property name="top_attach">5</property>
       </packing>
     </child>
     <child>
@@ -276,7 +276,7 @@
       </object>
       <packing>
         <property name="left_attach">1</property>
-        <property name="top_attach">4</property>
+        <property name="top_attach">5</property>
       </packing>
     </child>
     <child>
@@ -293,7 +293,7 @@
       </object>
       <packing>
         <property name="left_attach">0</property>
-        <property name="top_attach">3</property>
+        <property name="top_attach">4</property>
       </packing>
     </child>
     <child>
@@ -341,6 +341,33 @@
       </object>
       <packing>
         <property name="left_attach">1</property>
+        <property name="top_attach">4</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="organizer_label">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="xalign">1</property>
+        <property name="label" translatable="yes">Organizer</property>
+        <style>
+          <class name="dim-label"/>
+        </style>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">3</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="organizer_text">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="xalign">0</property>
+        <property name="label">(none)</property>
+      </object>
+      <packing>
+        <property name="left_attach">1</property>
         <property name="top_attach">3</property>
       </packing>
     </child>
diff --git a/src/toolkit/toolkit-listbox-model.vala b/src/toolkit/toolkit-listbox-model.vala
index efe3e95..92ef654 100644
--- a/src/toolkit/toolkit-listbox-model.vala
+++ b/src/toolkit/toolkit-listbox-model.vala
@@ -19,6 +19,7 @@ namespace California.Toolkit {
 
 public class ListBoxModel<G> : BaseObject {
     public const string PROP_SELECTED = "selected";
+    public const string PROP_SIZE = "size";
     
     private const string KEY = "org.yorba.california.listbox-model.model";
     
@@ -35,9 +36,9 @@ public class ListBoxModel<G> : BaseObject {
     public Gtk.ListBox listbox { get; private set; }
     
     /**
-     * The number if items in the { link ListBoxModel}.
+     * The number of items in the { link ListBoxModel}.
      */
-    public int size { get { return items.size; } }
+    public int size { get; private set; default = 0; }
     
     /**
      * The item currently selected by the { link listbox}, null if no selection has been made.
@@ -126,6 +127,9 @@ public class ListBoxModel<G> : BaseObject {
         listbox.add(row);
         row.show_all();
         
+        // adjust size before signalling
+        size = size + 1;
+        
         added(item);
         
         return true;
@@ -184,6 +188,9 @@ public class ListBoxModel<G> : BaseObject {
         if (remove_from_listbox)
             row.destroy();
         
+        // adjust before signalling
+        size = (size - 1).clamp(0, int.MAX);
+        
         removed(item);
         
         return true;
diff --git a/vapi/libecal-1.2.vapi b/vapi/libecal-1.2.vapi
index d692d2e..6ead3ec 100644
--- a/vapi/libecal-1.2.vapi
+++ b/vapi/libecal-1.2.vapi
@@ -560,11 +560,11 @@ namespace E {
        public delegate bool CalRecurInstanceFn (E.CalComponent comp, time_t instance_start, time_t 
instance_end);
        [CCode (cheader_filename = "libecal/libecal.h")]
        public delegate iCal.icaltimezone CalRecurResolveTimezoneFn (string tzid);
-       [CCode (cheader_filename = "libecal/libecal.h")]
+       [CCode (cheader_filename = "libecal/libecal.h", cname = "CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS")]
        public const string CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS;
-       [CCode (cheader_filename = "libecal/libecal.h")]
+       [CCode (cheader_filename = "libecal/libecal.h", cname = "CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS")]
        public const string CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS;
-       [CCode (cheader_filename = "libecal/libecal.h")]
+       [CCode (cheader_filename = "libecal/libecal.h", cname = "CAL_BACKEND_PROPERTY_DEFAULT_OBJECT")]
        public const string CAL_BACKEND_PROPERTY_DEFAULT_OBJECT;
        [CCode (cheader_filename = "libecal/libecal.h")]
        public const string CAL_STATIC_CAPABILITY_ALARM_DESCRIPTION;


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