[california] Upcoming events / Agenda view: Bug #734698
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california] Upcoming events / Agenda view: Bug #734698
- Date: Mon, 8 Dec 2014 23:13:51 +0000 (UTC)
commit 6635b308a6d295375ef4ceb61828d2af8f67c8b3
Author: Jim Nelson <jim yorba org>
Date: Mon Dec 8 15:11:53 2014 -0800
Upcoming events / Agenda view: Bug #734698
Basic Agenda view functionality, with ability to change the starting
date one day at a time and expand the amount of time being displayed
one month at a time.
po/POTFILES.in | 6 +
po/POTFILES.skip | 3 +
src/Makefile.am | 9 +
.../backing-calendar-subscription-manager.vala | 60 ++++-
src/calendar/calendar-date.vala | 24 ++
src/calendar/calendar-exact-time-span.vala | 19 ++
src/calendar/calendar-span.vala | 52 ++++
src/calendar/calendar.vala | 6 +-
src/california-resources.xml | 9 +
src/collection/collection-iterable.vala | 24 ++
src/component/component-event.vala | 1 -
src/component/component-instance.vala | 5 +-
src/host/host-main-window.vala | 3 +
src/rc/view-agenda-date-row.ui | 40 +++
src/rc/view-agenda-event-row.ui | 84 ++++++
src/rc/view-agenda-load-more-row.ui | 42 +++
src/toolkit/toolkit-listbox-model.vala | 33 ++
src/toolkit/toolkit.vala | 15 +
src/view/agenda/agenda-controller.vala | 309 ++++++++++++++++++++
src/view/agenda/agenda-date-row.vala | 115 ++++++++
src/view/agenda/agenda-event-row.vala | 160 ++++++++++
src/view/agenda/agenda-load-more-row.vala | 48 +++
src/view/agenda/agenda.vala | 35 +++
src/view/month/month-controller.vala | 5 -
src/view/view.vala | 2 +
25 files changed, 1098 insertions(+), 11 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index d4bdc75..15a5e19 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -26,6 +26,9 @@ src/host/host-quick-create-event.vala
src/host/host-show-event.vala
src/manager/manager-calendar-list-item.vala
src/manager/manager-remove-calendar.vala
+src/view/agenda/agenda-controller.vala
+src/view/agenda/agenda-event-row.vala
+src/view/agenda/agenda-load-more-row.vala
src/view/month/month-controller.vala
src/view/week/week-controller.vala
[type: gettext/glade]src/rc/activator-generic-subscribe.ui
@@ -47,4 +50,7 @@ src/view/week/week-controller.vala
[type: gettext/glade]src/rc/manager-calendar-list.ui
[type: gettext/glade]src/rc/manager-calendar-list-item.ui
[type: gettext/glade]src/rc/manager-remove-calendar.ui
+[type: gettext/glade]src/rc/view-agenda-date-row.ui
+[type: gettext/glade]src/rc/view-agenda-event-row.ui
+[type: gettext/glade]src/rc/view-agenda-load-more-row.ui
[type: gettext/glade]src/rc/window-menu.interface
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 05f7413..8afa4c4 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -24,6 +24,9 @@ src/host/host-quick-create-event.c
src/host/host-show-event.c
src/manager/manager-calendar-list-item.c
src/manager/manager-remove-calendar.c
+src/view/agenda/agenda-controller.c
+src/view/agenda/agenda-event-row.c
+src/view/agenda/agenda-load-more-row.c
src/view/month/month-controller.c
src/view/week/week-controller.c
diff --git a/src/Makefile.am b/src/Makefile.am
index f91c67c..070b25c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -170,6 +170,12 @@ california_VALASOURCES = \
view/view-controllable.vala \
view/view-palette.vala \
\
+ view/agenda/agenda.vala \
+ view/agenda/agenda-controller.vala \
+ view/agenda/agenda-date-row.vala \
+ view/agenda/agenda-event-row.vala \
+ view/agenda/agenda-load-more-row.vala \
+ \
view/common/common.vala \
view/common/common-events-cell.vala \
view/common/common-instance-container.vala \
@@ -214,6 +220,9 @@ california_RC = \
rc/manager-calendar-list.ui \
rc/manager-calendar-list-item.ui \
rc/manager-remove-calendar.ui \
+ rc/view-agenda-date-row.ui \
+ rc/view-agenda-event-row.ui \
+ rc/view-agenda-load-more-row.ui \
rc/window-menu.interface \
$(NULL)
diff --git a/src/backing/backing-calendar-subscription-manager.vala
b/src/backing/backing-calendar-subscription-manager.vala
index 71da1b4..68fa314 100644
--- a/src/backing/backing-calendar-subscription-manager.vala
+++ b/src/backing/backing-calendar-subscription-manager.vala
@@ -115,6 +115,55 @@ public class CalendarSubscriptionManager : BaseObject {
}
}
+ /**
+ * Expand the { link CalendarSubscriptionManager}'s { link window} of time.
+ *
+ * expand_window() will increase the contiguous span of time being monitored for changes by the
+ * subscription manager. There is no provision for managing multiple ''fragments'' of time,
+ * only expanding the window.
+ *
+ * expand_window() should ''not'' be called until { link start_async} has completed. Results
+ * are unguaranteed if called while start_async() is executing.
+ *
+ * If expanded_time is within the current window, nothing happens.
+ *
+ * TODO: Currently the subscription manager will expand the range by creating a new
+ * { link CalendarSubscription} for the new dates. This can be inefficient when dealing with
+ * lots of small ranges. A better solution would be to create wider ranges and filter out
+ * events outside of the specified window.
+ */
+ public async void expand_window_async(Calendar.ExactTime expanded_time) {
+ if (expanded_time in window)
+ return;
+
+ // and create a new subscription window to cover the new span of time without overlapping
+ // existing subscription(s)
+ Calendar.ExactTimeSpan subscription_window;
+ if (expanded_time.compare_to(window.start_exact_time) < 0) {
+ subscription_window = new Calendar.ExactTimeSpan(
+ expanded_time,
+ window.start_exact_time.adjust_time(-1, Calendar.TimeUnit.SECOND)
+ );
+ } else {
+ assert(expanded_time.compare_to(window.end_exact_time) > 0);
+
+ subscription_window = new Calendar.ExactTimeSpan(
+ window.end_exact_time.adjust_time(1, Calendar.TimeUnit.SECOND),
+ expanded_time
+ );
+ }
+
+ // expand the current window ... do this before adding subscriptions so if new calendars
+ // are reported during async calls, they use the full expanded window
+ window = window.expand(expanded_time);
+
+ // create new subscriptions for the expanded span only
+ foreach (Backing.Store store in Backing.Manager.instance.get_stores()) {
+ foreach (Backing.CalendarSource calendar in store.get_sources_of_type<Backing.CalendarSource>())
+ yield add_subscription_async(calendar, subscription_window, null);
+ }
+ }
+
private void on_source_added(Backing.Source source) {
Backing.CalendarSource? calendar = source as Backing.CalendarSource;
if (calendar != null)
@@ -125,12 +174,17 @@ public class CalendarSubscriptionManager : BaseObject {
// report calendar as added to subscription
calendar_added(calendar);
- // start generating instances on this calendar
+ // add a subscription for the new calendar with existing window
+ yield add_subscription_async(calendar, window, cancellable);
+ }
+
+ private async void add_subscription_async(Backing.CalendarSource calendar,
+ Calendar.ExactTimeSpan subscription_window, Cancellable? cancellable) {
try {
// Since this might be called after the dtor has finished (cancelling the operation), don't
// touch the "this" ref unless the Error is shown not to be a cancellation
- Backing.CalendarSourceSubscription subscription = yield calendar.subscribe_async(window,
- cancellable);
+ Backing.CalendarSourceSubscription subscription = yield calendar.subscribe_async(
+ subscription_window, cancellable);
// okay to use "this" ref
subscriptions.add(subscription);
diff --git a/src/calendar/calendar-date.vala b/src/calendar/calendar-date.vala
index 1bf41ce..4ba9e43 100644
--- a/src/calendar/calendar-date.vala
+++ b/src/calendar/calendar-date.vala
@@ -62,6 +62,15 @@ public class Date : Unit<Date>, Gee.Comparable<Date>, Gee.Hashable<Date> {
NO_DAY_OF_WEEK
}
+ /**
+ * The practical earliest representable { link Date} in time.
+ */
+ public static Date earliest { get; private set; }
+
+ /**
+ * The practical latest representable { link Date} in time.
+ */
+ public static Date latest { get; private set; }
/**
* @inheritDoc
@@ -145,6 +154,21 @@ public class Date : Unit<Date>, Gee.Comparable<Date>, Gee.Hashable<Date> {
year = new Year.from_gdate(gdate);
}
+ internal static void init() throws CalendarError {
+ GLib.Date earliest_gdate = GLib.Date();
+ earliest_gdate.set_julian(1);
+ earliest = new Date.from_gdate(earliest_gdate);
+
+ // GLib.Date.set_julian(uint.MAX) causes strange assertions inside of GLib, so just jimmying
+ // together a date far in the future for now
+ latest = new Date(DayOfMonth.for(31), Month.DEC, new Year(100000));
+ }
+
+ internal static void terminate() {
+ earliest = null;
+ latest = null;
+ }
+
/**
* Returns the { link Week} the { link Date} falls in.
*/
diff --git a/src/calendar/calendar-exact-time-span.vala b/src/calendar/calendar-exact-time-span.vala
index 4db2ca1..6e4a0b2 100644
--- a/src/calendar/calendar-exact-time-span.vala
+++ b/src/calendar/calendar-exact-time-span.vala
@@ -140,6 +140,25 @@ public class ExactTimeSpan : BaseObject, Gee.Comparable<ExactTimeSpan>, Gee.Hash
}
/**
+ * Returns an { link ExactTimeSpan} expanded to include the supplied { link ExactTime}.
+ *
+ * If the expanded_time is within this ExactTimeSpan, this object is returned.
+ */
+ public ExactTimeSpan expand(ExactTime expanded_time) {
+ if (contains(expanded_time))
+ return this;
+
+ // if supplied time before start of span, that becomes the new start time
+ if (expanded_time.compare_to(start_exact_time) < 0)
+ return new ExactTimeSpan(expanded_time, end_exact_time);
+
+ // prior tests guarantee supplied time is after end of this span
+ assert(expanded_time.compare_to(end_exact_time) > 0);
+
+ return new ExactTimeSpan(start_exact_time, expanded_time);
+ }
+
+ /**
* Returns a prettified string describing the { link Event}'s time span in as concise and
* economical manner possible.
*
diff --git a/src/calendar/calendar-span.vala b/src/calendar/calendar-span.vala
index 2f7eaeb..e5f2355 100644
--- a/src/calendar/calendar-span.vala
+++ b/src/calendar/calendar-span.vala
@@ -180,6 +180,58 @@ public abstract class Span : BaseObject {
}
/**
+ * Returns a { link DateSpan} that covers the time of this { link Span} and the supplied
+ * { link Date}.
+ *
+ * If the Date is within the existing Span, a DateSpan for this Span is returned, i.e. this
+ * is just like calling { link to_date_span}.
+ */
+ public DateSpan expand(Calendar.Date expansion) {
+ Date new_start = (expansion.compare_to(start_date) < 0) ? expansion : start_date;
+ Date new_end = (expansion.compare_to(end_date) > 0) ? expansion : end_date;
+
+ return new DateSpan(new_start, new_end);
+ }
+
+ /**
+ * Returns a { link DateSpan} that represents this { link Span} with the { link start_date}
+ * set to the supplied { link Date}.
+ *
+ * If the new start_date is the same or later than the { link end_date}, a one-day Span is
+ * returned that matches the supplied Date.
+ *
+ * If the new start date is outside the range of this Span, a DateSpan for this Span is
+ * returned, i.e. this is just like calling { link to_date_span}.
+ *
+ * @see reduce_from_end
+ */
+ public DateSpan reduce_from_start(Calendar.Date new_start_date) {
+ if (!has_date(new_start_date))
+ return to_date_span();
+
+ return new DateSpan(new_start_date, end_date);
+ }
+
+ /**
+ * Returns a { link DateSpan} that represents this { link Span} with the { link end_date}
+ * set to the supplied { link Date}.
+ *
+ * If the new end_date is the same or earlier than the { link start_date}, a one-day Span is
+ * returned that matches the supplied Date.
+ *
+ * If the new end date is outside the range of this Span, a DateSpan for this Span is
+ * returned, i.e. this is just like calling { link to_date_span}.
+ *
+ * @see reduce_from_start
+ */
+ public DateSpan reduce_from_end(Calendar.Date new_end_date) {
+ if (!has_date(new_end_date))
+ return to_date_span();
+
+ return new DateSpan(start_date, new_end_date);
+ }
+
+ /**
* True if the { link Span} contains the specified { link Date}.
*/
public bool has_date(Date date) {
diff --git a/src/calendar/calendar.vala b/src/calendar/calendar.vala
index 75cc426..09f7a83 100644
--- a/src/calendar/calendar.vala
+++ b/src/calendar/calendar.vala
@@ -233,13 +233,15 @@ public void init() throws Error {
// This init() throws an IOError, so perform before others to prevent unnecessary unwinding
System.preinit();
- // internal initialization
Collection.init();
+
+ // internal initialization
OlsonZone.init();
DayOfWeek.init();
DayOfMonth.init();
Month.init();
WallTime.init();
+ Date.init();
System.init();
Timezone.init();
}
@@ -250,11 +252,13 @@ public void terminate() {
Timezone.terminate();
System.terminate();
+ Date.terminate();
WallTime.terminate();
Month.terminate();
DayOfMonth.terminate();
DayOfWeek.terminate();
OlsonZone.terminate();
+
Collection.terminate();
}
diff --git a/src/california-resources.xml b/src/california-resources.xml
index 3f09836..707ebd1 100644
--- a/src/california-resources.xml
+++ b/src/california-resources.xml
@@ -58,6 +58,15 @@
<file compressed="true">rc/manager-remove-calendar.ui</file>
</gresource>
<gresource prefix="/org/yorba/california">
+ <file compressed="false">rc/view-agenda-date-row.ui</file>
+ </gresource>
+ <gresource prefix="/org/yorba/california">
+ <file compressed="false">rc/view-agenda-event-row.ui</file>
+ </gresource>
+ <gresource prefix="/org/yorba/california">
+ <file compressed="true">rc/view-agenda-load-more-row.ui</file>
+ </gresource>
+ <gresource prefix="/org/yorba/california">
<file compressed="true">rc/window-menu.interface</file>
</gresource>
</gresources>
diff --git a/src/collection/collection-iterable.vala b/src/collection/collection-iterable.vala
index cd1afbd..5c26149 100644
--- a/src/collection/collection-iterable.vala
+++ b/src/collection/collection-iterable.vala
@@ -307,6 +307,30 @@ public class Iterable<G> : Object {
}
/**
+ * Returns true if the { link Iterable} is empty.
+ *
+ * This is more efficient than checking if { link} count is zero when the Iterable is holding
+ * items.
+ *
+ * @see is_nonempty
+ */
+ public bool is_empty() {
+ return !iterator().has_next();
+ }
+
+ /**
+ * Returns true if the { link Iterable} is non-empty.
+ *
+ * This is more efficient than checking if { link} count is non-zero when the Iterable is
+ * holding items.
+ *
+ * @see is_empty
+ */
+ public bool is_nonempty() {
+ return iterator().has_next();
+ }
+
+ /**
* The resulting Gee.Iterable comes with the same caveat that you may only
* iterate over it once.
*/
diff --git a/src/component/component-event.vala b/src/component/component-event.vala
index 3f0734e..c21ee7c 100644
--- a/src/component/component-event.vala
+++ b/src/component/component-event.vala
@@ -274,7 +274,6 @@ public class Event : Instance, Gee.Comparable<Event> {
*
* This will return a DateSpan whether the Event is a DATE or DATE-TIME VEVENT.
*/
- // TODO: Make date_span/exact_time_span a separate object
public Calendar.DateSpan get_event_date_span(Calendar.Timezone? tz) {
if (date_span != null)
return date_span;
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index b65602b..258b6a7 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -29,7 +29,6 @@ namespace California.Component {
*/
public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
- public const string PROP_CALENDAR_SOURCE = "calendar-source";
public const string PROP_DTSTAMP = "dtstamp";
public const string PROP_UID = "uid";
public const string PROP_ICAL_COMPONENT = "ical-component";
@@ -47,6 +46,10 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
/**
* The { link Backing.CalendarSource} this { link Instance} originated from.
*
+ * This field is immutable for the lifetime of the { link Instance}. If an Instance is moved
+ * to another calendar, this instance will be destroyed and a new one reported from the
+ * appropriate { link Backing.CalendarSourceSubscription}.
+ *
* This will initialize as null if created as a { link blank} Instance.
*/
public Backing.CalendarSource? calendar_source { get; private set; default = null; }
diff --git a/src/host/host-main-window.vala b/src/host/host-main-window.vala
index 61e3df6..17e7a4c 100644
--- a/src/host/host-main-window.vala
+++ b/src/host/host-main-window.vala
@@ -89,6 +89,7 @@ public class MainWindow : Gtk.ApplicationWindow {
private View.Palette palette;
private View.Controllable month_view;
private View.Controllable week_view;
+ private View.Controllable agenda_view;
private View.Controllable? current_controller = null;
private Gee.HashSet<Binding> current_bindings = new Gee.HashSet<Binding>();
private Gtk.Stack view_stack = new Gtk.Stack();
@@ -142,10 +143,12 @@ public class MainWindow : Gtk.ApplicationWindow {
// ... then create the hosted views
month_view = new View.Month.Controller(palette);
week_view = new View.Week.Controller(palette);
+ agenda_view = new View.Agenda.Controller(palette);
// add views to view stack, first added is first shown
add_controller(month_view);
add_controller(week_view);
+ add_controller(agenda_view);
// if not on Unity, use headerbar as the titlebar (removes window chrome) and provide close
// button for users who might have trouble finding it otherwise
diff --git a/src/rc/view-agenda-date-row.ui b/src/rc/view-agenda-date-row.ui
new file mode 100644
index 0000000..ca1709b
--- /dev/null
+++ b/src/rc/view-agenda-date-row.ui
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <template class="CaliforniaViewAgendaDateRow" parent="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="date_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_right">16</property>
+ <property name="margin_top">3</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0</property>
+ <property name="label">(date)</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkListBox" id="event_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selection_mode">none</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/src/rc/view-agenda-event-row.ui b/src/rc/view-agenda-event-row.ui
new file mode 100644
index 0000000..c4d05ed
--- /dev/null
+++ b/src/rc/view-agenda-event-row.ui
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <template class="CaliforniaViewAgendaEventRow" parent="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkEventBox" id="time_eventbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="visible_window">False</property>
+ <child>
+ <object class="GtkLabel" id="time_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_right">16</property>
+ <property name="xalign">0</property>
+ <property name="label">(time)</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="summary_eventbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="visible_window">False</property>
+ <child>
+ <object class="GtkLabel" id="summary_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label">(summary)</property>
+ <property name="use_markup">True</property>
+ <property name="ellipsize">end</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="guests_icon">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <property name="tooltip_text" translatable="yes">Event has guests</property>
+ <property name="xpad">4</property>
+ <property name="pixel_size">12</property>
+ <property name="icon_name">system-users-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="recurring_icon">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <property name="tooltip_text" translatable="yes">Event is recurring</property>
+ <property name="xpad">4</property>
+ <property name="pixel_size">12</property>
+ <property name="icon_name">rotation-allowed-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/src/rc/view-agenda-load-more-row.ui b/src/rc/view-agenda-load-more-row.ui
new file mode 100644
index 0000000..b360c96
--- /dev/null
+++ b/src/rc/view-agenda-load-more-row.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <template class="CaliforniaViewAgendaLoadMoreRow" parent="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="hexpand">True</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkLabel" id="showing_until_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label">Showing events until Month, Day, Year.</property>
+ <attributes>
+ <attribute name="style" value="italic"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="load_more_button">
+ <property name="label" translatable="yes" comments="As in, "Load more events"">Load
_More</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_load_more_button_clicked"
object="CaliforniaViewAgendaLoadMoreRow" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/src/toolkit/toolkit-listbox-model.vala b/src/toolkit/toolkit-listbox-model.vala
index 92ef654..10d92a3 100644
--- a/src/toolkit/toolkit-listbox-model.vala
+++ b/src/toolkit/toolkit-listbox-model.vala
@@ -58,6 +58,11 @@ public class ListBoxModel<G> : BaseObject {
public signal void added(G item);
/**
+ * Fired when a GtkListBoxRow is added to the { link listbox}.
+ */
+ public signal void row_added(Gtk.ListBoxRow row, G item);
+
+ /**
* Fired when an item is removed from the { link ListBoxModel}.
*
* @see remove
@@ -65,6 +70,11 @@ public class ListBoxModel<G> : BaseObject {
public signal void removed(G item);
/**
+ * Fired when a GtkListBoxRow is removed from the { link listbox}.
+ */
+ public signal void row_removed(Gtk.ListBoxRow row, G item);
+
+ /**
* Fired when the { link listbox} activates an item.
*
* Gtk.ListBox can activate an item with a double- or single-click, depending on configuration.
@@ -107,6 +117,7 @@ public class ListBoxModel<G> : BaseObject {
* Returns true if the model (and therefore the listbox) were altered due to the addition.
*
* @see added
+ * @see row_added
*/
public bool add(G item) {
if (items.has_key(item))
@@ -115,9 +126,18 @@ public class ListBoxModel<G> : BaseObject {
// item -> Gtk.ListBoxRow, with MutableWidget support
Gtk.ListBoxRow row = new Gtk.ListBoxRow();
Gtk.Widget widget = model_presentation(item);
+
+ // allow for external callers to make the ListBoxRow visible via their supplied widget's
+ // visibility flag ... this is necessary because setting the presentation widget to invisible
+ // leaves the row's visible and taking up a little space for border and margin and such
+ widget.bind_property("visible", row, "visible",
+ BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE);
+
+ // if widget is mutable, watch for that
MutableWidget? mutable = widget as MutableWidget;
if (mutable != null)
mutable.mutated.connect(() => { row.changed(); });
+
row.add(widget);
// mappings
@@ -131,6 +151,7 @@ public class ListBoxModel<G> : BaseObject {
size = size + 1;
added(item);
+ row_added(row, item);
return true;
}
@@ -158,6 +179,7 @@ public class ListBoxModel<G> : BaseObject {
* Returns true if the model (and therefore the listbox) were altered due to the removal.
*
* @see removed
+ * @see row_removed
*/
public bool remove(G item) {
return internal_remove(item, true);
@@ -192,6 +214,7 @@ public class ListBoxModel<G> : BaseObject {
size = (size - 1).clamp(0, int.MAX);
removed(item);
+ row_removed(row, item);
return true;
}
@@ -241,6 +264,16 @@ public class ListBoxModel<G> : BaseObject {
}
row.changed();
+
+ // reset size as filter could have changed contents of list
+ int count = 0;
+ foreach (Gtk.Widget widget in listbox.get_children()) {
+ Gtk.ListBoxRow child = (Gtk.ListBoxRow) widget;
+ if (model_filter == null || model_filter(child.get_data<G>(KEY)))
+ count++;
+ }
+
+ size = count;
}
/**
diff --git a/src/toolkit/toolkit.vala b/src/toolkit/toolkit.vala
index 94c87ae..7ade342 100644
--- a/src/toolkit/toolkit.vala
+++ b/src/toolkit/toolkit.vala
@@ -150,4 +150,19 @@ public void destroy_later(Gtk.Widget widget) {
}, Priority.LOW);
}
+/**
+ * Prevent prelight when a mouse hovers over the widget.
+ *
+ * This operates by preventing the Gtk.StateFlag.PRELIGHT state from being entered. This may have
+ * negative effects for some widgets and should be used with caution.
+ */
+public void prevent_prelight(Gtk.Widget widget) {
+ widget.state_flags_changed.connect(on_state_flags_changed);
+}
+
+private void on_state_flags_changed(Gtk.Widget widget, Gtk.StateFlags old_state_flags) {
+ if ((widget.get_state_flags() & Gtk.StateFlags.PRELIGHT) != 0)
+ widget.unset_state_flags(Gtk.StateFlags.PRELIGHT);
+}
+
}
diff --git a/src/view/agenda/agenda-controller.vala b/src/view/agenda/agenda-controller.vala
new file mode 100644
index 0000000..3b85944
--- /dev/null
+++ b/src/view/agenda/agenda-controller.vala
@@ -0,0 +1,309 @@
+/* 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.View.Agenda {
+
+public class Controller : BaseObject, View.Controllable {
+ public const string PROP_CURRENT_SPAN = "current-span";
+
+ public const string VIEW_ID = "agenda";
+
+ private class Container : Gtk.ScrolledWindow, View.Container {
+ private unowned Controllable _owner;
+ public unowned Controllable owner { get { return _owner; } }
+
+ public Container(Controller controller, Gtk.Widget child) {
+ _owner = controller;
+
+ add_with_viewport(child);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public string id { get { return VIEW_ID; } }
+
+ /**
+ * @inheritDoc
+ */
+ public string title { get { return _("Agenda"); } }
+
+ /**
+ * @inheritDoc
+ */
+ public string current_label { get; protected set; }
+
+ /**
+ * @inheritDoc
+ */
+ public bool is_viewing_today { get; protected set; }
+
+ /**
+ * @inheritDoc
+ */
+ public ChronologyMotion motion { get { return ChronologyMotion.VERTICAL; } }
+
+ /**
+ * @inheritDoc
+ */
+ public bool in_transition { get; protected set; }
+
+ /**
+ * { link View.Palette} for the entire view.
+ */
+ public View.Palette palette { get; private set; }
+
+ /**
+ * Current { link Calendar.DateSpan} being displayed.
+ */
+ public Calendar.DateSpan current_span { get; private set; }
+
+ private Container container;
+ private Backing.CalendarSubscriptionManager? subscriptions = null;
+ private Gtk.ListBox listbox = new Gtk.ListBox();
+ private Toolkit.ListBoxModel<Calendar.Date> listbox_model;
+ private LoadMoreRow load_more_row;
+
+ public Controller(View.Palette palette) {
+ this.palette = palette;
+
+ container = new Container(this, listbox);
+ listbox_model = new Toolkit.ListBoxModel<Calendar.Date>(listbox, model_presentation);
+
+ // Don't prelight the DateRows, as they can't be selected or activated
+ listbox_model.row_added.connect((row, item) => {
+ Toolkit.prevent_prelight(row);
+ });
+
+ listbox.selection_mode = Gtk.SelectionMode.NONE;
+ listbox.activate_on_single_click = false;
+
+ // this will initialize current_span
+ reset_subscriptions(
+ new Calendar.DateSpan(
+ Calendar.System.today,
+ Calendar.System.today.adjust_by(2, Calendar.DateUnit.MONTH)
+ )
+ );
+
+ // LoadMoreRow is persistent and always sorts to the end of the list (see model_presentation)
+ // (need to add after setting current_span in reset_subscriptions)
+ load_more_row = new LoadMoreRow(this);
+ load_more_row.load_more.connect(on_load_more);
+ listbox_model.add(Calendar.Date.latest);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public View.Container get_container() {
+ return container;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public void next() {
+ reduce_subscriptions_start(current_span.start_date.adjust_by(1, Calendar.DateUnit.DAY));
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public void previous() {
+ expand_subscriptions(current_span.start_date.adjust_by(-1, Calendar.DateUnit.DAY));
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public void today() {
+ reset_subscriptions(new Calendar.DateSpan(Calendar.System.today, current_span.end_date));
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public void unselect_all() {
+ // no notion of selection in Agenda view
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public Gtk.Widget? get_widget_for_date(Calendar.Date date) {
+ return listbox_model.get_widget_for_item(date);
+ }
+
+ private Gtk.Widget model_presentation(Calendar.Date date) {
+ if (date.equal_to(Calendar.Date.latest))
+ return load_more_row;
+
+ DateRow date_row = new DateRow(this, date);
+ date_row.empty.connect(on_date_row_empty);
+
+ return date_row;
+ }
+
+ private void on_date_row_empty(DateRow date_row) {
+ listbox_model.remove(date_row.date);
+ }
+
+ private void on_load_more() {
+ expand_subscriptions(current_span.end_date.adjust_by(1, Calendar.DateUnit.MONTH));
+ }
+
+ private Iterable<DateRow> traverse_date_rows() {
+ return traverse<Calendar.Date>(listbox_model.all())
+ .map_nonnull<DateRow>(date => listbox_model.get_widget_for_item(date) as DateRow);
+ }
+
+ // Make existing DateRow widgets visible depending on if they're in the current_span; don't
+ // remove them to allow them to continue to receive event notifications in case the window is
+ // widened again to show them
+ private void show_hide_date_rows() {
+ traverse_date_rows()
+ .iterate(date_row => date_row.visible = date_row.date in current_span);
+ }
+
+ private void clear_date_rows() {
+ traverse_date_rows()
+ .iterate(date_row => listbox_model.remove(date_row.date));
+ }
+
+ private void reset_subscriptions(Calendar.DateSpan new_span) {
+ current_span = new_span;
+
+ clear_date_rows();
+
+ subscriptions = new Backing.CalendarSubscriptionManager(
+ current_span.to_exact_time_span(Calendar.Timezone.local));
+
+ subscriptions.calendar_added.connect(on_calendar_added);
+ subscriptions.calendar_removed.connect(on_calendar_removed);
+ subscriptions.instance_added.connect(on_instance_added_or_altered);
+ subscriptions.instance_removed.connect(on_instance_removed);
+
+ subscriptions.start_async.begin();
+
+ update_view_details();
+ }
+
+ private void expand_subscriptions(Calendar.Date expansion) {
+ current_span = current_span.expand(expansion);
+
+ // make previously invisible widgets (due to window reduction) visible again if in new span
+ show_hide_date_rows();
+
+ // to avoid adding a lot of little expansions (which is expensive), add them a month at a
+ // time ... first check if subscription expansion even necessary, and if so, on which ends
+ // of the span ... first, convert to DateSpan
+ Calendar.DateSpan sub_span = new Calendar.DateSpan.from_exact_time_span(
+ subscriptions.window.to_timezone(Calendar.Timezone.local));
+
+ bool expanded = false;
+
+ // if necessary, walk the subscription start date back one month from requested date
+ if (!(current_span.start_date in sub_span)) {
+ Calendar.Date new_sub_start = sub_span.start_date.adjust_by(-1, Calendar.DateUnit.MONTH);
+ if (current_span.start_date.compare_to(new_sub_start) < 0)
+ new_sub_start = current_span.start_date;
+
+ subscriptions.expand_window_async.begin(
+ new_sub_start.to_exact_time_span(Calendar.Timezone.local).start_exact_time);
+ expanded = true;
+ }
+
+ // do the same for the subscription end date
+ if (!(current_span.end_date in sub_span)) {
+ Calendar.Date new_sub_end = sub_span.end_date.adjust_by(1, Calendar.DateUnit.MONTH);
+ if (current_span.end_date.compare_to(new_sub_end) > 0)
+ new_sub_end = current_span.end_date;
+
+ subscriptions.expand_window_async.begin(
+ new_sub_end.to_exact_time_span(Calendar.Timezone.local).end_exact_time);
+ expanded = true;
+ }
+
+ if (expanded)
+ debug("Agenda subscription window expanded to %s", subscriptions.window.to_string());
+
+ update_view_details();
+ }
+
+ private void reduce_subscriptions_start(Calendar.Date new_start) {
+ current_span = current_span.reduce_from_start(new_start);
+
+ // make previously invisible widgets (due to window reduction) visible again if in new span
+ show_hide_date_rows();
+
+ update_view_details();
+ }
+
+ private void update_view_details() {
+ current_label = current_span.start_date.to_pretty_string(
+ Calendar.Date.PrettyFlag.ABBREV
+ | Calendar.Date.PrettyFlag.INCLUDE_YEAR
+ | Calendar.Date.PrettyFlag.NO_TODAY
+ );
+ is_viewing_today = current_span.start_date.equal_to(Calendar.System.today);
+ }
+
+ private void on_calendar_added(Backing.CalendarSource calendar) {
+ calendar.notify[Backing.Source.PROP_VISIBLE].connect(on_calendar_visibility_changed);
+ }
+
+ private void on_calendar_removed(Backing.CalendarSource calendar) {
+ calendar.notify[Backing.Source.PROP_VISIBLE].disconnect(on_calendar_visibility_changed);
+ }
+
+ private void on_calendar_visibility_changed(Object o, ParamSpec pspec) {
+ Backing.CalendarSource calendar = (Backing.CalendarSource) o;
+
+ traverse_date_rows()
+ .iterate(date_row => date_row.notify_calendar_visibility_changed(calendar));
+ }
+
+ private void on_instance_added_or_altered(Component.Instance instance) {
+ Component.Event? event = instance as Component.Event;
+ if (event == null)
+ return;
+
+ foreach (Calendar.Date date in event.get_event_date_span(Calendar.Timezone.local)) {
+ // Add dates on-demand; not all dates are listed in Agenda view
+ if (!listbox_model.contains(date))
+ listbox_model.add(date);
+
+ DateRow date_row = (DateRow) listbox_model.get_widget_for_item(date);
+ date_row.add_event(event);
+
+ // possible to be notified of Event outside of current_span; see reduce_subscriptions()
+ date_row.visible = date in current_span;
+ }
+ }
+
+ private void on_instance_removed(Component.Instance instance) {
+ Component.Event? event = instance as Component.Event;
+ if (event == null)
+ return;
+
+ foreach (Calendar.Date date in event.get_event_date_span(Calendar.Timezone.local)) {
+ if (!listbox_model.contains(date))
+ continue;
+
+ DateRow date_row = (DateRow) listbox_model.get_widget_for_item(date);
+ date_row.remove_event(event);
+ }
+ }
+
+ public override string to_string() {
+ return classname;
+ }
+}
+
+}
+
diff --git a/src/view/agenda/agenda-date-row.vala b/src/view/agenda/agenda-date-row.vala
new file mode 100644
index 0000000..18781c3
--- /dev/null
+++ b/src/view/agenda/agenda-date-row.vala
@@ -0,0 +1,115 @@
+/* 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.View.Agenda {
+
+[GtkTemplate (ui = "/org/yorba/california/rc/view-agenda-date-row.ui")]
+private class DateRow : Gtk.Box {
+ private const Calendar.Date.PrettyFlag DATE_PRETTY_FLAGS =
+ Calendar.Date.PrettyFlag.INCLUDE_OTHER_YEAR;
+
+ private static Gtk.SizeGroup date_label_size_group;
+
+ public Calendar.Date date { get; private set; }
+
+ public int size { get { return listbox_model.size; } }
+
+ [GtkChild]
+ private Gtk.Label date_label;
+
+ [GtkChild]
+ private Gtk.ListBox event_listbox;
+
+ private unowned Controller owner;
+ private Toolkit.ListBoxModel<Component.Event> listbox_model;
+
+ public signal void empty();
+
+ public DateRow(Controller owner, Calendar.Date date) {
+ this.owner = owner;
+ this.date = date;
+
+ listbox_model = new Toolkit.ListBoxModel<Component.Event>(event_listbox, model_presentation,
+ model_filter);
+ listbox_model.notify[Toolkit.ListBoxModel.PROP_SIZE].connect(on_listbox_model_size_changed);
+
+ // Don't prelight the DateRows, as they can't be selected or activated
+ listbox_model.row_added.connect((row, item) => {
+ Toolkit.prevent_prelight(row);
+ });
+
+ // all date labels are same width
+ date_label_size_group.add_widget(date_label);
+
+ // Because some date text labels are relative (i.e. "Today"), refresh when the date changes
+ Calendar.System.instance.today_changed.connect(update_ui);
+
+ update_ui();
+ }
+
+ ~DateRow() {
+ Calendar.System.instance.today_changed.disconnect(update_ui);
+ }
+
+ internal static void init() {
+ date_label_size_group = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL);
+ }
+
+ internal static void terminate() {
+ date_label_size_group = null;
+ }
+
+ private void update_ui() {
+ date_label.label = date.to_pretty_string(DATE_PRETTY_FLAGS);
+ }
+
+ private void on_listbox_model_size_changed() {
+ if (listbox_model.size == 0)
+ empty();
+ }
+
+ public void add_event(Component.Event event) {
+ if (!listbox_model.add(event))
+ return;
+
+ // watch for date changes, which affect if the event is represented here
+ event.notify[Component.Event.PROP_DATE_SPAN].connect(on_event_date_changed);
+ event.notify[Component.Event.PROP_EXACT_TIME_SPAN].connect(on_event_date_changed);
+ }
+
+ public void remove_event(Component.Event event) {
+ if (!listbox_model.remove(event))
+ return;
+
+ event.notify[Component.Event.PROP_DATE_SPAN].disconnect(on_event_date_changed);
+ event.notify[Component.Event.PROP_EXACT_TIME_SPAN].disconnect(on_event_date_changed);
+ }
+
+ private void on_event_date_changed(Object o, ParamSpec pspec) {
+ Component.Event event = (Component.Event) o;
+
+ if (!(date in event.get_event_date_span(Calendar.Timezone.local)))
+ remove_event(event);
+ }
+
+ public void notify_calendar_visibility_changed(Backing.CalendarSource calendar_source) {
+ foreach (Component.Event event in listbox_model.all()) {
+ if (event.calendar_source == calendar_source)
+ listbox_model.mutated(event);
+ }
+ }
+
+ private Gtk.Widget model_presentation(Component.Event event) {
+ return new EventRow(owner, event);
+ }
+
+ private bool model_filter(Component.Event event) {
+ return event.calendar_source.visible;
+ }
+}
+
+}
+
diff --git a/src/view/agenda/agenda-event-row.vala b/src/view/agenda/agenda-event-row.vala
new file mode 100644
index 0000000..2558dae
--- /dev/null
+++ b/src/view/agenda/agenda-event-row.vala
@@ -0,0 +1,160 @@
+/* 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.View.Agenda {
+
+[GtkTemplate (ui = "/org/yorba/california/rc/view-agenda-event-row.ui")]
+private class EventRow : Gtk.Box, Toolkit.MutableWidget {
+ private const Calendar.WallTime.PrettyFlag TIME_PRETTY_FLAGS = Calendar.WallTime.PrettyFlag.NONE;
+
+ private static Gtk.SizeGroup time_label_size_group;
+
+ public new Component.Event event { get; private set; }
+
+ [GtkChild]
+ private Gtk.EventBox time_eventbox;
+
+ [GtkChild]
+ private Gtk.Label time_label;
+
+ [GtkChild]
+ private Gtk.EventBox summary_eventbox;
+
+ [GtkChild]
+ private Gtk.Label summary_label;
+
+ [GtkChild]
+ private Gtk.Image guests_icon;
+
+ [GtkChild]
+ private Gtk.Image recurring_icon;
+
+ private Controller owner;
+ private Toolkit.ButtonConnector button_connector = new Toolkit.ButtonConnector();
+ private Toolkit.MotionConnector motion_connector = new Toolkit.MotionConnector();
+
+ public EventRow(Controller owner, Component.Event event) {
+ this.owner = owner;
+ this.event = event;
+
+ // all time labels are the same width
+ time_label_size_group.add_widget(time_label);
+
+ // capture motion and mouse clicks for both labels
+ button_connector.connect_to(time_eventbox);
+ button_connector.connect_to(summary_eventbox);
+ motion_connector.connect_to(time_eventbox);
+ motion_connector.connect_to(summary_eventbox);
+
+ button_connector.clicked.connect(on_event_clicked);
+ button_connector.double_clicked.connect(on_event_double_clicked);
+ motion_connector.entered.connect(on_event_entered_exited);
+ motion_connector.exited.connect(on_event_entered_exited);
+ motion_connector.motion.connect(on_event_motion);
+
+ // watch for changes to the event
+ event.notify[Component.Event.PROP_SUMMARY].connect(update_ui);
+ event.notify[Component.Event.PROP_DATE_SPAN].connect(update_ui);
+ event.notify[Component.Event.PROP_EXACT_TIME_SPAN].connect(update_ui);
+ event.notify[Component.Event.PROP_LOCATION].connect(update_ui);
+ event.notify[Component.Instance.PROP_ATTENDEES].connect(update_ui);
+ event.notify[Component.Instance.PROP_ORGANIZERS].connect(update_ui);
+ event.notify[Component.Instance.PROP_RRULE].connect(update_ui);
+
+ // watch for changes to the calendar (which is immutable for the lifetime of the Event
+ // instances)
+ event.calendar_source.notify[Backing.Source.PROP_COLOR].connect(update_ui);
+
+ // .. and assume that all property changes cause sort-order changes (no reliable way to
+ // know exactly when for now)
+ event.altered.connect(() => { mutated(); });
+
+ // .. date formatting changes
+ Calendar.System.instance.is_24hr_changed.connect(update_ui);
+ Calendar.System.instance.zone_changed.connect(update_ui);
+ Calendar.System.instance.timezone_changed.connect(update_ui);
+
+ update_ui();
+ }
+
+ ~EventRow() {
+ event.notify[Component.Event.PROP_SUMMARY].disconnect(update_ui);
+ event.notify[Component.Event.PROP_DATE_SPAN].disconnect(update_ui);
+ event.notify[Component.Event.PROP_EXACT_TIME_SPAN].disconnect(update_ui);
+ event.notify[Component.Event.PROP_LOCATION].disconnect(update_ui);
+ event.notify[Component.Instance.PROP_ATTENDEES].disconnect(update_ui);
+ event.notify[Component.Instance.PROP_ORGANIZERS].disconnect(update_ui);
+ event.notify[Component.Instance.PROP_RRULE].disconnect(update_ui);
+
+ event.calendar_source.notify[Backing.Source.PROP_COLOR].disconnect(update_ui);
+
+ Calendar.System.instance.is_24hr_changed.disconnect(update_ui);
+ Calendar.System.instance.zone_changed.disconnect(update_ui);
+ Calendar.System.instance.timezone_changed.disconnect(update_ui);
+ }
+
+ internal static void init() {
+ time_label_size_group = new Gtk.SizeGroup(Gtk.SizeGroupMode.HORIZONTAL);
+ }
+
+ internal static void terminate() {
+ time_label_size_group = null;
+ }
+
+ private void update_ui() {
+ if (event.is_all_day) {
+ time_label.label = _("All day");
+ } else {
+ Calendar.ExactTimeSpan time_span = event.exact_time_span.to_timezone(Calendar.Timezone.local);
+
+ // hex value is an endash
+ time_label.label = "%s – %s".printf(
+ time_span.start_exact_time.to_wall_time().to_pretty_string(TIME_PRETTY_FLAGS),
+ time_span.end_exact_time.to_wall_time().to_pretty_string(TIME_PRETTY_FLAGS)
+ );
+ }
+
+ if (!String.is_empty(event.location)) {
+ // hex value is an endash
+ summary_label.label = "<span color=\"%s\">%s</span> – %s".printf(
+ event.calendar_source.color, event.summary, event.location);
+ } else {
+ summary_label.label = "<span color=\"%s\">%s</span>".printf(
+ event.calendar_source.color, event.summary);
+ }
+
+ // only show guests icon if attendees include someone not an organizer
+ guests_icon.visible = traverse<Component.Person>(event.attendees)
+ .filter(person => !event.organizers.contains(person))
+ .is_nonempty();
+ recurring_icon.visible = event.rrule != null;
+ }
+
+ private bool on_event_clicked(Toolkit.ButtonEvent details) {
+ owner.request_display_event(event, details.widget, details.press_point);
+
+ return Toolkit.STOP;
+ }
+
+ private bool on_event_double_clicked(Toolkit.ButtonEvent details) {
+ owner.request_edit_event(event, details.widget, details.press_point);
+
+ return Toolkit.STOP;
+ }
+
+ private void on_event_entered_exited(Toolkit.MotionEvent details) {
+ // when entering or leaving cell, reset the cursor
+ Toolkit.set_toplevel_cursor(details.widget, null);
+ }
+
+ private void on_event_motion(Toolkit.MotionEvent details) {
+ // if hovering over an event, show the "hyperlink" cursor
+ Toolkit.set_toplevel_cursor(details.widget, Gdk.CursorType.HAND1);
+ }
+}
+
+}
+
diff --git a/src/view/agenda/agenda-load-more-row.vala b/src/view/agenda/agenda-load-more-row.vala
new file mode 100644
index 0000000..5c1d65a
--- /dev/null
+++ b/src/view/agenda/agenda-load-more-row.vala
@@ -0,0 +1,48 @@
+/* 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.View.Agenda {
+
+[GtkTemplate (ui = "/org/yorba/california/rc/view-agenda-load-more-row.ui")]
+private class LoadMoreRow : Gtk.Box {
+ private unowned Controller owner;
+
+ [GtkChild]
+ private Gtk.Label showing_until_label;
+
+ public signal void load_more();
+
+ public LoadMoreRow(Controller owner) {
+ this.owner = owner;
+
+ owner.notify[Controller.PROP_CURRENT_SPAN].connect(update_ui);
+
+ update_ui();
+ }
+
+ ~LoadMoreRow() {
+ owner.notify[Controller.PROP_CURRENT_SPAN].disconnect(update_ui);
+ }
+
+ private void update_ui() {
+ string date_str = owner.current_span.end_date.to_pretty_string(
+ Calendar.Date.PrettyFlag.INCLUDE_YEAR
+ | Calendar.Date.PrettyFlag.NO_DAY_OF_WEEK
+ | Calendar.Date.PrettyFlag.NO_TODAY
+ );
+
+ // %s is a date, i.e. "Showing events until December 5, 2014"
+ showing_until_label.label = _("Showing events until %s").printf(date_str);
+ }
+
+ [GtkCallback]
+ private void on_load_more_button_clicked() {
+ load_more();
+ }
+}
+
+}
+
diff --git a/src/view/agenda/agenda.vala b/src/view/agenda/agenda.vala
new file mode 100644
index 0000000..66cfb7a
--- /dev/null
+++ b/src/view/agenda/agenda.vala
@@ -0,0 +1,35 @@
+/* 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.View.Agenda {
+
+private int init_count = 0;
+
+public void init() throws Error {
+ if (!Unit.do_init(ref init_count))
+ return;
+
+ Toolkit.init();
+ Calendar.init();
+
+ // internal unit init
+ DateRow.init();
+ EventRow.init();
+}
+
+public void terminate() {
+ if (!Unit.do_terminate(ref init_count))
+ return;
+
+ EventRow.terminate();
+ DateRow.terminate();
+
+ Calendar.terminate();
+ Toolkit.terminate();
+}
+
+}
+
diff --git a/src/view/month/month-controller.vala b/src/view/month/month-controller.vala
index f70d30c..5a588a0 100644
--- a/src/view/month/month-controller.vala
+++ b/src/view/month/month-controller.vala
@@ -68,11 +68,6 @@ public class Controller : BaseObject, View.Controllable {
/**
* @inheritDoc
*/
- public Calendar.Date default_date { get; protected set; }
-
- /**
- * @inheritDoc
- */
public bool in_transition { get; protected set; }
/**
diff --git a/src/view/view.vala b/src/view/view.vala
index 584bfaa..22ea522 100644
--- a/src/view/view.vala
+++ b/src/view/view.vala
@@ -24,12 +24,14 @@ public void init() throws Error {
View.Common.init();
View.Month.init();
View.Week.init();
+ View.Agenda.init();
}
public void terminate() {
if (!Unit.do_terminate(ref init_count))
return;
+ View.Agenda.terminate();
View.Week.terminate();
View.Month.terminate();
View.Common.terminate();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]