[california] Set and honor default calendar: Bug #725781



commit d82f0476cdb33d32dbd5f43715f31d8603971c64
Author: Jim Nelson <jim yorba org>
Date:   Wed Oct 22 15:24:30 2014 -0700

    Set and honor default calendar: Bug #725781
    
    California now honors EDS' default calendar by listing it first
    when creating a new event.  Default is displayed in Calendar Manager
    and can be changed by the user.

 src/backing/backing-calendar-source.vala      |   21 +++++++++
 src/backing/backing-store.vala                |   13 ++++++
 src/backing/eds/backing-eds-store.vala        |   49 ++++++++++++++++++++--
 src/host/host-create-update-event.vala        |    2 +-
 src/host/host-quick-create-event.vala         |    4 +-
 src/host/host.vala                            |   10 ++++
 src/manager/manager-calendar-list-item.vala   |   28 ++++++++++++
 src/rc/calendar-manager-list-item.ui          |   56 +++++++++++++++++-------
 src/toolkit/toolkit-combo-box-text-model.vala |   46 ++++++++++++++++++++
 9 files changed, 205 insertions(+), 24 deletions(-)
---
diff --git a/src/backing/backing-calendar-source.vala b/src/backing/backing-calendar-source.vala
index aa5755d..bc27ed0 100644
--- a/src/backing/backing-calendar-source.vala
+++ b/src/backing/backing-calendar-source.vala
@@ -18,6 +18,8 @@ namespace California.Backing {
  */
 
 public abstract class CalendarSource : Source {
+    public const string PROP_IS_DEFAULT = "is-default";
+    
     /**
      * The affected range of a removal operation.
      *
@@ -39,8 +41,27 @@ public abstract class CalendarSource : Source {
         ALL
     }
     
+    /**
+     * Indicates this { link CalendarSource} is the default calendar for its { link Backing.Store}.
+     *
+     * Thus, if/when there are more than one Backing.Store, there can be more than one default
+     * CalendarSource.
+     */
+    public bool is_default { get; private set; default = false; }
+    
     protected CalendarSource(Store store, string id, string title) {
         base (store, id, title);
+        
+        // watch store for change to default calendar
+        store.notify[Store.PROP_DEFAULT_CALENDAR].connect(on_store_default_calendar_changed);
+    }
+    
+    ~CalendarSource() {
+        store.notify[Store.PROP_DEFAULT_CALENDAR].disconnect(on_store_default_calendar_changed);
+    }
+    
+    private void on_store_default_calendar_changed() {
+        is_default = store.default_calendar == this;
     }
     
     /**
diff --git a/src/backing/backing-store.vala b/src/backing/backing-store.vala
index e29d3a9..bdd9194 100644
--- a/src/backing/backing-store.vala
+++ b/src/backing/backing-store.vala
@@ -14,6 +14,7 @@ namespace California.Backing {
 
 public abstract class Store : BaseObject {
     public const string PROP_IS_OPEN = "is-open";
+    public const string PROP_DEFAULT_CALENDAR = "default-calendar";
     
     /**
      * Set when the { link Store} is opened.
@@ -22,6 +23,11 @@ public abstract class Store : BaseObject {
      */
     public bool is_open { get; protected set; default = false; }
     
+    /**
+     * Set to the default { link CalendarSource} for this { link Store}.
+     */
+    public CalendarSource? default_calendar { get; protected set; default = null; }
+    
     private string desc;
     
     /**
@@ -114,6 +120,13 @@ public abstract class Store : BaseObject {
         return result;
     }
     
+    /**
+     * Set the { link CalendarSource} to the default for this { link Store}.
+     *
+     * @throws { link BackingError.MISMATCH} if CalendarSource did not originate from this store.
+     */
+    public abstract void make_default_calendar(Backing.CalendarSource calendar) throws Error;
+    
     public override string to_string() {
         return desc;
     }
diff --git a/src/backing/eds/backing-eds-store.vala b/src/backing/eds/backing-eds-store.vala
index 8c192a4..e4cdb44 100644
--- a/src/backing/eds/backing-eds-store.vala
+++ b/src/backing/eds/backing-eds-store.vala
@@ -30,6 +30,10 @@ internal class EdsStore : Store, WebCalSubscribable, CalDAVSubscribable {
         registry.source_added.connect(eds_source => add_eds_source_async.begin(eds_source));
         registry.source_removed.connect(eds_source => remove_eds_source(eds_source));
         
+        // watch for external changes of the default calendar and use handler to initialize
+        registry.notify["default-calendar"].connect(on_default_calendar_changed);
+        on_default_calendar_changed();
+        
         is_open = true;
     }
     
@@ -43,12 +47,33 @@ internal class EdsStore : Store, WebCalSubscribable, CalDAVSubscribable {
         is_open = false;
     }
     
+    private void check_open() throws BackingError {
+        if (!is_open)
+            throw new BackingError.UNAVAILABLE("EDS not open");
+    }
+    
+    private void on_default_calendar_changed() {
+        // EDS has a habit of issue property notifications in background threads, so ensure this
+        // property change happens in the foreground thread
+        Idle.add(() => {
+            Backing.CalendarSource? new_default_calendar = sources[registry.default_calendar]
+                as Backing.CalendarSource;
+            if (default_calendar == new_default_calendar)
+                return false;
+            
+            default_calendar = new_default_calendar;
+            
+            debug("Default EDS calendar: %s", (default_calendar != null) ? default_calendar.title : 
"(none)");
+            
+            return false;
+        });
+    }
+    
     /**
      * @inheritDoc
      */
     public override async void remove_source_async(Source source, Cancellable? cancellable) throws Error {
-        if (!is_open)
-            throw new BackingError.UNAVAILABLE("EDS not open");
+        check_open();
         
         if (source.store != this) {
             throw new BackingError.INVALID("Attempted to remove source %s from wrong store %s",
@@ -101,8 +126,7 @@ internal class EdsStore : Store, WebCalSubscribable, CalDAVSubscribable {
     
     private async void subscribe_eds_async(string title, Soup.URI uri, string? username, string color,
         string backend_name, Cancellable? cancellable) throws Error {
-        if (!is_open)
-            throw new BackingError.UNAVAILABLE("EDS not open");
+        check_open();
         
         E.Source scratch = new E.Source(null, null);
         // Surprise -- Google gets special treatment
@@ -154,6 +178,9 @@ internal class EdsStore : Store, WebCalSubscribable, CalDAVSubscribable {
         yield registry.create_sources(sources, cancellable);
     }
     
+    /**
+     * @inheritDoc
+     */
     public override Gee.List<Source> get_sources() {
         Gee.List<Source> list = new Gee.ArrayList<Source>();
         list.add_all(sources.values.read_only_view);
@@ -201,6 +228,20 @@ internal class EdsStore : Store, WebCalSubscribable, CalDAVSubscribable {
         if (calendar != null)
             calendar.close_async.begin(null);
     }
+    
+    /**
+     * @inheritDoc
+     */
+    public override void make_default_calendar(Backing.CalendarSource calendar) throws Error {
+        check_open();
+        
+        Backing.EdsCalendarSource? eds_calendar = calendar as Backing.EdsCalendarSource;
+        if (eds_calendar == null)
+            throw new BackingError.MISMATCH("Not an EDS calendar source");
+        
+        if (registry.default_calendar != eds_calendar.eds_source)
+            registry.default_calendar = eds_calendar.eds_source;
+    }
 }
 
 }
diff --git a/src/host/host-create-update-event.vala b/src/host/host-create-update-event.vala
index 300968e..561d91e 100644
--- a/src/host/host-create-update-event.vala
+++ b/src/host/host-create-update-event.vala
@@ -151,7 +151,7 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
         if (event.calendar_source != null) {
             calendar_model.set_item_active(event.calendar_source);
         } else {
-            calendar_combo.active = 0;
+            calendar_model.set_item_default_active();
             is_update = false;
         }
         
diff --git a/src/host/host-quick-create-event.vala b/src/host/host-quick-create-event.vala
index 2624cc5..f55721a 100644
--- a/src/host/host-quick-create-event.vala
+++ b/src/host/host-quick-create-event.vala
@@ -87,8 +87,8 @@ public class QuickCreateEvent : Gtk.Grid, Toolkit.Card {
         
         example_label.label = "<small><i>%s</i></small>".printf(eg);
         
-        // make first item active
-        calendar_combo_box.active = 0;
+        // reset calendar combo to default active item
+        model.set_item_default_active();
     }
     
     [GtkCallback]
diff --git a/src/host/host.vala b/src/host/host.vala
index 7f5758c..a1fada4 100644
--- a/src/host/host.vala
+++ b/src/host/host.vala
@@ -48,6 +48,7 @@ private Toolkit.ComboBoxTextModel<Backing.CalendarSource> build_calendar_source_
     
     // initialize with current list of calendars ... this control does not auto-update as
     // calendars are added/removed/modified
+    Backing.CalendarSource? first_default = null;
     foreach (Backing.CalendarSource calendar_source in
         Backing.Manager.instance.get_sources_of_type<Backing.CalendarSource>()) {
         if (!include_invisible && !calendar_source.visible)
@@ -57,6 +58,15 @@ private Toolkit.ComboBoxTextModel<Backing.CalendarSource> build_calendar_source_
             continue;
         
         model.add(calendar_source);
+        
+        // set first calendar marked as default as the initial choice
+        if (calendar_source.is_default && first_default == null)
+            first_default = calendar_source;
+    }
+    
+    if (first_default != null) {
+        model.set_item_active(first_default);
+        model.make_default_active(first_default);
     }
     
     return model;
diff --git a/src/manager/manager-calendar-list-item.vala b/src/manager/manager-calendar-list-item.vala
index ca6a2a6..f770cb4 100644
--- a/src/manager/manager-calendar-list-item.vala
+++ b/src/manager/manager-calendar-list-item.vala
@@ -36,6 +36,9 @@ internal class CalendarListItem : Gtk.Grid, Toolkit.MutableWidget {
     [GtkChild]
     private Gtk.ColorButton color_button;
     
+    [GtkChild]
+    private Gtk.Image default_icon;
+    
     private Toolkit.EditableLabel? editable_label = null;
     
     public CalendarListItem(Backing.CalendarSource source) {
@@ -55,6 +58,10 @@ internal class CalendarListItem : Gtk.Grid, Toolkit.MutableWidget {
             BindingFlags.SYNC_CREATE, xform_readonly_to_icon_name);
         source.bind_property(Backing.Source.PROP_READONLY, readonly_icon, "tooltip-text",
             BindingFlags.SYNC_CREATE, xform_readonly_to_tooltip_text);
+        source.bind_property(Backing.CalendarSource.PROP_IS_DEFAULT, default_icon, "icon-name",
+            BindingFlags.SYNC_CREATE, xform_default_to_icon_name);
+        source.bind_property(Backing.CalendarSource.PROP_IS_DEFAULT, default_icon, "tooltip-text",
+            BindingFlags.SYNC_CREATE, xform_default_to_tooltip_text);
         
         title_eventbox.button_release_event.connect(on_title_button_release);
     }
@@ -80,6 +87,18 @@ internal class CalendarListItem : Gtk.Grid, Toolkit.MutableWidget {
         return true;
     }
     
+    private bool xform_default_to_icon_name(Binding binding, Value source_value, ref Value target_value) {
+        target_value = source.is_default ? "starred-symbolic" : "";
+        
+        return true;
+    }
+    
+    private bool xform_default_to_tooltip_text(Binding binding, Value source_value, ref Value target_value) {
+        target_value = source.is_default ? _("Calendar is default") : _("Make this calendar default");
+        
+        return true;
+    }
+    
     public override bool query_tooltip(int x, int y, bool keyboard_mode, Gtk.Tooltip tooltip) {
         // no tooltip if text is entirely shown
         if (!title_label.get_layout().is_ellipsized())
@@ -150,6 +169,15 @@ internal class CalendarListItem : Gtk.Grid, Toolkit.MutableWidget {
         if (!String.is_empty(text))
             source.title = text;
     }
+    
+    [GtkCallback]
+    private void on_default_button_clicked() {
+        try {
+            source.store.make_default_calendar(source);
+        } catch (Error err) {
+            message("Unable to set default calendar to %s: %s", source.title, err.message);
+        }
+    }
 }
 
 }
diff --git a/src/rc/calendar-manager-list-item.ui b/src/rc/calendar-manager-list-item.ui
index 420b610..d074600 100644
--- a/src/rc/calendar-manager-list-item.ui
+++ b/src/rc/calendar-manager-list-item.ui
@@ -7,6 +7,34 @@
     <property name="can_focus">False</property>
     <property name="column_spacing">4</property>
     <child>
+      <object class="GtkEventBox" id="title_eventbox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="events">GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
+        <property name="visible_window">False</property>
+        <child>
+          <object class="GtkLabel" id="title_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="valign">center</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="label">calendar name</property>
+            <property name="ellipsize">end</property>
+            <property name="single_line_mode">True</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">4</property>
+        <property name="top_attach">0</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
       <object class="GtkColorButton" id="color_button">
         <property name="visible">True</property>
         <property name="can_focus">True</property>
@@ -17,7 +45,7 @@
         <property name="title" translatable="yes">Calendar color</property>
       </object>
       <packing>
-        <property name="left_attach">2</property>
+        <property name="left_attach">3</property>
         <property name="top_attach">0</property>
         <property name="width">1</property>
         <property name="height">1</property>
@@ -38,7 +66,7 @@
         </child>
       </object>
       <packing>
-        <property name="left_attach">1</property>
+        <property name="left_attach">2</property>
         <property name="top_attach">0</property>
         <property name="width">1</property>
         <property name="height">1</property>
@@ -55,35 +83,29 @@
         <property name="icon_size">1</property>
       </object>
       <packing>
-        <property name="left_attach">0</property>
+        <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>
-      <object class="GtkEventBox" id="title_eventbox">
+      <object class="GtkButton" id="default_button">
         <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="events">GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
-        <property name="visible_window">False</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">True</property>
+        <property name="relief">none</property>
+        <signal name="clicked" handler="on_default_button_clicked" 
object="CaliforniaManagerCalendarListItem" swapped="no"/>
         <child>
-          <object class="GtkLabel" id="title_label">
+          <object class="GtkImage" id="default_icon">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="valign">center</property>
-            <property name="hexpand">True</property>
-            <property name="vexpand">True</property>
-            <property name="xalign">0</property>
-            <property name="yalign">0</property>
-            <property name="label">calendar name</property>
-            <property name="ellipsize">end</property>
-            <property name="single_line_mode">True</property>
+            <property name="pixel_size">16</property>
           </object>
         </child>
       </object>
       <packing>
-        <property name="left_attach">3</property>
+        <property name="left_attach">0</property>
         <property name="top_attach">0</property>
         <property name="width">1</property>
         <property name="height">1</property>
diff --git a/src/toolkit/toolkit-combo-box-text-model.vala b/src/toolkit/toolkit-combo-box-text-model.vala
index dc556cb..8e5f58e 100644
--- a/src/toolkit/toolkit-combo-box-text-model.vala
+++ b/src/toolkit/toolkit-combo-box-text-model.vala
@@ -27,6 +27,13 @@ public class ComboBoxTextModel<G> : BaseObject {
     public G? active { get; private set; }
     
     /**
+     * The default active item.
+     *
+     * This can be used to restore the model to an initial state.
+     */
+    public G? default_active { get; private set; default = null; }
+    
+    /**
      * Set to true if the { link ModelPresentation} returns Pango markup instead of plain text.
      */
     public bool is_markup { get; set; default = false; }
@@ -159,6 +166,45 @@ public class ComboBoxTextModel<G> : BaseObject {
     }
     
     /**
+     * Makes the item the { link default_active} item.
+     *
+     * The supplied item must already be a member of the model.
+     *
+     * @returns False if not present in model.
+     */
+    public bool make_default_active(G item) {
+        if (!indices.has_key(item))
+            return false;
+        
+        default_active = item;
+        
+        return true;
+    }
+    
+    /**
+     * Clears the { link default_active} item.
+     */
+    public void clear_default_active() {
+        default_active = null;
+    }
+    
+    /**
+     * Makes the { link default_active} item active in the Gtk.ComboBoxText.
+     *
+     * If default_active is null, the Gtk.ComboBoxText's zeroeth item will be set active.
+     *
+     * Returns the result of { link set_item_active} or true if default_active is null.
+     */
+    public bool set_item_default_active() {
+        if (default_active != null)
+            return set_item_active(default_active);
+        
+        combo_box.active = 0;
+        
+        return true;
+    }
+    
+    /**
      * Returns the item at the Gtk.ComboBoxText index.
      */
     public G? get_item_at(int index) {


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