[california/wip/729221-quick-add] Always use quick add when creating events



commit 9eb676ec42d5b444ce69b5ba3a9613f9c12acc1a
Author: Jim Nelson <jim yorba org>
Date:   Fri May 30 18:25:39 2014 -0700

    Always use quick add when creating events
    
    Only thing missing is a button to go to the full create event dialog
    (if the user doesn't want to use parsed string for adding event)

 src/component/component-details-parser.vala |   47 +++++++++++++++++++---
 src/component/component-event.vala          |   56 ++++++++++++++++++++++++++
 src/host/host-create-update-event.vala      |   12 ++++--
 src/host/host-main-window.vala              |   42 ++++----------------
 src/host/host-quick-create-event.vala       |   58 ++++++++++++++++++++++++--
 src/host/host-show-event.vala               |   51 +----------------------
 src/rc/quick-create-event.ui                |   47 ++++++++++++++++------
 src/view/month/month-grid.vala              |   14 +------
 8 files changed, 204 insertions(+), 123 deletions(-)
---
diff --git a/src/component/component-details-parser.vala b/src/component/component-details-parser.vala
index 20c42c3..3db383b 100644
--- a/src/component/component-details-parser.vala
+++ b/src/component/component-details-parser.vala
@@ -9,7 +9,7 @@ namespace California.Component {
 /**
  * Parse the details of a user-entered string into an { link Event}.
  *
- * DetailsParser makes no claims of natural language parsing or interpretation.  It merely
+ * DetailsParser makes no claims of advanced natural language parsing or interpretation.  It merely
  * looks for keywords and patterns within the tokenized stream and guesses what Event details
  * they refer to.
  *
@@ -51,7 +51,7 @@ public class DetailsParser : BaseObject {
     /**
      * The generated { link Event}.
      */
-    public Component.Event event { get; private set; default = new Component.Event.blank(); }
+    public Component.Event event { get; private set; }
     
     private Collection.LookaheadStack<Token> stack;
     private StringBuilder summary = new StringBuilder();
@@ -66,18 +66,50 @@ public class DetailsParser : BaseObject {
     private bool adding_location = false;
     
     /**
-     * Parses a user-entered string of { link Event} details into an Event.
+     * Parses a user-entered string of event details into an { link Event}.
      *
-     * This always generates an Event, but very little in it may be available.  Its backup case
+     * This always generates an Event, but very little in it may be prepared.  Its backup case
      * is to use the details string as a summary and leave all other fields empty.  The caller
      * should complete the other fields to generate a valid VEVENT.
      *
+     * If the caller wishes to "pre-fill" the Event with certain details, it can supply an Event
+     * that will be used for initial values.  This will have an effect on the parser; in particular,
+     * with those details pre-filled in, values detected in the parsed string that would normally
+     * be used in their place will be dropped.  In other words, adding initial values will remove
+     * what the user can then add in their own string.
+     *
+     * The { link details} supplied by the user are stored in { link Event.description} verbatim.
+     *
      * If the details string is empty, a blank Event is generated.
      */
-    public DetailsParser(string? details, Backing.CalendarSource? calendar_source) {
+    public DetailsParser(string? details, Backing.CalendarSource? calendar_source, Event? initial = null) {
+        event = initial ?? new Component.Event.blank();
         event.calendar_source = calendar_source;
         this.details = details ?? "";
         
+        // pull out details from the initial Event and add to the local state, which is then
+        // supplanted (but not replaced) by parsed information
+        if (initial != null) {
+            if (!String.is_empty(initial.summary))
+                summary.append(initial.summary.strip());
+            
+            if (!String.is_empty(initial.location))
+                location.append(initial.location.strip());
+            
+            if (event.is_all_day) {
+                start_date = event.date_span.start_date;
+                end_date = event.date_span.end_date;
+            } else {
+                start_date = event.exact_time_span.start_date;
+                start_time = event.exact_time_span.start_exact_time.to_wall_time();
+                start_time_strict = true;
+                
+                end_date = event.exact_time_span.end_date;
+                end_time = event.exact_time_span.end_exact_time.to_wall_time();
+                end_time_strict = true;
+            }
+        }
+        
         // tokenize the string and arrange as a stack for the parser
         string[] tokenized = String.reduce_whitespace(this.details).split(" ");
         Gee.LinkedList<Token> list = new Gee.LinkedList<Token>();
@@ -217,7 +249,10 @@ public class DetailsParser : BaseObject {
             event.location = location.str;
         
         // store full detail text in the event description for user and for debugging
-        event.description = details;
+        if (String.is_empty(event.description))
+            event.description = details;
+        else
+            event.description += "\n" + details;
     }
     
     private bool parse_time(Token? specifier, bool strict) {
diff --git a/src/component/component-event.vala b/src/component/component-event.vala
index adba976..ac47026 100644
--- a/src/component/component-event.vala
+++ b/src/component/component-event.vala
@@ -261,6 +261,62 @@ public class Event : Instance, Gee.Comparable<Event> {
     }
     
     /**
+     * Returns a prettified string describing the { link Event}'s time span in as concise and
+     * economical manner possible.
+     */
+    public string get_event_time_pretty_string(Calendar.Timezone timezone) {
+        // if any dates are not in current year, display year in all dates
+        Calendar.Date.PrettyFlag date_flags = Calendar.Date.PrettyFlag.NONE;
+        Calendar.DateSpan date_span = get_event_date_span(timezone);
+        if (!date_span.start_date.year.equal_to(Calendar.System.today.year)
+            || !date_span.end_date.year.equal_to(Calendar.System.today.year)) {
+            date_flags |= Calendar.Date.PrettyFlag.INCLUDE_YEAR;
+        }
+        
+        // span string is kinda tricky
+        string span;
+        if (is_all_day) {
+            if (date_span.is_same_day) {
+                // All-day one-day event, print that date's "<full date>", including year if not
+                // current year
+                span = date_span.start_date.to_pretty_string(date_flags);
+            } else {
+                // All-day event spanning days, print "<abbrev date> to <abbrev date>"
+                date_flags |= Calendar.Date.PrettyFlag.ABBREV;
+                // Prints a span of dates, i.e. "January 3 to January 6"
+                span = _("%s to %s").printf(date_span.start_date.to_pretty_string(date_flags),
+                    date_span.end_date.to_pretty_string(date_flags));
+            }
+        } else {
+            Calendar.ExactTimeSpan exact_time_span = exact_time_span.to_timezone(timezone);
+            if (exact_time_span.is_same_day) {
+                // A span of time, i.e. "3:30pm to 4:30pm"
+                string timespan = _("%s to %s").printf(
+                    
exact_time_span.start_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE),
+                    exact_time_span.end_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE));
+                
+                // Single-day timed event, print "<full date>, <full start time> to <full end time>",
+                // including year if not current year
+                span = "%s, %s".printf(exact_time_span.start_date.to_pretty_string(date_flags),
+                    timespan);
+            } else {
+                // Multi-day timed event, print "<full time>, <full date>" on both lines,
+                // including year if either not current year
+                // Prints two full time and date strings on separate lines, i.e.:
+                // 12 January 2012, 3:30pm
+                // 13 January 2013, 6:30am
+                span = _("%s, %s\n%s, %s").printf(
+                    exact_time_span.start_exact_time.to_pretty_date_string(date_flags),
+                    
exact_time_span.start_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE),
+                    exact_time_span.end_exact_time.to_pretty_date_string(date_flags),
+                    exact_time_span.end_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE));
+            }
+        }
+        
+        return span;
+    }
+    
+    /**
      * @inheritDoc
      */
     public override bool is_valid() {
diff --git a/src/host/host-create-update-event.vala b/src/host/host-create-update-event.vala
index 96d9674..6fbb06b 100644
--- a/src/host/host-create-update-event.vala
+++ b/src/host/host-create-update-event.vala
@@ -256,8 +256,6 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
             update_event_async.begin(null);
         else
             create_event_async.begin(null);
-        
-        notify_success();
     }
     
     [GtkCallback]
@@ -266,8 +264,11 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
     }
     
     private async void create_event_async(Cancellable? cancellable) {
-        if (event.calendar_source == null)
+        if (event.calendar_source == null) {
+            notify_failure(_("Unable to create event: calendar must be specified"));
+            
             return;
+        }
         
         try {
             yield event.calendar_source.create_component_async(event, cancellable);
@@ -279,8 +280,11 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
     
     // TODO: Delete from original source if not the same as the new source
     private async void update_event_async(Cancellable? cancellable) {
-        if (event.calendar_source == null)
+        if (event.calendar_source == null) {
+            notify_failure(_("Unable to update event: calendar must be specified"));
+            
             return;
+        }
         
         try {
             yield event.calendar_source.update_component_async(event, cancellable);
diff --git a/src/host/host-main-window.vala b/src/host/host-main-window.vala
index ddb084a..00aee5e 100644
--- a/src/host/host-main-window.vala
+++ b/src/host/host-main-window.vala
@@ -260,22 +260,7 @@ public class MainWindow : Gtk.ApplicationWindow {
     }
     
     private void on_quick_create_event() {
-        QuickCreateEvent quick_create = new QuickCreateEvent();
-        
-        quick_create.success.connect(() => {
-            if (quick_create.parsed_event == null)
-                return;
-            
-            if (quick_create.parsed_event.is_valid())
-                create_event_async.begin(quick_create.parsed_event, null);
-            else
-                create_event(quick_create.parsed_event, quick_add_button, null);
-        });
-        
-        Toolkit.Deck deck = new Toolkit.Deck();
-        deck.add_cards(iterate<Toolkit.Card>(quick_create).to_array_list());
-        
-        show_deck(quick_add_button, null, deck);
+        quick_create_event(null, quick_add_button, null);
     }
     
     private void on_jump_to_today() {
@@ -303,7 +288,7 @@ public class MainWindow : Gtk.ApplicationWindow {
         Component.Event event = new Component.Event.blank();
         event.set_event_exact_time_span(initial);
         
-        create_event(event, relative_to, for_location);
+        quick_create_event(event, relative_to, for_location);
     }
     
     private void on_request_create_all_day_event(Calendar.Span initial, Gtk.Widget relative_to,
@@ -311,31 +296,20 @@ public class MainWindow : Gtk.ApplicationWindow {
         Component.Event event = new Component.Event.blank();
         event.set_event_date_span(initial.to_date_span());
         
-        create_event(event, relative_to, for_location);
+        quick_create_event(event, relative_to, for_location);
     }
     
-    private void create_event(Component.Event event, Gtk.Widget relative_to, Gdk.Point? for_location) {
-        CreateUpdateEvent create_update_event = new CreateUpdateEvent();
-        create_update_event.is_update = false;
+    private void quick_create_event(Component.Event? initial, Gtk.Widget relative_to, Gdk.Point? 
for_location) {
+        QuickCreateEvent quick_create = new QuickCreateEvent(initial);
+        CreateUpdateEvent create_update = new CreateUpdateEvent();
+        create_update.is_update = false;
         
         Toolkit.Deck deck = new Toolkit.Deck();
-        deck.add_cards(iterate<Toolkit.Card>(create_update_event).to_array_list());
-        deck.go_home(event);
+        deck.add_cards(iterate<Toolkit.Card>(quick_create, create_update).to_array_list());
         
         show_deck(relative_to, for_location, deck);
     }
     
-    private async void create_event_async(Component.Event event, Cancellable? cancellable) {
-        if (event.calendar_source == null)
-            return;
-        
-        try {
-            yield event.calendar_source.create_component_async(event, cancellable);
-        } catch (Error err) {
-            debug("Unable to create event: %s", err.message);
-        }
-    }
-    
     private void on_request_display_event(Component.Event event, Gtk.Widget relative_to,
         Gdk.Point? for_location) {
         ShowEvent show_event = new ShowEvent();
diff --git a/src/host/host-quick-create-event.vala b/src/host/host-quick-create-event.vala
index af33921..609d490 100644
--- a/src/host/host-quick-create-event.vala
+++ b/src/host/host-quick-create-event.vala
@@ -14,16 +14,25 @@ public class QuickCreateEvent : Gtk.Grid, Toolkit.Card {
     
     public string? title { get { return null; } }
     
-    public Component.Event? parsed_event { get; private set; default = null; }
+    public new Component.Event? event { get; private set; default = null; }
     
     public Gtk.Widget? default_widget { get { return create_button; } }
     
     public Gtk.Widget? initial_focus { get { return details_entry; } }
     
     [GtkChild]
+    private Gtk.Box when_box;
+    
+    [GtkChild]
+    private Gtk.Label when_text_label;
+    
+    [GtkChild]
     private Gtk.Entry details_entry;
     
     [GtkChild]
+    private Gtk.Label example_label;
+    
+    [GtkChild]
     private Gtk.ComboBoxText calendar_combo_box;
     
     [GtkChild]
@@ -31,7 +40,26 @@ public class QuickCreateEvent : Gtk.Grid, Toolkit.Card {
     
     private Toolkit.ComboBoxTextModel<Backing.CalendarSource> model;
     
-    public QuickCreateEvent() {
+    public QuickCreateEvent(Component.Event? initial) {
+        event = initial;
+        
+        // if initial date/times supplied, reveal to the user and change the example
+        string eg;
+        if (initial != null && (initial.date_span != null || initial.exact_time_span != null)) {
+            when_box.visible = true;
+            when_text_label.label = initial.get_event_time_pretty_string(Calendar.Timezone.local);
+            if (initial.date_span != null)
+                eg = _("Example: Dinner at Tadich Grill 7:30pm");
+            else
+                eg = _("Example: Dinner at Tadich Grill");
+        } else {
+            when_box.visible = false;
+            when_box.no_show_all = true;
+            eg = _("Example: Dinner at Tadich Grill 7:30pm tomorrow");
+        }
+
+        example_label.label = "<small><i>%s</i></small>".printf(eg);
+        
         // create and initialize combo box model
         model = new Toolkit.ComboBoxTextModel<Backing.CalendarSource>(calendar_combo_box,
             (cal) => cal.title);
@@ -47,6 +75,7 @@ public class QuickCreateEvent : Gtk.Grid, Toolkit.Card {
         // make first item active
         calendar_combo_box.active = 0;
         
+        // enable create button only when the user has typed something
         details_entry.bind_property("text-length", create_button, "sensitive", BindingFlags.SYNC_CREATE,
             xform_text_length_to_sensitive);
     }
@@ -75,10 +104,29 @@ public class QuickCreateEvent : Gtk.Grid, Toolkit.Card {
     
     [GtkCallback]
     private void on_create_button_clicked() {
-        Component.DetailsParser parser = new Component.DetailsParser(details_entry.text, model.active);
-        parsed_event = parser.event;
+        Component.DetailsParser parser = new Component.DetailsParser(details_entry.text, model.active,
+            event);
+        event = parser.event;
         
-        notify_success();
+        if (event.is_valid())
+            create_event_async.begin(null);
+        else
+            jump_to_card_by_name(CreateUpdateEvent.ID, event);
+    }
+    
+    private async void create_event_async(Cancellable? cancellable) {
+        if (event.calendar_source == null) {
+            notify_failure(_("Unable to create event: calendar must be specified"));
+            
+            return;
+        }
+        
+        try {
+            yield event.calendar_source.create_component_async(event, cancellable);
+            notify_success();
+        } catch (Error err) {
+            notify_failure(_("Unable to create event: %s").printf(err.message));
+        }
     }
 }
 
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index 9bc4a02..67cece1 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -67,55 +67,8 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
         if (!String.is_empty(event.description))
             add_lf_lf(builder).append_printf("%s", Markup.escape_text(event.description));
         
-        // if any dates are not in current year, display year in all dates
-        Calendar.Date.PrettyFlag date_flags = Calendar.Date.PrettyFlag.NONE;
-        Calendar.DateSpan date_span = event.get_event_date_span(Calendar.Timezone.local);
-        if (!date_span.start_date.year.equal_to(Calendar.System.today.year)
-            || !date_span.end_date.year.equal_to(Calendar.System.today.year)) {
-            date_flags |= Calendar.Date.PrettyFlag.INCLUDE_YEAR;
-        }
-        
-        // span string is kinda tricky
-        string span;
-        if (event.is_all_day) {
-            if (date_span.is_same_day) {
-                // All-day one-day event, print that date's "<full date>", including year if not
-                // current year
-                span = date_span.start_date.to_pretty_string(date_flags);
-            } else {
-                // All-day event spanning days, print "<abbrev date> to <abbrev date>"
-                date_flags |= Calendar.Date.PrettyFlag.ABBREV;
-                // Prints a span of dates, i.e. "January 3 to January 6"
-                span = _("%s to %s").printf(date_span.start_date.to_pretty_string(date_flags),
-                    date_span.end_date.to_pretty_string(date_flags));
-            }
-        } else {
-            Calendar.ExactTimeSpan exact_time_span = event.exact_time_span.to_timezone(
-                Calendar.Timezone.local);
-            if (exact_time_span.is_same_day) {
-                // Single-day timed event, print "<full date>\n<full start time> to <full end time>",
-                // including year if not current year
-                // Prints a span of time, i.e. "3:30pm to 4:30pm"
-                string timespan = _("%s to %s").printf(
-                    
exact_time_span.start_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE),
-                    exact_time_span.end_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE));
-                span = "%s\n%s".printf(exact_time_span.start_date.to_pretty_string(date_flags),
-                    timespan);
-            } else {
-                // Multi-day timed event, print "<full time>, <full date>" on both lines,
-                // including year if either not current year
-                // Prints two full time and date strings on separate lines, i.e.:
-                // 12 January 2012, 3:30pm
-                // 13 January 2013, 6:30am
-                span = _("%s, %s\n%s, %s").printf(
-                    exact_time_span.start_exact_time.to_pretty_date_string(date_flags),
-                    
exact_time_span.start_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE),
-                    exact_time_span.end_exact_time.to_pretty_date_string(date_flags),
-                    exact_time_span.end_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE));
-            }
-        }
-        
-        add_lf_lf(builder).append_printf("<small>%s</small>", Markup.escape_text(span));
+        add_lf_lf(builder).append_printf("<small>%s</small>",
+            Markup.escape_text(event.get_event_time_pretty_string(Calendar.Timezone.local)));
         
         text_label.label = builder.str;
         
diff --git a/src/rc/quick-create-event.ui b/src/rc/quick-create-event.ui
index 827d36d..d717926 100644
--- a/src/rc/quick-create-event.ui
+++ b/src/rc/quick-create-event.ui
@@ -10,8 +10,9 @@
       <object class="GtkLabel" id="title_label">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
+        <property name="margin_bottom">8</property>
         <property name="xalign">0</property>
-        <property name="label" translatable="yes">_Quick add event:</property>
+        <property name="label" translatable="yes">_Quick add event</property>
         <property name="use_underline">True</property>
         <property name="mnemonic_widget">details_entry</property>
         <attributes>
@@ -29,7 +30,9 @@
       <object class="GtkButtonBox" id="buttonbox1">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
+        <property name="valign">end</property>
         <property name="margin_top">8</property>
+        <property name="vexpand">True</property>
         <property name="spacing">8</property>
         <property name="homogeneous">True</property>
         <property name="layout_style">end</property>
@@ -71,6 +74,19 @@
       </object>
       <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>
+      <object class="GtkComboBoxText" id="calendar_combo_box">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="margin_top">8</property>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
         <property name="top_attach">3</property>
         <property name="width">1</property>
         <property name="height">1</property>
@@ -102,9 +118,12 @@
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <property name="xalign">0</property>
-            <property name="label" translatable="yes">&lt;small&gt;&lt;i&gt;Example: Dinner at Tadich Grill 
7:30pm tomorrow&lt;/i&gt;&lt;/small&gt;</property>
+            <property name="label">(empty)</property>
             <property name="use_markup">True</property>
             <property name="ellipsize">start</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
           </object>
           <packing>
             <property name="expand">False</property>
@@ -115,25 +134,24 @@
       </object>
       <packing>
         <property name="left_attach">0</property>
-        <property name="top_attach">1</property>
+        <property name="top_attach">2</property>
         <property name="width">1</property>
         <property name="height">1</property>
       </packing>
     </child>
     <child>
-      <object class="GtkBox" id="box2">
+      <object class="GtkBox" id="when_box">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
         <property name="spacing">8</property>
         <child>
-          <object class="GtkLabel" id="calendar_label">
+          <object class="GtkLabel" id="when_label">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="xalign">0</property>
-            <property name="label" translatable="yes">Ca_lendar:</property>
-            <property name="use_underline">True</property>
-            <property name="mnemonic_widget">calendar_combo_box</property>
-            <property name="ellipsize">start</property>
+            <property name="label" translatable="yes">When</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
           </object>
           <packing>
             <property name="expand">False</property>
@@ -142,12 +160,15 @@
           </packing>
         </child>
         <child>
-          <object class="GtkComboBoxText" id="calendar_combo_box">
+          <object class="GtkLabel" id="when_text_label">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
+            <property name="hexpand">True</property>
+            <property name="xalign">0</property>
+            <property name="label">(empty)</property>
           </object>
           <packing>
-            <property name="expand">True</property>
+            <property name="expand">False</property>
             <property name="fill">True</property>
             <property name="position">1</property>
           </packing>
@@ -155,7 +176,7 @@
       </object>
       <packing>
         <property name="left_attach">0</property>
-        <property name="top_attach">2</property>
+        <property name="top_attach">1</property>
         <property name="width">1</property>
         <property name="height">1</property>
       </packing>
diff --git a/src/view/month/month-grid.vala b/src/view/month/month-grid.vala
index 497e4c9..8203f62 100644
--- a/src/view/month/month-grid.vala
+++ b/src/view/month/month-grid.vala
@@ -405,18 +405,8 @@ private class Grid : Gtk.Grid {
         if (release_cell.date == null)
             return true;
         
-        // TODO: Define default time better
-        Calendar.ExactTime start;
-        if(release_cell.date.equal_to(Calendar.System.today)) {
-            start = new Calendar.ExactTime.now(Calendar.Timezone.local);
-        } else {
-            start = new Calendar.ExactTime(Calendar.Timezone.local, release_cell.date,
-                new Calendar.WallTime(13, 0, 0));
-        }
-        
-        Calendar.ExactTime end = start.adjust_time(1, Calendar.TimeUnit.HOUR);
-        
-        owner.request_create_timed_event(new Calendar.ExactTimeSpan(start, end), release_cell, 
release_point);
+        owner.request_create_all_day_event(new Calendar.DateSpan(press_cell.date, release_cell.date),
+            release_cell, release_point);
         
         // stop propagation
         return true;


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