[california] Hide/show calendars: Closes bgo#725780



commit 334bef8ff1750514ba924af74fc82dddd9d326f1
Author: Jim Nelson <jim yorba org>
Date:   Fri Mar 14 17:28:11 2014 -0700

    Hide/show calendars: Closes bgo#725780
    
    This introduces the Calendar Manager, which will be expanded later
    to allow the user to add, remove, and configure their calendars.
    Right now the only active control in it is to set the visibility
    of the calendars.  It's also tied to the EDS setting, meaning
    changing visibility in another EDS client (i.e. Evolution) will
    be reflected in California.

 src/Makefile.am                                  |    8 +++
 src/application/california-application.vala      |   11 +++
 src/backing/backing-manager.vala                 |    4 +-
 src/backing/backing-source.vala                  |   14 ++++
 src/backing/eds/backing-eds-calendar-source.vala |   65 +++++++++++++++++++
 src/california-resources.xml                     |    6 ++
 src/host/host-create-update-event.vala           |    3 +
 src/host/host-interaction.vala                   |   10 ++--
 src/host/host-main-window.vala                   |   36 +++++------
 src/host/host-modal-window.vala                  |   72 ++++++++++++++++++++++
 src/host/host-popup.vala                         |    2 +-
 src/manager/manager-calendar-list-item.vala      |   34 ++++++++++
 src/manager/manager-calendar-list.vala           |   69 +++++++++++++++++++++
 src/manager/manager-window.vala                  |   40 ++++++++++++
 src/manager/manager.vala                         |   30 +++++++++
 src/rc/app-menu.interface                        |    7 ++
 src/rc/calendar-manager-list-item.ui             |   50 +++++++++++++++
 src/rc/calendar-manager-list.ui                  |   58 +++++++++++++++++
 src/util/util-string.vala                        |    4 +
 src/view/month/month-cell.vala                   |    3 +
 src/view/month/month-controllable.vala           |    1 +
 21 files changed, 500 insertions(+), 27 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 0effdca..4c6ac3b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -65,9 +65,15 @@ california_VALASOURCES = \
        host/host-create-update-event.vala \
        host/host-interaction.vala \
        host/host-main-window.vala \
+       host/host-modal-window.vala \
        host/host-popup.vala \
        host/host-show-event.vala \
        \
+       manager/manager.vala \
+       manager/manager-calendar-list.vala \
+       manager/manager-calendar-list-item.vala \
+       manager/manager-window.vala \
+       \
        util/util-memory.vala \
        util/util-string.vala \
        \
@@ -87,6 +93,8 @@ california_SOURCES = \
 
 california_RC = \
        rc/app-menu.interface \
+       rc/calendar-manager-list.ui \
+       rc/calendar-manager-list-item.ui \
        rc/create-update-event.ui \
        rc/show-event.ui \
        $(NULL)
diff --git a/src/application/california-application.vala b/src/application/california-application.vala
index ebbfd82..761e2fe 100644
--- a/src/application/california-application.vala
+++ b/src/application/california-application.vala
@@ -29,7 +29,12 @@ public class Application : Gtk.Application {
         null
     };
     
+    public const string ACTION_CALENDAR_MANAGER = "app.calendar-manager";
+    public const string ACTION_ABOUT = "app.about";
+    public const string ACTION_QUIT = "app.quit";
+    
     private static const ActionEntry[] action_entries = {
+        { "calendar-manager", on_calendar_manager },
         { "about", on_about },
         { "quit", on_quit }
     };
