[california/wip/731543-attendees] Refinements



commit 48fb987d96a73df1ffbac1ab52dcfc8b1462ae28
Author: Jim Nelson <jim yorba org>
Date:   Wed Nov 12 16:07:06 2014 -0800

    Refinements

 src/component/component-person.vala    |   34 ++++++++++++++++--
 src/host/host-attendees-editor.vala    |   63 ++++++++++++++++++++++++++++++--
 src/host/host-create-update-event.vala |    2 +
 src/rc/attendees-editor.ui             |   31 ++++++++++++----
 src/rc/create-update-event.ui          |   34 ++----------------
 src/toolkit/toolkit-listbox-model.vala |   15 ++++++++
 src/util/util-uri.vala                 |    9 +++++
 7 files changed, 144 insertions(+), 44 deletions(-)
---
diff --git a/src/component/component-person.vala b/src/component/component-person.vala
index 4ccfcf7..78a81f6 100644
--- a/src/component/component-person.vala
+++ b/src/component/component-person.vala
@@ -33,6 +33,16 @@ public class Person : BaseObject, Gee.Hashable<Person>, Gee.Comparable<Person> {
     public string? common_name { get; private set; default = null; }
     
     /**
+     * The participation ROLE for the { link Person}.
+     */
+    public iCal.icalparameter_role role { get; private set; default = 
iCal.icalparameter_role.REQPARTICIPANT; }
+    
+    /**
+     * RSVP required for the { link Person}.
+     */
+    public bool rsvp { get; private set; default = false; }
+    
+    /**
      * The { link mailto} URI as a text string.
      *
      * @see mailbox
@@ -59,18 +69,28 @@ public class Person : BaseObject, Gee.Hashable<Person>, Gee.Comparable<Person> {
     private Gee.HashSet<string> parameters = new Gee.HashSet<string>(String.ci_hash, String.ci_equal);
     
     /**
-     * Create an { link Person} with the required { link mailto} and optional { link common_name}.
+     * Create a { link Person} with the required { link mailto} and optional { link common_name}.
      */
-    public Person(Soup.URI mailto, string? common_name) throws ComponentError {
+    public Person(Soup.URI mailto, string? common_name = null,
+        iCal.icalparameter_role role = iCal.icalparameter_role.REQPARTICIPANT, bool rsvp = false)
+        throws ComponentError {
         validate_mailto(mailto);
         
         this.mailto = mailto;
         this.common_name = common_name;
+        this.role = role;
+        this.rsvp = rsvp;
         full_mailbox = make_full_address(mailto, common_name);
         
         // store in parameters in case object is serialized as an iCal property.
         if (!String.is_empty(common_name))
             parameters.add(new iCal.icalparameter.cn(common_name).as_ical_string());
+        
+        if (role != iCal.icalparameter_role.REQPARTICIPANT)
+            parameters.add(new iCal.icalparameter.role(role).as_ical_string());
+        
+        if (rsvp)
+            parameters.add(new iCal.icalparameter.rsvp(iCal.icalparameter_rsvp.TRUE).as_ical_string());
     }
     
     internal Person.from_property(iCal.icalproperty prop) throws Error {
@@ -103,6 +123,14 @@ public class Person : BaseObject, Gee.Hashable<Person>, Gee.Comparable<Person> {
                     common_name = param.get_cn();
                 break;
                 
+                case iCal.icalparameter_kind.ROLE_PARAMETER:
+                    role = param.get_role();
+                break;
+                
+                case iCal.icalparameter_kind.RSVP_PARAMETER:
+                    rsvp = param.get_rsvp() == iCal.icalparameter_rsvp.TRUE;
+                break;
+                
                 default:
                     // fall-through
                 break;
@@ -115,7 +143,7 @@ public class Person : BaseObject, Gee.Hashable<Person>, Gee.Comparable<Person> {
     }
     
     private static void validate_mailto(Soup.URI uri) throws ComponentError {
-        if (uri.scheme != "mailto" || String.is_empty(uri.path))
+        if (!String.ci_equal(uri.scheme, "mailto") || String.is_empty(uri.path) || 
!URI.is_valid_mailbox(uri.path))
             throw new ComponentError.INVALID("Invalid mailto: %s", uri.to_string(false));
     }
     
diff --git a/src/host/host-attendees-editor.vala b/src/host/host-attendees-editor.vala
index c00b274..5e2646d 100644
--- a/src/host/host-attendees-editor.vala
+++ b/src/host/host-attendees-editor.vala
@@ -22,16 +22,41 @@ public class AttendeesEditor : Gtk.Box, Toolkit.Card {
     private Gtk.Entry add_guest_entry;
     
     [GtkChild]
+    private Gtk.Button add_guest_button;
+    
+    [GtkChild]
     private Gtk.ListBox guest_listbox;
     
     [GtkChild]
+    private Gtk.Button remove_guest_button;
+    
+    [GtkChild]
     private Gtk.Button accept_button;
     
     private new Component.Event? event = null;
-    private Toolkit.ListBoxModel guest_model;
+    private Toolkit.ListBoxModel<Component.Person> guest_model;
     
     public AttendeesEditor() {
         guest_model = new Toolkit.ListBoxModel<Component.Person>(guest_listbox, model_presentation);
+        
+        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);
+    }
+    
+    private bool transform_add_guest_text_to_button(Binding binding, Value source_value,
+        ref Value target_value) {
+        target_value = URI.is_valid_mailbox(add_guest_entry.text);
+        
+        return true;
+    }
+    
+    private bool transform_list_selected_to_button(Binding binding, Value source_value,
+        ref Value target_value) {
+        target_value = guest_model.selected != null;
+        
+        return true;
     }
     
     public void jumped_to(Toolkit.Card? from, Toolkit.Card.Jump reason, Value? message) {
@@ -39,13 +64,45 @@ public class AttendeesEditor : Gtk.Box, Toolkit.Card {
         if (event == null)
             return;
         
+        // clear list and add all attendees who are not organizers
         guest_model.clear();
-        guest_model.add_many(event.attendees);
+        guest_model.add_many(traverse<Component.Person>(event.attendees)
+            .filter(attendee => !event.organizers.contains(attendee))
+            .to_array_list()
+        );
+    }
+    
+    [GtkCallback]
+    private void on_add_guest_button_clicked() {
+        if (!URI.is_valid_mailbox(add_guest_entry.text))
+            return;
+        
+        try {
+            // add to model (which adds to listbox) and clear entry
+            guest_model.add(new Component.Person(URI.generate_mailto(add_guest_entry.text)));
+            add_guest_entry.text = "";
+        } catch (Error err) {
+            debug("Unable to generate mailto: %s", err.message);
+        }
+    }
+    
+    [GtkCallback]
+    private void on_remove_guest_button_clicked() {
+        if (guest_model.selected != null)
+            guest_model.remove(guest_model.selected);
+    }
+    
+    [GtkCallback]
+    private void on_accept_button_clicked() {
+        event.clear_attendees();
+        event.add_attendees(guest_model.all());
+        
+        jump_to_card_by_name(CreateUpdateEvent.ID, event);
     }
     
     [GtkCallback]
     private void on_cancel_button_clicked() {
-        jump_home();
+        jump_back();
     }
     
     private Gtk.Widget model_presentation(Component.Person person) {
diff --git a/src/host/host-create-update-event.vala b/src/host/host-create-update-event.vala
index 1b781cd..74d8f8e 100644
--- a/src/host/host-create-update-event.vala
+++ b/src/host/host-create-update-event.vala
@@ -160,6 +160,8 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
         attendees_text.label = traverse<Component.Person>(event.attendees)
             .filter(attendee => !event.organizers.contains(attendee))
             .to_string(stringify_attendees);
+        if (String.is_empty(attendees_text.label))
+            attendees_text.label = _("None");
         
         Component.Event master = event.is_master_instance ? event : (Component.Event) event.master;
         
diff --git a/src/rc/attendees-editor.ui b/src/rc/attendees-editor.ui
index 3296111..00a064a 100644
--- a/src/rc/attendees-editor.ui
+++ b/src/rc/attendees-editor.ui
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.16.1 -->
+<!-- Generated with glade 3.18.3 -->
 <interface>
   <requires lib="gtk+" version="3.12"/>
   <template class="CaliforniaHostAttendeesEditor" parent="GtkBox">
@@ -32,6 +32,7 @@
           <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="placeholder_text" translatable="yes">Email address</property>
             <property name="input_purpose">email</property>
           </object>
@@ -49,6 +50,7 @@
             <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>
@@ -65,15 +67,28 @@
       </packing>
     </child>
     <child>
-      <object class="GtkListBox" id="guest_listbox">
+      <object class="GtkScrolledWindow" id="scrolledwindow1">
         <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>
+        <property name="can_focus">True</property>
+        <property name="shadow_type">in</property>
+        <child>
+          <object class="GtkViewport" id="viewport1">
+            <property name="visible">True</property>
+            <property name="can_focus">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="expand">False</property>
+        <property name="expand">True</property>
         <property name="fill">True</property>
         <property name="position">2</property>
       </packing>
@@ -86,6 +101,7 @@
         <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>
@@ -126,6 +142,7 @@
             <property name="has_default">True</property>
             <property name="receives_default">True</property>
             <property name="use_underline">True</property>
+            <signal name="clicked" handler="on_accept_button_clicked" object="CaliforniaHostAttendeesEditor" 
swapped="no"/>
             <style>
               <class name="suggested-action"/>
             </style>
diff --git a/src/rc/create-update-event.ui b/src/rc/create-update-event.ui
index d3969a6..43fb9d0 100644
--- a/src/rc/create-update-event.ui
+++ b/src/rc/create-update-event.ui
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.16.1 -->
+<!-- Generated with glade 3.18.3 -->
 <interface>
   <requires lib="gtk+" version="3.10"/>
   <template class="CaliforniaHostCreateUpdateEvent" parent="GtkGrid">
@@ -23,8 +23,6 @@
       <packing>
         <property name="left_attach">1</property>
         <property name="top_attach">0</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -40,8 +38,6 @@
       <packing>
         <property name="left_attach">0</property>
         <property name="top_attach">0</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -57,8 +53,6 @@
       <packing>
         <property name="left_attach">0</property>
         <property name="top_attach">1</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -105,8 +99,6 @@
       <packing>
         <property name="left_attach">1</property>
         <property name="top_attach">1</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -122,8 +114,6 @@
       <packing>
         <property name="left_attach">0</property>
         <property name="top_attach">2</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -170,8 +160,6 @@
       <packing>
         <property name="left_attach">1</property>
         <property name="top_attach">2</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -189,7 +177,6 @@
         <property name="left_attach">0</property>
         <property name="top_attach">7</property>
         <property name="width">2</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -205,8 +192,6 @@
       <packing>
         <property name="left_attach">0</property>
         <property name="top_attach">6</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -218,8 +203,6 @@
       <packing>
         <property name="left_attach">1</property>
         <property name="top_attach">6</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -239,8 +222,6 @@
       <packing>
         <property name="left_attach">0</property>
         <property name="top_attach">5</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -268,8 +249,6 @@
       <packing>
         <property name="left_attach">1</property>
         <property name="top_attach">5</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -287,8 +266,6 @@
       <packing>
         <property name="left_attach">0</property>
         <property name="top_attach">4</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -300,8 +277,6 @@
       <packing>
         <property name="left_attach">1</property>
         <property name="top_attach">4</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -310,7 +285,7 @@
         <property name="can_focus">False</property>
         <property name="no_show_all">True</property>
         <property name="xalign">1</property>
-        <property name="label" translatable="yes">_Guests</property>
+        <property name="label" translatable="yes">Invited Guests</property>
         <property name="use_underline">True</property>
         <style>
           <class name="dim-label"/>
@@ -319,8 +294,6 @@
       <packing>
         <property name="left_attach">0</property>
         <property name="top_attach">3</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
     <child>
@@ -348,6 +321,7 @@
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <property name="receives_default">True</property>
+            <property name="tooltip_text" translatable="yes">Add and remove invited guests</property>
             <property name="relief">none</property>
             <signal name="clicked" handler="on_attendees_button_clicked" 
object="CaliforniaHostCreateUpdateEvent" swapped="no"/>
             <child>
@@ -368,8 +342,6 @@
       <packing>
         <property name="left_attach">1</property>
         <property name="top_attach">3</property>
-        <property name="width">1</property>
-        <property name="height">1</property>
       </packing>
     </child>
   </template>
diff --git a/src/toolkit/toolkit-listbox-model.vala b/src/toolkit/toolkit-listbox-model.vala
index 951b8ad..b043fda 100644
--- a/src/toolkit/toolkit-listbox-model.vala
+++ b/src/toolkit/toolkit-listbox-model.vala
@@ -197,6 +197,21 @@ public class ListBoxModel<G> : BaseObject {
     }
     
     /**
+     * A Gee.Set of all items in the { link ListBoxModel}, sorted if appropriate.
+     */
+    public Gee.Set<G> all() {
+        Gee.TreeSet<G> treeset;
+        if (comparator != null)
+            treeset = new Gee.TreeSet<G>(comparator);
+        else
+            treeset = new Gee.TreeSet<G>();
+        
+        treeset.add_all(items.keys);
+        
+        return treeset;
+    }
+    
+    /**
      * Returns the { link ModelPresentation} widget for the item.
      */
     public Gtk.Widget? get_widget_for_item(G item) {
diff --git a/src/util/util-uri.vala b/src/util/util-uri.vala
index 0a61e1a..cd92c53 100644
--- a/src/util/util-uri.vala
+++ b/src/util/util-uri.vala
@@ -76,5 +76,14 @@ public bool is_valid_mailbox(string str) {
     return email_regex.match(str);
 }
 
+/**
+ * Generates a valid mailto: Soup.URI given a mailbox (i.e. email) address.
+ *
+ * No validity checking is done here on the mailbox; use { link is_valid_mailbox}.
+ */
+public Soup.URI generate_mailto(string mailbox) throws Error {
+    return parse("mailto:%s".printf(GLib.Uri.escape_string(mailbox, "@")));
+}
+
 }
 


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