[california/wip/732930-explain] Added explanation to recurring editor
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california/wip/732930-explain] Added explanation to recurring editor
- Date: Fri, 5 Sep 2014 21:50:28 +0000 (UTC)
commit 4e9a7c40116938c1c643282f479b0ba274796127
Author: Jim Nelson <jim yorba org>
Date: Fri Sep 5 14:49:58 2014 -0700
Added explanation to recurring editor
src/calendar/calendar-date.vala | 13 ++++-
src/collection/collection-iterable.vala | 9 +++
src/collection/collection.vala | 13 ++++
src/component/component-recurrence-rule.vala | 91 +++++++++++++++++++++-----
src/host/host-create-update-recurring.vala | 46 +++++++++++---
src/host/host-show-event.vala | 4 +-
src/rc/create-update-recurring.ui | 27 +++++++-
7 files changed, 173 insertions(+), 30 deletions(-)
---
diff --git a/src/calendar/calendar-date.vala b/src/calendar/calendar-date.vala
index 2f8da5c..1bf41ce 100644
--- a/src/calendar/calendar-date.vala
+++ b/src/calendar/calendar-date.vala
@@ -41,10 +41,17 @@ public class Date : Unit<Date>, Gee.Comparable<Date>, Gee.Hashable<Date> {
*/
COMPACT,
/**
- * Indicates that the year should be included in the return date string.
+ * Indicates that the year should be included in the returned date string.
*/
INCLUDE_YEAR,
/**
+ * Indicates that the year should be included in the returned string if it's not the current
+ * year.
+ *
+ * { link INCLUDE_YEAR} overrides this flag to always return the year in the string.
+ */
+ INCLUDE_OTHER_YEAR,
+ /**
* Indicates that the localized string for "Today" should not be used if the date matches
* { link System.today}.
*/
@@ -345,12 +352,16 @@ public class Date : Unit<Date>, Gee.Comparable<Date>, Gee.Hashable<Date> {
bool compact = (flags & PrettyFlag.COMPACT) != 0;
bool abbrev = (flags & PrettyFlag.ABBREV) != 0;
bool with_year = (flags & PrettyFlag.INCLUDE_YEAR) != 0;
+ bool with_other_year = (flags & PrettyFlag.INCLUDE_OTHER_YEAR) != 0;
bool no_today = (flags & PrettyFlag.NO_TODAY) != 0;
bool no_dow = (flags & PrettyFlag.NO_DAY_OF_WEEK) != 0;
if (!no_today && !with_year && equal_to(System.today))
return _("Today");
+ if (!with_year && with_other_year && !year.equal_to(System.today.year))
+ with_year = true;
+
unowned string fmt;
if (abbrev) {
if (no_dow)
diff --git a/src/collection/collection-iterable.vala b/src/collection/collection-iterable.vala
index d51e82d..2e237a1 100644
--- a/src/collection/collection-iterable.vala
+++ b/src/collection/collection-iterable.vala
@@ -256,6 +256,15 @@ public class Iterable<G> : Object {
return true;
}
+ public bool contains_all(Gee.Collection<G> c) {
+ foreach (G g in this) {
+ if (!c.contains(g))
+ return false;
+ }
+
+ return true;
+ }
+
public int count_matching(owned Gee.Predicate<G> f) {
int count = 0;
foreach (G g in this) {
diff --git a/src/collection/collection.vala b/src/collection/collection.vala
index 6557566..1bf5dcd 100644
--- a/src/collection/collection.vala
+++ b/src/collection/collection.vala
@@ -26,6 +26,19 @@ public inline bool is_empty(Gee.Collection? c) {
}
/**
+ * Returns true if the two Collections contains all the same elements and the same number of elements.
+ */
+public bool equal<G>(Gee.Collection<G>? a, Gee.Collection<G>? b) {
+ if (a == b)
+ return true;
+
+ if (size(a) != size(b))
+ return false;
+
+ return traverse<G>(a).contains_all(b);
+}
+
+/**
* Returns the size of the Collection, zero if null.
*/
public inline int size(Gee.Collection? c) {
diff --git a/src/component/component-recurrence-rule.vala b/src/component/component-recurrence-rule.vala
index 8a72ce2..4c86e24 100644
--- a/src/component/component-recurrence-rule.vala
+++ b/src/component/component-recurrence-rule.vala
@@ -20,6 +20,14 @@ public class RecurrenceRule : BaseObject {
public const string PROP_INTERVAL = "interval";
public const string PROP_FIRST_OF_WEEK = "first-of-week";
+ private const Calendar.Date.PrettyFlag UNTIL_DATE_PRETTY_FLAGS =
+ Calendar.Date.PrettyFlag.ABBREV
+ | Calendar.Date.PrettyFlag.NO_DAY_OF_WEEK
+ | Calendar.Date.PrettyFlag.INCLUDE_OTHER_YEAR;
+
+ private const Calendar.WallTime.PrettyFlag UNTIL_TIME_PRETTY_FLAGS =
+ Calendar.WallTime.PrettyFlag.NONE;
+
/**
* Enumeration of various BY rules (BYSECOND, BYMINUTE, etc.)
*/
@@ -583,10 +591,10 @@ public class RecurrenceRule : BaseObject {
*
* Returns null if the RRULE is beyond the comprehension of this parser.
*/
- public string? explain() {
+ public string? explain(Calendar.Date start_date) {
switch (freq) {
case iCal.icalrecurrencetype_frequency.DAILY_RECURRENCE:
- return explain_simple(ngettext("day", "%d days", interval).printf(interval));
+ return explain_daily(ngettext("day", "%d days", interval).printf(interval));
case iCal.icalrecurrencetype_frequency.WEEKLY_RECURRENCE:
return explain_weekly(ngettext("week", "%d weeks", interval).printf(interval));
@@ -608,7 +616,7 @@ public class RecurrenceRule : BaseObject {
return explain_monthly_bymonthday(unit);
case iCal.icalrecurrencetype_frequency.YEARLY_RECURRENCE:
- return explain_simple(ngettext("year", "%d years", interval).printf(interval));
+ return explain_yearly(ngettext("year", "%d years", interval).printf(interval), start_date);
default:
return null;
@@ -616,7 +624,7 @@ public class RecurrenceRule : BaseObject {
}
- private string explain_simple(string units) {
+ private string explain_daily(string units) {
if (count > 0) {
// As in, "Repeats every day, 2 times"
return _("Repeats every %s, %s").printf(units,
@@ -627,15 +635,15 @@ public class RecurrenceRule : BaseObject {
if (until_date != null) {
// As in, "Repeats every week until Sept. 2, 2014"
return _("Repeats every %s until %s").printf(units,
- until_date.to_pretty_string(Calendar.Date.PrettyFlag.INCLUDE_YEAR)
+ until_date.to_pretty_string(UNTIL_DATE_PRETTY_FLAGS)
);
}
if (until_exact_time != null) {
// As in, "Repeats every month until Sept. 2, 2014, 8:00pm"
return _("Repeats every %s until %s, %s").printf(units,
- until_exact_time.to_pretty_date_string(Calendar.Date.PrettyFlag.INCLUDE_YEAR),
- until_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE)
+ until_exact_time.to_pretty_date_string(UNTIL_DATE_PRETTY_FLAGS),
+ until_exact_time.to_pretty_time_string(UNTIL_TIME_PRETTY_FLAGS)
);
}
@@ -656,10 +664,24 @@ public class RecurrenceRule : BaseObject {
dows.add(dow);
}
- // must be one to work
+ // must be at least one to work
if (dows.size == 0)
return null;
+ // look for expressible patterns
+ if (dows.size == Calendar.DayOfWeek.COUNT)
+ return _("every day");
+
+ Gee.Collection<Calendar.DayOfWeek> weekend_days =
+ from_array<Calendar.DayOfWeek>(Calendar.DayOfWeek.weekend_days).to_array_list();
+ if (Collection.equal<Calendar.DayOfWeek>(weekend_days, dows))
+ return _("weekend days");
+
+ Gee.Collection<Calendar.DayOfWeek> weekdays =
+ from_array<Calendar.DayOfWeek>(Calendar.DayOfWeek.weekdays).to_array_list();
+ if (Collection.equal<Calendar.DayOfWeek>(weekdays, dows))
+ return _("weekdays");
+
// assemble a text list of days
StringBuilder days_of_the_week = new StringBuilder();
bool first = true;
@@ -669,7 +691,7 @@ public class RecurrenceRule : BaseObject {
days_of_the_week.append(_(", "));
}
- days_of_the_week.append(dow.full_name);
+ days_of_the_week.append(dow.abbrev_name);
first = false;
}
@@ -696,15 +718,15 @@ public class RecurrenceRule : BaseObject {
if (until_date != null) {
// As in, "Repeats every week on Thursday until Sept. 2, 2014"
return _("Repeats every %s on %s until %s").printf(units, days_of_the_week,
- until_date.to_pretty_string(Calendar.Date.PrettyFlag.INCLUDE_YEAR)
+ until_date.to_pretty_string(UNTIL_DATE_PRETTY_FLAGS)
);
}
if (until_exact_time != null) {
// As in, "Repeats every week on Friday, Saturday until Sept. 2, 2014, 8:00pm"
return _("Repeats every %s on %s until %s, %s").printf(units, days_of_the_week,
- until_exact_time.to_pretty_date_string(Calendar.Date.PrettyFlag.INCLUDE_YEAR),
- until_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE)
+ until_exact_time.to_pretty_date_string(UNTIL_DATE_PRETTY_FLAGS),
+ until_exact_time.to_pretty_time_string(UNTIL_TIME_PRETTY_FLAGS)
);
}
@@ -773,15 +795,15 @@ public class RecurrenceRule : BaseObject {
if (until_date != null) {
// As in, "Repeats every month on the second Monday until Sept. 2, 2014"
return _("Repeats every %s on the %s until %s").printf(units, day,
- until_date.to_pretty_string(Calendar.Date.PrettyFlag.INCLUDE_YEAR)
+ until_date.to_pretty_string(UNTIL_DATE_PRETTY_FLAGS)
);
}
if (until_exact_time != null) {
// As in, "Repeats every month on the last Friday until Sept. 2, 2014, 8:00pm"
return _("Repeats every %s on the %s until %s, %s").printf(units, day,
- until_exact_time.to_pretty_date_string(Calendar.Date.PrettyFlag.INCLUDE_YEAR),
- until_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE)
+ until_exact_time.to_pretty_date_string(UNTIL_DATE_PRETTY_FLAGS),
+ until_exact_time.to_pretty_time_string(UNTIL_TIME_PRETTY_FLAGS)
);
}
@@ -808,15 +830,15 @@ public class RecurrenceRule : BaseObject {
if (until_date != null) {
// As in, "Repeats every month on day 21 until Sept. 2, 2014"
return _("Repeats every %s on %s until %s").printf(units, day,
- until_date.to_pretty_string(Calendar.Date.PrettyFlag.INCLUDE_YEAR)
+ until_date.to_pretty_string(UNTIL_DATE_PRETTY_FLAGS)
);
}
if (until_exact_time != null) {
// As in, "Repeats every month on day 20 until Sept. 2, 2014, 8:00pm"
return _("Repeats every %s on %s until %s, %s").printf(units, day,
- until_exact_time.to_pretty_date_string(Calendar.Date.PrettyFlag.INCLUDE_YEAR),
- until_exact_time.to_pretty_time_string(Calendar.WallTime.PrettyFlag.NONE)
+ until_exact_time.to_pretty_date_string(UNTIL_DATE_PRETTY_FLAGS),
+ until_exact_time.to_pretty_time_string(UNTIL_TIME_PRETTY_FLAGS)
);
}
@@ -824,6 +846,39 @@ public class RecurrenceRule : BaseObject {
return _("Repeats every %s on %s").printf(units, day);
}
+ private string explain_yearly(string units, Calendar.Date start_date) {
+ string date = start_date.to_pretty_string(
+ Calendar.Date.PrettyFlag.NO_DAY_OF_WEEK
+ | Calendar.Date.PrettyFlag.NO_TODAY
+ | Calendar.Date.PrettyFlag.ABBREV
+ );
+
+ if (count > 0) {
+ // As in, "Repeats every year on 3 March 2014, 2 times"
+ return _("Repeats every %s on %s, %s").printf(units, date,
+ ngettext("%d time", "%d times", count).printf(count)
+ );
+ }
+
+ if (until_date != null) {
+ // As in, "Repeats every year on 3 March 2014 until Sept. 2, 2014"
+ return _("Repeats every %s on %s until %s").printf(units, date,
+ until_date.to_pretty_string(UNTIL_DATE_PRETTY_FLAGS)
+ );
+ }
+
+ if (until_exact_time != null) {
+ // As in, "Repeats every year on 3 March 2014 until Sept. 2, 2014, 8:00pm"
+ return _("Repeats every %s on %s until %s, %s").printf(units, date,
+ until_exact_time.to_pretty_date_string(UNTIL_DATE_PRETTY_FLAGS),
+ until_exact_time.to_pretty_time_string(UNTIL_TIME_PRETTY_FLAGS)
+ );
+ }
+
+ // As in, "Repeats every year on 3 March 2014"
+ return _("Repeats every %s on %s").printf(units, date);
+ }
+
public override string to_string() {
return "RRULE %s".printf(freq.to_string());
}
diff --git a/src/host/host-create-update-recurring.vala b/src/host/host-create-update-recurring.vala
index 3e202ac..09b905c 100644
--- a/src/host/host-create-update-recurring.vala
+++ b/src/host/host-create-update-recurring.vala
@@ -95,6 +95,9 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
private Gtk.RadioButton ends_on_radiobutton;
[GtkChild]
+ private Gtk.Label recurring_explanation_label;
+
+ [GtkChild]
private Gtk.Label warning_label;
[GtkChild]
@@ -132,6 +135,10 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
bind_property(PROP_END_DATE, end_date_button, "label", BindingFlags.SYNC_CREATE,
transform_date_to_string);
+ // update recurring explanation when start/end date changes
+ notify[PROP_START_DATE].connect(on_update_explanation);
+ notify[PROP_END_DATE].connect(on_update_explanation);
+
// map on-day checkboxes to days of week
on_day_checkbuttons[Calendar.DayOfWeek.SUN] = sunday_checkbutton;
on_day_checkbuttons[Calendar.DayOfWeek.MON] = monday_checkbutton;
@@ -141,6 +148,10 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
on_day_checkbuttons[Calendar.DayOfWeek.FRI] = friday_checkbutton;
on_day_checkbuttons[Calendar.DayOfWeek.SAT] = saturday_checkbutton;
+ // updating any of them updates the recurring explanation
+ foreach (Gtk.CheckButton checkbutton in on_day_checkbuttons.values)
+ checkbutton.toggled.connect(on_update_explanation);
+
numeric_filter.connect_to(every_entry);
numeric_filter.connect_to(after_entry);
@@ -247,6 +258,8 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
foreach (Gtk.CheckButton checkbutton in on_day_checkbuttons.values)
checkbutton.active = false;
+ update_explanation(master.rrule, master.get_event_date_span(Calendar.Timezone.local).start_date);
+
// set remaining defaults if not a recurring event
if (master.rrule == null) {
repeats_combobox.active = Repeats.DAILY;
@@ -324,6 +337,13 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
warning_label.visible = supported != null;
}
+ private void update_explanation(Component.RecurrenceRule? rrule, Calendar.Date? start_date) {
+ string? explanation = (rrule != null && start_date != null) ? rrule.explain(start_date) : null;
+ recurring_explanation_label.label = explanation;
+ recurring_explanation_label.visible = !String.is_empty(explanation);
+ recurring_explanation_label.no_show_all = String.is_empty(explanation);
+ }
+
// Returns a logging string for why not reported, null if supported
private string? is_supported_rrule() {
// only some frequencies support, and in some of those, certain requirements
@@ -381,6 +401,11 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
}
[GtkCallback]
+ private void on_update_explanation() {
+ update_explanation(make_recurring_checkbutton.active ? make_rrule() : null, start_date);
+ }
+
+ [GtkCallback]
private void on_repeats_combobox_changed() {
on_repeats_combobox_or_every_entry_changed();
}
@@ -458,13 +483,7 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
jump_to_card_by_name(CreateUpdateEvent.ID, event);
}
- private void update_master() {
- if (!make_recurring_checkbutton.active) {
- master.make_recurring(null);
-
- return;
- }
-
+ private Component.RecurrenceRule make_rrule() {
iCal.icalrecurrencetype_frequency freq;
switch (repeats_combobox.active) {
case Repeats.DAILY:
@@ -519,7 +538,9 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
});
}
- start_date = new_start_date;
+ // avoid property change notification, as this can start a signal storm
+ if (!start_date.equal_to(new_start_date))
+ start_date = new_start_date;
}
// set start and end dates (which may actually be date-times, so use adjust)
@@ -556,11 +577,18 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
}
}
+ return rrule;
+ }
+
+ private void update_master() {
// remove EXDATEs and RDATEs, those are not currently supported
master.exdates = null;
master.rdates = null;
- master.make_recurring(rrule);
+ if (!make_recurring_checkbutton.active)
+ master.make_recurring(null);
+ else
+ master.make_recurring(make_rrule());
}
}
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index 0bce204..937606a 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -157,7 +157,9 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
set_label(null, description_text, Markup.linkify(escape(event.description), linkify_delegate));
// recurring explanation (if appropriate)
- string? explanation = (event.rrule != null) ? event.rrule.explain() : null;
+ string? explanation = (event.rrule != null)
+ ? event.rrule.explain(event.get_event_date_span(Calendar.Timezone.local).start_date)
+ : null;
recurring_explanation_label.label = explanation ?? "";
recurring_explanation_label.visible = !String.is_empty(explanation);
recurring_explanation_label.no_show_all = String.is_empty(explanation);
diff --git a/src/rc/create-update-recurring.ui b/src/rc/create-update-recurring.ui
index 1e52fc5..ae1b67a 100644
--- a/src/rc/create-update-recurring.ui
+++ b/src/rc/create-update-recurring.ui
@@ -19,6 +19,7 @@
<property name="use_underline">True</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_update_explanation" object="CaliforniaHostCreateUpdateRecurring"
swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
@@ -137,6 +138,7 @@
<item id="4" translatable="yes">Yearly</item>
</items>
<signal name="changed" handler="on_repeats_combobox_changed"
object="CaliforniaHostCreateUpdateRecurring" swapped="no"/>
+ <signal name="changed" handler="on_update_explanation"
object="CaliforniaHostCreateUpdateRecurring" swapped="no"/>
</object>
<packing>
<property name="left_attach">1</property>
@@ -285,6 +287,7 @@
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_update_explanation"
object="CaliforniaHostCreateUpdateRecurring" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -307,6 +310,7 @@
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
<property name="group">never_radiobutton</property>
+ <signal name="toggled" handler="on_update_explanation"
object="CaliforniaHostCreateUpdateRecurring" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -351,6 +355,7 @@
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">never_radiobutton</property>
+ <signal name="toggled" handler="on_update_explanation"
object="CaliforniaHostCreateUpdateRecurring" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -367,6 +372,7 @@
<property name="width_chars">5</property>
<property name="input_purpose">number</property>
<signal name="changed" handler="on_after_entry_changed"
object="CaliforniaHostCreateUpdateRecurring" swapped="no"/>
+ <signal name="changed" handler="on_update_explanation"
object="CaliforniaHostCreateUpdateRecurring" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -431,6 +437,7 @@
<property name="width_chars">5</property>
<property name="input_purpose">number</property>
<signal name="changed" handler="on_every_entry_changed"
object="CaliforniaHostCreateUpdateRecurring" swapped="no"/>
+ <signal name="changed" handler="on_update_explanation"
object="CaliforniaHostCreateUpdateRecurring" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -516,7 +523,7 @@
</object>
<packing>
<property name="left_attach">0</property>
- <property name="top_attach">3</property>
+ <property name="top_attach">4</property>
<property name="width">2</property>
<property name="height">1</property>
</packing>
@@ -538,6 +545,24 @@
</object>
<packing>
<property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ <property name="width">2</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="recurring_explanation_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label">(empty)</property>
+ <property name="wrap">True</property>
+ <property name="max_width_chars">64</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
<property name="top_attach">2</property>
<property name="width">2</property>
<property name="height">1</property>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]