[california] Unify Show Event and Create/Update Event dialogs



commit 09e678137541b65beb9c38dd37401f2cd49f8a51
Author: Jim Nelson <jim yorba org>
Date:   Wed Apr 23 17:08:29 2014 -0700

    Unify Show Event and Create/Update Event dialogs

 src/component/component-instance.vala  |    2 +-
 src/host/host-create-update-event.vala |  177 +++++++++++++++-----------------
 src/host/host-main-window.vala         |   61 ++++--------
 src/host/host-show-event.vala          |   17 ++--
 src/rc/create-update-event.ui          |   47 ++++-----
 src/toolkit/toolkit-card.vala          |   10 ++
 src/toolkit/toolkit-deck.vala          |   33 ++++++
 7 files changed, 179 insertions(+), 168 deletions(-)
---
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index 8c56076..f5c62ba 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -168,7 +168,7 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
     }
     
     /**
-     * Updates the { link Instance} with information from the E.CalComponent.
+     * Updates the { link Instance} with information from the iCal component.
      *
      * The Instance will update whatever changes it discovers from this new component and fire
      * signals to update subscribers.
diff --git a/src/host/host-create-update-event.vala b/src/host/host-create-update-event.vala
index b77aacf..fa43c28 100644
--- a/src/host/host-create-update-event.vala
+++ b/src/host/host-create-update-event.vala
@@ -54,61 +54,67 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
     
     public Calendar.DateSpan selected_date_span { get; set; }
     
-    private new Component.Event event;
+    public bool is_update { get; set; default = false; }
+    
+    private new Component.Event event = new Component.Event.blank();
     private Gee.HashMap<string, Calendar.WallTime> time_map = new Gee.HashMap<string, Calendar.WallTime>();
-    private Gee.List<Backing.CalendarSource> calendar_sources;
     private Backing.CalendarSource? original_calendar_source;
-    private Backing.CalendarSource? selected_calendar_source;
-    private bool is_update = false;
+    private Toolkit.ComboBoxTextModel<Backing.CalendarSource> calendar_model;
     private Gtk.Button? last_date_button_touched = null;
     private bool both_date_buttons_touched = false;
     
-    public signal void create_event(Component.Event event);
-    
-    public signal void update_event(Backing.CalendarSource? original_source, Component.Event event);
-    
-    public CreateUpdateEvent(Calendar.ExactTimeSpan initial) {
-        event = new Component.Event.blank();
-        event.set_event_exact_time_span(initial);
-        original_calendar_source = null;
+    public CreateUpdateEvent() {
+        // when selected_date_span updates, update date buttons as well
+        notify[PROP_SELECTED_DATE_SPAN].connect(() => {
+            dtstart_date_button.label = selected_date_span.start_date.to_standard_string();
+            dtend_date_button.label = selected_date_span.end_date.to_standard_string();
+        });
         
-        init();
-    }
-    
-    public CreateUpdateEvent.all_day(Calendar.DateSpan initial) {
-        event = new Component.Event.blank();
-        event.set_event_date_span(initial);
-        original_calendar_source = null;
+        // create button is active only if summary is filled out; all other fields (so far)
+        // guarantee valid values at all times
+        summary_entry.bind_property("text-length", accept_button, "sensitive",
+            BindingFlags.SYNC_CREATE);
         
-        init();
-    }
-    
-    public CreateUpdateEvent.update(Component.Event event) {
-        this.event = event;
-        original_calendar_source = event.calendar_source;
+        // hide start/end time widgets if an all-day event ..."no-show-all" needed to avoid the
+        // merciless effects of show_all()
+        all_day_toggle.bind_property("active", dtstart_time_combo, "visible",
+            BindingFlags.INVERT_BOOLEAN | BindingFlags.SYNC_CREATE);
+        dtstart_time_combo.no_show_all = true;
+        all_day_toggle.bind_property("active", dtend_time_combo, "visible",
+            BindingFlags.INVERT_BOOLEAN | BindingFlags.SYNC_CREATE);
+        dtend_time_combo.no_show_all = true;
         
-        accept_button.label = _("_Update");
-        is_update = true;
+        // use model to control calendars combo box
+        calendar_model = new Toolkit.ComboBoxTextModel<Backing.CalendarSource>(calendar_combo,
+            (cal) => cal.title);
+        foreach (Backing.CalendarSource calendar_source in
+            Backing.Manager.instance.get_sources_of_type<Backing.CalendarSource>()) {
+            if (!calendar_source.visible)
+                continue;
+            
+            calendar_model.add(calendar_source);
+        }
         
-        init();
+        update_controls();
     }
     
-    public CreateUpdateEvent.finish(Component.Event event) {
-        this.event = event;
+    public void jumped_to(Toolkit.Card? from, Value? message) {
+        if (message != null) {
+            event = message as Component.Event;
+            assert(event != null);
+        } else {
+            event = new Component.Event.blank();
+        }
         
-        init();
+        update_controls();
     }
     
-    private void init() {
+    private void update_controls() {
         if (event.summary != null)
             summary_entry.text = event.summary;
+        else
+            summary_entry.text = "";
         
-        notify[PROP_SELECTED_DATE_SPAN].connect(() => {
-            dtstart_date_button.label = selected_date_span.start_date.to_standard_string();
-            dtend_date_button.label = selected_date_span.end_date.to_standard_string();
-        });
-        
-        // date/date-time must be set in the Event prior to this call
         Calendar.WallTime initial_start_time, initial_end_time;
         if (event.exact_time_span != null) {
             all_day_toggle.active = false;
@@ -131,7 +137,7 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
                 Calendar.System.now.adjust_time(1, Calendar.TimeUnit.HOUR));
         }
         
-        // initialize start and end time (as in, wall clock time)
+        // initialize start and end time controls (as in, wall clock time)
         Calendar.WallTime current = new Calendar.WallTime(START_HOUR, Calendar.WallTime.MIN_MINUTE, 0);
         Calendar.WallTime end = new Calendar.WallTime(END_HOUR, Calendar.WallTime.MAX_MINUTE, 0);
         int index = 0;
@@ -168,57 +174,16 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
         dtstart_time_combo.set_active(dtstart_active_index.clamp(0, index - 1));
         dtend_time_combo.set_active(dtend_active_index.clamp(0, index - 1));
         
-        // hide start/end time widgets if an all-day event ..."no-show-all" needed to avoid the
-        // merciless effects of show_all()
-        all_day_toggle.bind_property("active", dtstart_time_combo, "visible",
-            BindingFlags.INVERT_BOOLEAN | BindingFlags.SYNC_CREATE);
-        dtstart_time_combo.no_show_all = true;
-        all_day_toggle.bind_property("active", dtend_time_combo, "visible",
-            BindingFlags.INVERT_BOOLEAN | BindingFlags.SYNC_CREATE);
-        dtend_time_combo.no_show_all = true;
-        
-        // initialize available calendars
-        calendar_sources = Backing.Manager.instance.get_sources_of_type<Backing.CalendarSource>();
-        calendar_sources.sort();
-        
-        index = 0;
-        int calendar_source_index = 0;
-        Gee.Iterator<Backing.Source> iter = calendar_sources.iterator();
-        while (iter.next()) {
-            Backing.Source source = iter.get();
-            
-            // drop from List to ensure ComboBox indices match array indices
-            if (!source.visible) {
-                iter.remove();
-                
-                continue;
-            }
-            
-            calendar_combo.append_text(source.title);
-            if (source == event.calendar_source)
-                calendar_source_index = index;
-            
-            index++;
+        // set combo to event's calendar
+        if (event.calendar_source != null) {
+            calendar_model.set_item_active(event.calendar_source);
+        } else {
+            calendar_combo.active = 0;
+            is_update = false;
         }
         
-        // keep attribute up-to-date
-        calendar_combo.notify["active"].connect(() => {
-            if (calendar_combo.active >= 0 && calendar_combo.active < calendar_sources.size)
-                selected_calendar_source = calendar_sources[calendar_combo.active];
-            else
-                selected_calendar_source = null;
-        });
-        
-        // set now that handlers are in place
-        calendar_combo.set_active(calendar_source_index);
-        
-        // create button is active only if summary is filled out; all other fields (so far)
-        // guarantee valid values at all times
-        summary_entry.bind_property("text-length", accept_button, "sensitive",
-            BindingFlags.SYNC_CREATE);
-    }
-    
-    public void jumped_to(Toolkit.Card? from, Value? message) {
+        accept_button.label = is_update ? _("_Update") : _("C_reate");
+        original_calendar_source = event.calendar_source;
     }
     
     [GtkCallback]
@@ -260,10 +225,10 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
     
     [GtkCallback]
     private void on_accept_clicked() {
-        if (selected_calendar_source == null)
+        if (calendar_model.active == null)
             return;
         
-        event.calendar_source = selected_calendar_source;
+        event.calendar_source = calendar_model.active;
         event.summary = summary_entry.text;
         
         if (all_day_toggle.active) {
@@ -284,17 +249,43 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
         }
         
         if (is_update)
-            update_event(original_calendar_source, event);
+            update_event_async.begin(null);
         else
-            create_event(event);
+            create_event_async.begin(null);
         
         notify_success();
     }
     
     [GtkCallback]
     private void on_cancel_button_clicked() {
-        notify_user_closed();
+        jump_home_or_user_closed();
+    }
+    
+    private async void create_event_async(Cancellable? cancellable) {
+        if (event.calendar_source == null)
+            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));
+        }
     }
+    
+    // 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)
+            return;
+        
+        try {
+            yield event.calendar_source.update_component_async(event, cancellable);
+            notify_success();
+        } catch (Error err) {
+            notify_failure(_("Unable to update event: %s").printf(err.message));
+        }
+    }
+    
 }
 
 }
diff --git a/src/host/host-main-window.vala b/src/host/host-main-window.vala
index a4eec0d..1d2922e 100644
--- a/src/host/host-main-window.vala
+++ b/src/host/host-main-window.vala
@@ -153,7 +153,7 @@ public class MainWindow : Gtk.ApplicationWindow {
             if (quick_create.parsed_event.is_valid())
                 create_event_async.begin(quick_create.parsed_event, null);
             else
-                create_event(null, null, quick_create.parsed_event, true, quick_add_button, null);
+                create_event(quick_create.parsed_event, quick_add_button, null);
         });
         
         Toolkit.Deck deck = new Toolkit.Deck();
@@ -176,39 +176,27 @@ public class MainWindow : Gtk.ApplicationWindow {
     
     private void on_request_create_timed_event(Calendar.ExactTimeSpan initial, Gtk.Widget relative_to,
         Gdk.Point? for_location) {
-        create_event(initial, null, null, false, relative_to, for_location);
+        Component.Event event = new Component.Event.blank();
+        event.set_event_exact_time_span(initial);
+        
+        create_event(event, relative_to, for_location);
     }
     
     private void on_request_create_all_day_event(Calendar.DateSpan initial, Gtk.Widget relative_to,
         Gdk.Point? for_location) {
-        create_event(null, initial, null, false, relative_to, for_location);
+        Component.Event event = new Component.Event.blank();
+        event.set_event_date_span(initial);
+        
+        create_event(event, relative_to, for_location);
     }
     
-    private void create_event(Calendar.ExactTimeSpan? time_span, Calendar.DateSpan? date_span,
-        Component.Event? existing, bool create_existing, Gtk.Widget relative_to, Gdk.Point? for_location) {
-        assert(time_span != null || date_span != null || existing != null);
-        
-        CreateUpdateEvent create_update_event;
-        if (time_span != null)
-            create_update_event = new CreateUpdateEvent(time_span);
-        else if (date_span != null)
-            create_update_event = new CreateUpdateEvent.all_day(date_span);
-        else if (create_existing)
-            create_update_event = new CreateUpdateEvent.finish(existing);
-        else
-            create_update_event = new CreateUpdateEvent.update(existing);
-        
-        create_update_event.create_event.connect((event) => {
-            create_event_async.begin(event, null);
-        });
-        
-        create_update_event.update_event.connect((original_source, event) => {
-            // TODO: Delete from original source if not the same as the new source
-            update_event_async.begin(event, null);
-        });
+    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;
         
         Toolkit.Deck deck = new Toolkit.Deck();
         deck.add_cards(iterate<Toolkit.Card>(create_update_event).to_array_list());
+        deck.go_home(event);
         
         show_deck(relative_to, for_location, deck);
     }
@@ -224,31 +212,20 @@ public class MainWindow : Gtk.ApplicationWindow {
         }
     }
     
-    private async void update_event_async(Component.Event event, Cancellable? cancellable) {
-        if (event.calendar_source == null)
-            return;
-        
-        try {
-            yield event.calendar_source.update_component_async(event, cancellable);
-        } catch (Error err) {
-            debug("Unable to update 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(event);
-        
+        ShowEvent show_event = new ShowEvent();
         show_event.remove_event.connect(() => {
             remove_event_async.begin(event, null);
         });
         
-        show_event.update_event.connect(() => {
-            create_event(null, null, event, false, relative_to, for_location);
-        });
+        CreateUpdateEvent create_update_event = new CreateUpdateEvent();
+        create_update_event.is_update = true;
         
         Toolkit.Deck deck = new Toolkit.Deck();
-        deck.add_cards(iterate<Toolkit.Card>(show_event).to_array_list());
+        deck.add_card(show_event);
+        deck.add_card(create_update_event);
+        deck.go_home(event);
         
         show_deck(relative_to, for_location, deck);
     }
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index 9091e98..963149e 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -34,12 +34,7 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
     
     public signal void remove_event(Component.Event event);
     
-    public signal void update_event(Component.Event event);
-    
-    public ShowEvent(Component.Event event) {
-        this.event = event;
-        
-        build_display();
+    public ShowEvent() {
         Calendar.System.instance.is_24hr_changed.connect(build_display);
         Calendar.System.instance.today_changed.connect(build_display);
     }
@@ -50,6 +45,13 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
     }
     
     public void jumped_to(Toolkit.Card? from, Value? message) {
+        if (message == null)
+            return;
+        
+        event = message as Component.Event;
+        assert(event != null);
+        
+        build_display();
     }
     
     private void build_display() {
@@ -143,8 +145,7 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
     
     [GtkCallback]
     private void on_update_button_clicked() {
-        update_event(event);
-        notify_success();
+        jump_to_card_by_name(CreateUpdateEvent.ID, event);
     }
     
     [GtkCallback]
diff --git a/src/rc/create-update-event.ui b/src/rc/create-update-event.ui
index d785f96..c1890ff 100644
--- a/src/rc/create-update-event.ui
+++ b/src/rc/create-update-event.ui
@@ -18,9 +18,9 @@
         <property name="can_focus">True</property>
         <property name="has_focus">True</property>
         <property name="is_focus">True</property>
-        <property name="max_length">64</property>
+        <property name="max_length">128</property>
         <property name="activates_default">True</property>
-        <property name="width_chars">24</property>
+        <property name="width_chars">48</property>
         <property name="caps_lock_warning">False</property>
         <property name="placeholder_text" translatable="yes">Untitled event</property>
       </object>
@@ -121,25 +121,6 @@
       </packing>
     </child>
     <child>
-      <object class="GtkCheckButton" id="all_day_toggle">
-        <property name="label" translatable="yes">_All-day event</property>
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="receives_default">False</property>
-        <property name="halign">end</property>
-        <property name="use_underline">True</property>
-        <property name="xalign">0</property>
-        <property name="image_position">right</property>
-        <property name="draw_indicator">True</property>
-      </object>
-      <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>
       <object class="GtkBox" id="calendar_box">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
@@ -180,12 +161,11 @@
       <object class="GtkButtonBox" id="button_box">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
-        <property name="halign">baseline</property>
-        <property name="valign">baseline</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="baseline_position">bottom</property>
         <property name="layout_style">end</property>
         <child>
           <object class="GtkButton" id="cancel_button">
@@ -235,6 +215,25 @@
       </packing>
     </child>
     <child>
+      <object class="GtkCheckButton" id="all_day_toggle">
+        <property name="label" translatable="yes">_All-day event</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">False</property>
+        <property name="halign">start</property>
+        <property name="use_underline">True</property>
+        <property name="xalign">0</property>
+        <property name="image_position">right</property>
+        <property name="draw_indicator">True</property>
+      </object>
+      <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>
       <placeholder/>
     </child>
   </template>
diff --git a/src/toolkit/toolkit-card.vala b/src/toolkit/toolkit-card.vala
index e192943..158a8a3 100644
--- a/src/toolkit/toolkit-card.vala
+++ b/src/toolkit/toolkit-card.vala
@@ -160,6 +160,16 @@ public interface Card : Gtk.Widget {
         dismiss(true, false);
         failure(user_message);
     }
+    
+    /**
+     * Jump home or, if this { link Card} is the home card, dismiss { link Deck}.
+     */
+    protected void jump_home_or_user_closed() {
+        if (deck.home == this)
+            notify_user_closed();
+        else
+            jump_home();
+    }
 }
 
 }