@@ -72,6 +77,7 @@ public class Application : Gtk.Application {
         // unit initialization
         try {
             Host.init();
+            Manager.init();
         } catch (Error err) {
             error_message(_("Unable to open California: %s").printf(err.message));
             quit();
@@ -87,6 +93,7 @@ public class Application : Gtk.Application {
         main_window = null;
         
         // unit termination
+        Manager.terminate();
         Host.terminate();
         
         base.shutdown();
@@ -113,6 +120,10 @@ public class Application : Gtk.Application {
         dialog.destroy();
     }
     
+    private void on_calendar_manager() {
+        Manager.Window.display(main_window);
+    }
+    
     private void on_about() {
         Gtk.show_about_dialog(main_window,
             "program-name", TITLE,
diff --git a/src/backing/backing-manager.vala b/src/backing/backing-manager.vala
index 7238528..4ce555b 100644
--- a/src/backing/backing-manager.vala
+++ b/src/backing/backing-manager.vala
@@ -116,7 +116,7 @@ public class Manager : BaseObject {
      *
      * The list will be sorted by the Sources title in lexiographic order.
      *
-     * Must only be called wheil the { link Manager} is open.
+     * Must only be called while the { link Manager} is open.
      *
      * @see Store.get_sources_of_type
      */
@@ -126,7 +126,7 @@ public class Manager : BaseObject {
             sources.add_all(store.get_sources_of_type<G>());
         
         sources.sort((a, b) => {
-            return strcmp(((Source) a).title, ((Source) b).title);
+            return String.stricmp(((Source) a).title, ((Source) b).title);
         });
         
         return sources;
diff --git a/src/backing/backing-source.vala b/src/backing/backing-source.vala
index 4517c92..022c9c6 100644
--- a/src/backing/backing-source.vala
+++ b/src/backing/backing-source.vala
@@ -18,6 +18,8 @@ namespace California.Backing {
 
 public abstract class Source : BaseObject {
     public const string PROP_IS_AVAILABLE = "is-available";
+    public const string PROP_TITLE = "title";
+    public const string PROP_VISIBLE = "visible";
     
     /**
      * True if the { link Source} is unavailable for use due to being removed from it's
@@ -35,6 +37,18 @@ public abstract class Source : BaseObject {
      */
     public string title { get; private set; }
     
+    /**
+     * Whether the { link Source} should be visible to the user.
+     *
+     * The caller should monitor this setting to decide whether or not to display the Source's
+     * associated inforamtion.  Hiding a Source does not mean that a Source subscription won't
+     * continue generating information.  Likewise, a hidden Source can still accept operations
+     * like adding and removing objects.
+     *
+     * @see CalendarSourceSubscription
+     */
+    public bool visible { get; set; }
+    
     protected Source(string title) {
         this.title = title;
     }
diff --git a/src/backing/eds/backing-eds-calendar-source.vala 
b/src/backing/eds/backing-eds-calendar-source.vala
index d4e6ecc..8b31f90 100644
--- a/src/backing/eds/backing-eds-calendar-source.vala
+++ b/src/backing/eds/backing-eds-calendar-source.vala
@@ -11,15 +11,80 @@ namespace California.Backing {
  */
 
 internal class EdsCalendarSource : CalendarSource {
+    private const int UPDATE_DELAY_MSEC = 500;
+    
     private E.Source eds_source;
     private E.SourceCalendar eds_calendar;
     private E.CalClient? client = null;
+    private uint source_write_id = 0;
+    private Cancellable? source_write_cancellable = null;
     
     public EdsCalendarSource(E.Source eds_source, E.SourceCalendar eds_calendar) {
         base (eds_source.display_name);
         
         this.eds_source = eds_source;
         this.eds_calendar = eds_calendar;
+        
+        // use unidirectional bindings so source updates (writing) only occurs when changed from
+        // within the app
+        eds_calendar.bind_property("selected", this, PROP_VISIBLE, BindingFlags.SYNC_CREATE);
+        
+        // when changed within the app, need to write it back out
+        notify[PROP_VISIBLE].connect(on_visible_changed);
+    }
+    
+    ~EdsCalendarSource() {
+        cancel_source_write();
+    }
+    
+    private void on_visible_changed() {
+        // only schedule source writes if something actually changed
+        if (eds_calendar.selected == visible)
+            return;
+        
+        eds_calendar.selected = visible;
+        schedule_source_write("visible=%s".printf(visible.to_string()));
+    }
+    
+    private void schedule_source_write(string reason) {
+        cancel_source_write();
+        
+        debug("Scheduling update of %s due to %s...", to_string(), reason);
+        source_write_cancellable = new Cancellable();
+        source_write_id = Timeout.add(UPDATE_DELAY_MSEC, on_background_write_source, Priority.LOW);
+    }
+    
+    private void cancel_source_write() {
+        if (source_write_id != 0) {
+            GLib.Source.remove(source_write_id);
+            source_write_id = 0;
+        }
+        
+        if (source_write_cancellable != null) {
+            source_write_cancellable.cancel();
+            source_write_cancellable = null;
+        }
+    }
+    
+    private bool on_background_write_source() {
+        // in essence, say this is no longer scheduled ... for now, allow another write to be
+        // scheduled while this one is occurring
+        source_write_id = 0;
+        Cancellable? cancellable = source_write_cancellable;
+        source_write_cancellable = null;
+        
+        if (cancellable == null || cancellable.is_cancelled())
+            return false;
+        
+        try {
+            debug("Updating EDS source %s...", to_string());
+            // TODO: Fix bindings to use async variant
+            eds_source.write_sync(cancellable);
+        } catch (Error err) {
+            debug("Error updating EDS source %s: %s", to_string(), err.message);
+        }
+        
+        return false;
     }
     
     // Invoked by EdsStore prior to making it available outside of unit
diff --git a/src/california-resources.xml b/src/california-resources.xml
index 8f5d8d5..3a514fb 100644
--- a/src/california-resources.xml
+++ b/src/california-resources.xml
@@ -4,6 +4,12 @@
         <file compressed="true">rc/app-menu.interface</file>
     </gresource>
     <gresource prefix="/org/yorba/california">
+        <file compressed="false">rc/calendar-manager-list.ui</file>
+    </gresource>
+    <gresource prefix="/org/yorba/california">
+        <file compressed="false">rc/calendar-manager-list-item.ui</file>
+    </gresource>
+    <gresource prefix="/org/yorba/california">
         <file compressed="false">rc/create-update-event.ui</file>
     </gresource>
     <gresource prefix="/org/yorba/california">
diff --git a/src/host/host-create-update-event.vala b/src/host/host-create-update-event.vala
index 4e350d8..55606ac 100644
--- a/src/host/host-create-update-event.vala
+++ b/src/host/host-create-update-event.vala
@@ -168,6 +168,9 @@ public class CreateUpdateEvent : Gtk.Grid, Interaction {
         index = 0;
         int calendar_source_index = 0;
         foreach (Backing.Source source in calendar_sources) {
+            if (!source.visible)
+                continue;
+            
             calendar_combo.append_text(source.title);
             if (source == event.calendar_source)
                 calendar_source_index = index;
diff --git a/src/host/host-interaction.vala b/src/host/host-interaction.vala
index cf31c88..afebc36 100644
--- a/src/host/host-interaction.vala
+++ b/src/host/host-interaction.vala
@@ -22,17 +22,17 @@ namespace California.Host {
 
 public interface Interaction : Gtk.Widget {
     /**
+     * The default widget for the { link Interaction}.
+     */
+    public abstract Gtk.Widget? default_widget { get; }
+    
+    /**
      * Fired when the user has cancelled, closed, or dismissed the { link Interaction}.
      *
      * This should be called by implementing classes even if other signals suggest or imply that
      * the Interaction is dismissed, so a single signal handler can deal with cleanup.
      */
     public signal void dismissed();
-    
-    /**
-     * The default widget for the { link Interaction}.
-     */
-    public abstract Gtk.Widget? default_widget { get; }
 }
 
 }
diff --git a/src/host/host-main-window.vala b/src/host/host-main-window.vala
index 2b002f6..c7465f6 100644
--- a/src/host/host-main-window.vala
+++ b/src/host/host-main-window.vala
@@ -64,8 +64,14 @@ public class MainWindow : Gtk.ApplicationWindow {
         new_event.tooltip_text = _("Create a new event for today");
         new_event.clicked.connect(on_new_event);
         
+        Gtk.Button calendars = new Gtk.Button.from_icon_name("x-office-calendar-symbolic",
+            Gtk.IconSize.MENU);
+        calendars.tooltip_text = _("Calendars (Ctrl+L)");
+        calendars.set_action_name(Application.ACTION_CALENDAR_MANAGER);
+        
         // pack right-side of window
         headerbar.pack_end(new_event);
+        headerbar.pack_end(calendars);
         
         Gtk.Box layout = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
 #if ENABLE_UNITY
@@ -87,27 +93,18 @@ public class MainWindow : Gtk.ApplicationWindow {
         add(layout);
     }
     
-    private Gtk.Widget show_interaction(Gtk.Widget relative_to, Gdk.Point? for_location,
+    private void show_interaction(Gtk.Widget relative_to, Gdk.Point? for_location,
         Interaction child) {
-        Gtk.Dialog dialog = new Gtk.Dialog();
-        dialog.transient_for = this;
-        dialog.modal = true;
-        ((Gtk.Box) dialog.get_content_area()).pack_start(child, true, true, 0);
+        ModalWindow modal_window = new ModalWindow(this);
+        modal_window.content_area.add(child);
         
         // when the dialog closes, reset View.Controllable state (selection is maintained while
         // use is viewing/editing Interaction) and destroy widgets
-        child.dismissed.connect(() => {
-            current_view.unselect_all();
-            dialog.destroy();
-        });
-        
-        dialog.show_all();
+        child.dismissed.connect(() => current_view.unselect_all());
         
-        // the default widget is lost in the shuffle, reestablish its primacy
-        if (child.default_widget != null)
-            child.default_widget.grab_default();
-        
-        return dialog;
+        modal_window.show_all();
+        modal_window.run();
+        modal_window.destroy();
     }
     
     private void on_new_event() {
@@ -140,8 +137,6 @@ public class MainWindow : Gtk.ApplicationWindow {
         else
             create_update_event = new CreateUpdateEvent.update(existing);
         
-        show_interaction(relative_to, for_location, create_update_event);
-        
         create_update_event.create_event.connect((event) => {
             create_event_async.begin(event, null);
         });
@@ -150,6 +145,8 @@ public class MainWindow : Gtk.ApplicationWindow {
             // TODO: Delete from original source if not the same as the new source
             update_event_async.begin(event, null);
         });
+        
+        show_interaction(relative_to, for_location, create_update_event);
     }
     
     private async void create_event_async(Component.Event event, Cancellable? cancellable) {
@@ -177,7 +174,6 @@ public class MainWindow : Gtk.ApplicationWindow {
     private void on_request_display_event(Component.Event event, Gtk.Widget relative_to,
         Gdk.Point? for_location) {
         ShowEvent show_event = new ShowEvent(event);
-        show_interaction(relative_to, for_location, show_event);
         
         show_event.remove_event.connect(() => {
             remove_event_async.begin(event, null);
@@ -186,6 +182,8 @@ public class MainWindow : Gtk.ApplicationWindow {
         show_event.update_event.connect(() => {
             create_event(null, null, event, relative_to, for_location);
         });
+        
+        show_interaction(relative_to, for_location, show_event);
     }
     
     private async void remove_event_async(Component.Event event, Cancellable? cancellable) {
diff --git a/src/host/host-modal-window.vala b/src/host/host-modal-window.vala
new file mode 100644
index 0000000..45992d5
--- /dev/null
+++ b/src/host/host-modal-window.vala
@@ -0,0 +1,72 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Host {
+
+/**
+ * A GtkDialog with no visible action area.
+ *
+ * This is designed for UI panes that want to control their own interaction with the user (in
+ * particular, button placement) but need all the benefits interaction-wise of GtkDialog.
+ *
+ * It's expected this will go away when we move to GTK+ 3.12 and can use GtkPopovers for these
+ * interactions.
+ */
+
+public class ModalWindow : Gtk.Dialog {
+    public Gtk.Box content_area { get; private set; }
+    
+    private Interaction? primary = null;
+    
+    public ModalWindow(Gtk.Window? parent) {
+        transient_for = parent;
+        modal = true;
+        resizable = false;
+        
+        content_area = (Gtk.Box) get_content_area();
+        content_area.margin = 8;
+        content_area.add.connect(on_content_added);
+        content_area.remove.connect(on_content_removed);
+        
+        get_action_area().visible = false;
+        get_action_area().no_show_all = true;
+    }
+    
+    private void on_content_added(Gtk.Widget widget) {
+        Interaction? interaction = widget as Interaction;
+        if (interaction != null) {
+            if (primary == null)
+                primary = interaction;
+            
+            interaction.dismissed.connect(on_interaction_dismissed);
+        }
+    }
+    
+    private void on_content_removed(Gtk.Widget widget) {
+        Interaction? interaction = widget as Interaction;
+        if (interaction != null) {
+            if (primary == interaction)
+                primary = null;
+            
+            interaction.dismissed.disconnect(on_interaction_dismissed);
+        }
+    }
+    
+    private void on_interaction_dismissed() {
+        response(Gtk.ResponseType.CLOSE);
+    }
+    
+    public override void show() {
+        base.show();
+        
+        // the default widget is lost in the shuffle, reestablish its primacy
+        if (primary != null && primary.default_widget != null)
+            primary.default_widget.grab_default();
+    }
+}
+
+}
+
diff --git a/src/host/host-popup.vala b/src/host/host-popup.vala
index b40dd54..5219753 100644
--- a/src/host/host-popup.vala
+++ b/src/host/host-popup.vala
@@ -30,7 +30,7 @@ public class Popup : Gtk.Window {
      * The GtkWidget must be realized when this is invoked.
      */
     public Popup(Gtk.Widget relative_to) {
-        Object (type:Gtk.WindowType.TOPLEVEL);
+        Object(type:Gtk.WindowType.TOPLEVEL);
         
         assert(relative_to.get_realized());
         this.relative_to = relative_to;
diff --git a/src/manager/manager-calendar-list-item.vala b/src/manager/manager-calendar-list-item.vala
new file mode 100644
index 0000000..87c1e6c
--- /dev/null
+++ b/src/manager/manager-calendar-list-item.vala
@@ -0,0 +1,34 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Manager {
+
+/**
+ * An interactive list item in a { link CalendarList}.
+ */
+
+[GtkTemplate (ui = "/org/yorba/california/rc/calendar-manager-list-item.ui")]
+public class CalendarListItem : Gtk.Grid {
+    public Backing.CalendarSource source { get; private set; }
+    
+    [GtkChild]
+    private Gtk.CheckButton visible_check_button;
+    
+    [GtkChild]
+    private Gtk.Label title_label;
+    
+    public CalendarListItem(Backing.CalendarSource source) {
+        this.source = source;
+        
+        source.bind_property(Backing.Source.PROP_TITLE, title_label, "label",
+            BindingFlags.SYNC_CREATE);
+        source.bind_property(Backing.Source.PROP_VISIBLE, visible_check_button, "active",
+            BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
+    }
+}
+
+}
+
diff --git a/src/manager/manager-calendar-list.vala b/src/manager/manager-calendar-list.vala
new file mode 100644
index 0000000..61ccf47
--- /dev/null
+++ b/src/manager/manager-calendar-list.vala
@@ -0,0 +1,69 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Manager {
+
+/**
+ * A list of available calendars and basic configuration controls.
+ */
+
+[GtkTemplate (ui = "/org/yorba/california/rc/calendar-manager-list.ui")]
+public class CalendarList : Gtk.Grid, Host.Interaction {
+    public Gtk.Widget? default_widget { get { return null; } }
+    
+    [GtkChild]
+    private Gtk.ListBox calendar_list_box;
+    
+    public CalendarList() {
+        // if already open, initialize now
+        if (Backing.Manager.instance.is_open)
+            init();
+        
+        // otherwise, initialize when it does open
+        Backing.Manager.instance.notify[Backing.Manager.PROP_IS_OPEN].connect(on_manager_opened_closed);
+    }
+    
+    ~CalendarList() {
+        Backing.Manager.instance.notify[Backing.Manager.PROP_IS_OPEN].disconnect(on_manager_opened_closed);
+    }
+    
+    private void on_manager_opened_closed() {
+        if (Backing.Manager.instance.is_open)
+            init();
+        else
+            clear();
+    }
+    
+    private void init() {
+        assert(Backing.Manager.instance.is_open);
+        
+        foreach (Backing.CalendarSource source in
+            Backing.Manager.instance.get_sources_of_type<Backing.CalendarSource>()) {
+            calendar_list_box.add(new CalendarListItem(source));
+        }
+    }
+    
+    private void clear() {
+        foreach (unowned Gtk.Widget child in calendar_list_box.get_children()) {
+            if (child is CalendarListItem)
+                calendar_list_box.remove(child);
+        };
+    }
+    
+    [GtkCallback]
+    private void on_calendar_list_box_row_activated(Gtk.ListBoxRow row) {
+        CalendarListItem item = (CalendarListItem) row.get_child();
+        debug("activated %s", item.source.to_string());
+    }
+    
+    [GtkCallback]
+    private void on_close_button_clicked() {
+        dismissed();
+    }
+}
+
+}
+
diff --git a/src/manager/manager-window.vala b/src/manager/manager-window.vala
new file mode 100644
index 0000000..a910c6f
--- /dev/null
+++ b/src/manager/manager-window.vala
@@ -0,0 +1,40 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Manager {
+
+/**
+ * The Calendar Manager main window.
+ */
+
+public class Window : Host.ModalWindow {
+    private static Manager.Window? instance = null;
+    
+    private Window(Gtk.Window? parent) {
+        base (parent);
+        
+        content_area.add(new CalendarList());
+    }
+    
+    public static void display(Gtk.Window? parent) {
+        // only allow one instance at a time
+        if (instance != null) {
+            instance.present_with_time(Gdk.CURRENT_TIME);
+            
+            return;
+        }
+        
+        instance = new Manager.Window(parent);
+        instance.show_all();
+        instance.run();
+        instance.destroy();
+        
+        instance = null;
+    }
+}
+
+}
+
diff --git a/src/manager/manager.vala b/src/manager/manager.vala
new file mode 100644
index 0000000..daf174d
--- /dev/null
+++ b/src/manager/manager.vala
@@ -0,0 +1,30 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * The Calendar Manager, an interactive window for the user to add, configure, and remove
+ * calendar subscriptions.
+ */
+
+namespace California.Manager {
+
+private int init_count = 0;
+
+public void init() throws Error {
+    if (!Unit.do_init(ref init_count))
+        return;
+    
+    Backing.init();
+}
+
+public void terminate() {
+    if (!Unit.do_terminate(ref init_count))
+        return;
+    
+    Backing.terminate();
+}
+
+}
diff --git a/src/rc/app-menu.interface b/src/rc/app-menu.interface
index 19cc62f..564080b 100644
--- a/src/rc/app-menu.interface
+++ b/src/rc/app-menu.interface
@@ -3,6 +3,13 @@
     <menu id="app-menu">
         <section>
             <item>
+                <attribute name="label" translatable="yes">_Calendars</attribute>
+                <attribute name="action">app.calendar-manager</attribute>
+                <attribute name="accel">&lt;Primary&gt;l</attribute>
+            </item>
+        </section>
+        <section>
+            <item>
                 <attribute name="label" translatable="yes">_About</attribute>
                 <attribute name="action">app.about</attribute>
             </item>
diff --git a/src/rc/calendar-manager-list-item.ui b/src/rc/calendar-manager-list-item.ui
new file mode 100644
index 0000000..b081b42
--- /dev/null
+++ b/src/rc/calendar-manager-list-item.ui
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.16.1 -->
+<interface>
+  <requires lib="gtk+" version="3.10"/>
+  <template class="CaliforniaManagerCalendarListItem" parent="GtkGrid">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="column_spacing">4</property>
+    <child>
+      <object class="GtkCheckButton" id="visible_check_button">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">False</property>
+        <property name="halign">start</property>
+        <property name="valign">center</property>
+        <property name="xalign">0.50999999046325684</property>
+        <property name="draw_indicator">True</property>
+        <child>
+          <placeholder/>
+        </child>
+      </object>
+      <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>
+      <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>
+      <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>
+  </template>
+</interface>
diff --git a/src/rc/calendar-manager-list.ui b/src/rc/calendar-manager-list.ui
new file mode 100644
index 0000000..787736c
--- /dev/null
+++ b/src/rc/calendar-manager-list.ui
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.16.1 -->
+<interface>
+  <requires lib="gtk+" version="3.10"/>
+  <template class="CaliforniaManagerCalendarList" parent="GtkGrid">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkListBox" id="calendar_list_box">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="hexpand">True</property>
+        <property name="vexpand">True</property>
+        <signal name="row-activated" handler="on_calendar_list_box_row_activated" 
object="CaliforniaManagerCalendarList" swapped="no"/>
+      </object>
+      <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>
+      <object class="GtkButtonBox" id="calendar_list_button_box">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="valign">end</property>
+        <property name="margin_top">8</property>
+        <property name="baseline_position">bottom</property>
+        <property name="layout_style">end</property>
+        <child>
+          <object class="GtkButton" id="close_button">
+            <property name="label" translatable="yes">_Close</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="valign">end</property>
+            <property name="use_underline">True</property>
+            <signal name="clicked" handler="on_close_button_clicked" object="CaliforniaManagerCalendarList" 
swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+      <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>
+  </template>
+</interface>
diff --git a/src/util/util-string.vala b/src/util/util-string.vala
index af7c6b9..070c9d3 100644
--- a/src/util/util-string.vala
+++ b/src/util/util-string.vala
@@ -12,5 +12,9 @@ public inline bool is_empty(string? str) {
     return (str == null) || (str[0] == NUL);
 }
 
+public int stricmp(string a, string b) {
+    return strcmp(a.casefold(), b.casefold());
+}
+
 }
 
diff --git a/src/view/month/month-cell.vala b/src/view/month/month-cell.vala
index ff7e0d9..80e01de 100644
--- a/src/view/month/month-cell.vala
+++ b/src/view/month/month-cell.vala
@@ -243,6 +243,9 @@ public class Cell : Gtk.EventBox {
         // draw all events in chronological order, all-day events first, storing lookup data
         // as the "lines" are drawn ... make sure to convert them all to local timezone
         foreach (Component.Event event in days_events) {
+            if (!event.calendar_source.visible)
+                continue;
+            
             string text;
             if (event.is_all_day) {
                 text = event.summary;
diff --git a/src/view/month/month-controllable.vala b/src/view/month/month-controllable.vala
index a1a0e74..bff8f18 100644
--- a/src/view/month/month-controllable.vala
+++ b/src/view/month/month-controllable.vala
@@ -265,6 +265,7 @@ public class Controllable : Gtk.Grid, View.Controllable {
         foreach (Backing.Store store in Backing.Manager.instance.get_stores()) {
             foreach (Backing.Source source in store.get_sources_of_type<Backing.CalendarSource>()) {
                 Backing.CalendarSource calendar = (Backing.CalendarSource) source;
+                calendar.notify[Backing.Source.PROP_VISIBLE].connect(queue_draw);
                 calendar.subscribe_async.begin(time_window, null, on_subscribed);
             }
         }


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