diff --git a/src/toolkit/toolkit-deck.vala b/src/toolkit/toolkit-deck.vala
index 306ba85..c8b6803 100644
--- a/src/toolkit/toolkit-deck.vala
+++ b/src/toolkit/toolkit-deck.vala
@@ -15,6 +15,11 @@ namespace California.Toolkit {
 
 public class Deck : Gtk.Stack {
     /**
+     * A slightly slower transition duration than default.
+     */
+    public const int DEFAULT_TRANSITION_MSEC = 300;
+    
+    /**
      * @inheritedDoc
      */
     public Gtk.Widget? default_widget { get { return null; } }
@@ -67,6 +72,8 @@ public class Deck : Gtk.Stack {
      */
     public Deck() {
         transition_type = Gtk.StackTransitionType.SLIDE_LEFT_RIGHT;
+        transition_duration = DEFAULT_TRANSITION_MSEC;
+        
         notify["visible-child"].connect(on_child_to_top);
     }
     
@@ -104,6 +111,13 @@ public class Deck : Gtk.Stack {
     }
     
     /**
+     * A helper method for { link add_cards}.
+     */
+    public void add_card(Card card) {
+        add_cards(iterate<Card>(card).to_array_list());
+    }
+    
+    /**
      * Add { link Card}s to the { link Deck}.
      *
      * Cards can be added in multiple batches, but the ordering is important as it dictates how
@@ -182,6 +196,25 @@ public class Deck : Gtk.Stack {
         }
     }
     
+    /**
+     * Force the { link Deck} to jump to the { link home} { link Card}.
+     *
+     * In general, Deck avoids jumping to a Card if it's already displayed (on top).  However, for
+     * this call it will call the Card's { link Card.jumped_to} method and pass the supplied
+     * message every time, even if already on top.  This allows for this call to be used for Deck
+     * initialization.
+     */
+    public void go_home(Value? message) {
+        if (home == null)
+            return;
+        
+        // clear navigation stack, this acts as a kind of reset
+        navigation_stack.clear();
+        
+        set_visible_child(home);
+        home.jumped_to(null, message);
+    }
+    
     private void on_jump_to_card(Card card, Card next, Value? message) {
         // do nothing if already visible
         if (get_visible_child() == next) {


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