[california/wip/725786-edit-recurring] Getting close, can create and update recurring events, still some work to be done to clean it up and
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california/wip/725786-edit-recurring] Getting close, can create and update recurring events, still some work to be done to clean it up and
- Date: Wed, 16 Jul 2014 02:32:30 +0000 (UTC)
commit 7a8fcf06be51e423c8027ec130cc24c2568507d8
Author: Jim Nelson <jim yorba org>
Date: Tue Jul 15 19:31:39 2014 -0700
Getting close, can create and update recurring events, still some
work to be done to clean it up and bullet-proof it.
src/Makefile.am | 1 +
src/component/component-date-time.vala | 4 +-
src/component/component-event.vala | 39 +++++++
src/component/component-instance.vala | 2 +
src/component/component-recurrence-rule.vala | 15 ++-
src/host/host-create-update-event.vala | 33 +++++--
src/host/host-create-update-recurring.vala | 148 +++++++++++++++++++++-----
src/host/host-quick-create-event.vala | 2 +-
src/host/host-show-event.vala | 4 +
src/rc/create-update-recurring.ui | 37 -------
src/util/util-numeric.vala | 17 +++
11 files changed, 224 insertions(+), 78 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index c404850..14a1332 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -134,6 +134,7 @@ california_VALASOURCES = \
util/util-gfx.vala \
util/util-markup.vala \
util/util-memory.vala \
+ util/util-numeric.vala \
util/util-scheduled.vala \
util/util-string.vala \
util/util-uri.vala \
diff --git a/src/component/component-date-time.vala b/src/component/component-date-time.vala
index aba1c9f..85825c5 100644
--- a/src/component/component-date-time.vala
+++ b/src/component/component-date-time.vala
@@ -133,10 +133,10 @@ public class DateTime : BaseObject, Gee.Hashable<DateTime>, Gee.Comparable<DateT
public DateTime.rrule_until(iCal.icalrecurrencetype rrule, DateTime dtstart, bool strict)
throws ComponentError {
if (iCal.icaltime_is_null_time(rrule.until) != 0)
- throw new ComponentError.INVALID("DATE-TIME for RRULE UNTIL is null time");
+ throw new ComponentError.UNAVAILABLE("DATE-TIME for RRULE UNTIL is null time");
if (iCal.icaltime_is_valid_time(rrule.until) == 0)
- throw new ComponentError.INVALID("DATE-TIME for RRULE UNTIL is invalid");
+ throw new ComponentError.UNAVAILABLE("DATE-TIME for RRULE UNTIL is invalid");
bool until_is_date = (iCal.icaltime_is_date(rrule.until) != 0);
bool until_is_utc = (iCal.icaltime_is_utc(rrule.until) != 0);
diff --git a/src/component/component-event.vala b/src/component/component-event.vala
index e5a492f..27e13cc 100644
--- a/src/component/component-event.vala
+++ b/src/component/component-event.vala
@@ -249,8 +249,12 @@ public class Event : Instance, Gee.Comparable<Event> {
* @see set_event_exact_time_span
*/
public void set_event_date_span(Calendar.DateSpan date_span) {
+ freeze_notify();
+
this.date_span = date_span;
exact_time_span = null;
+
+ thaw_notify();
}
/**
@@ -261,8 +265,43 @@ public class Event : Instance, Gee.Comparable<Event> {
* @see set_event_date_span
*/
public void set_event_exact_time_span(Calendar.ExactTimeSpan exact_time_span) {
+ freeze_notify();
+
this.exact_time_span = exact_time_span;
date_span = null;
+
+ thaw_notify();
+ }
+
+ /**
+ * Adjusts the dates of an { link Event} while preserving { link WallTime}, if present.
+ *
+ * This will preserve the DATE/DATE-TIME aspect of an Event while adjusting the start and
+ * end { link Calendar.Date}s. If a DATE Event, then this is functionally equivalent to
+ * { link set_event_date_span}. If a DATE-TIME event, then this is like
+ * { link set_event_exact_time_span} but without the hassle of preserving start and end times
+ * while changing the dates.
+ */
+ public void adjust_event_date_span(Calendar.DateSpan date_span) {
+ if (is_all_day) {
+ set_event_date_span(date_span);
+
+ return;
+ }
+
+ Calendar.ExactTime new_start_time = new Calendar.ExactTime(
+ exact_time_span.start_exact_time.tz,
+ date_span.start_date,
+ exact_time_span.start_exact_time.to_wall_time()
+ );
+
+ Calendar.ExactTime new_end_time = new Calendar.ExactTime(
+ exact_time_span.end_exact_time.tz,
+ date_span.end_date,
+ exact_time_span.end_exact_time.to_wall_time()
+ );
+
+ set_event_exact_time_span(new Calendar.ExactTimeSpan(new_start_time, new_end_time));
}
/**
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index 4154669..a57c6d0 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -304,6 +304,8 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
make_recurring(new RecurrenceRule.from_ical(ical_component, false));
} catch (ComponentError comperr) {
// ignored; generally means no RRULE in component
+ if (!(comperr is ComponentError.UNAVAILABLE))
+ debug("Unable to parse RRULE for %s: %s", to_string(), comperr.message);
}
// save own copy of component; no ownership transferrance w/ current bindings
diff --git a/src/component/component-recurrence-rule.vala b/src/component/component-recurrence-rule.vala
index ab3d624..845e94e 100644
--- a/src/component/component-recurrence-rule.vala
+++ b/src/component/component-recurrence-rule.vala
@@ -155,11 +155,16 @@ public class RecurrenceRule : BaseObject {
if (rrule.count > 0) {
set_recurrence_count(rrule.count);
} else {
- Component.DateTime date_time = new DateTime.rrule_until(rrule, dtstart, strict);
- if (date_time.is_date)
- set_recurrence_end_date(date_time.to_date());
- else
- set_recurrence_end_exact_time(date_time.to_exact_time());
+ try {
+ Component.DateTime date_time = new DateTime.rrule_until(rrule, dtstart, strict);
+ if (date_time.is_date)
+ set_recurrence_end_date(date_time.to_date());
+ else
+ set_recurrence_end_exact_time(date_time.to_exact_time());
+ } catch (ComponentError comperr) {
+ if (!(comperr is ComponentError.UNAVAILABLE))
+ throw comperr;
+ }
}
switch (rrule.week_start) {
diff --git a/src/host/host-create-update-event.vala b/src/host/host-create-update-event.vala
index 2f5d3be..68f92ae 100644
--- a/src/host/host-create-update-event.vala
+++ b/src/host/host-create-update-event.vala
@@ -66,6 +66,7 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
public bool is_update { get; set; default = false; }
private new Component.Event event = new Component.Event.blank();
+ private Component.RecurrenceRule? rrule = null;
private Gee.HashMap<string, Calendar.WallTime> time_map = new Gee.HashMap<string, Calendar.WallTime>();
private Backing.CalendarSource? original_calendar_source;
private Toolkit.ComboBoxTextModel<Backing.CalendarSource> calendar_model;
@@ -141,17 +142,24 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
}
public void jumped_to(Toolkit.Card? from, Toolkit.Card.Jump reason, Value? message) {
- // if jumping back, message is null but want to keep using current Event
- if (reason != Toolkit.Card.Jump.BACK) {
- if (message != null) {
- event = message as Component.Event;
- assert(event != null);
- } else {
- event = new Component.Event.blank();
+ bool update = false;
+
+ if (message != null) {
+ if (message.type() == typeof (Component.Event)) {
+ event = (Component.Event) message;
+ update = true;
+ } else if (message.type() == typeof (Component.RecurrenceRule)) {
+ rrule = (Component.RecurrenceRule) message;
}
+ } else if (event == null) {
+ event = new Component.Event.blank();
+ update = true;
}
- update_controls();
+ assert(event != null);
+
+ if (update)
+ update_controls();
}
private void update_controls() {
@@ -178,6 +186,13 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
selected_date_span = new Calendar.DateSpan(Calendar.System.today, Calendar.System.today);
initial_start_time = Calendar.System.now.to_wall_time();
initial_end_time = Calendar.System.now.adjust_time(1, Calendar.TimeUnit.HOUR).to_wall_time();
+
+ // set in Component.Event as well, to at least initialize it for use elsewhere while
+ // editing (such as the RRULE)
+ event.set_event_exact_time_span(new Calendar.ExactTimeSpan(
+ new Calendar.ExactTime(Calendar.Timezone.local, Calendar.System.today, initial_start_time),
+ new Calendar.ExactTime(Calendar.Timezone.local, Calendar.System.today, initial_end_time)
+ ));
}
// initialize start and end time controls (as in, wall clock time)
@@ -311,6 +326,8 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
);
}
+ event.make_recurring(rrule);
+
if (is_update)
update_event_async.begin(null);
else
diff --git a/src/host/host-create-update-recurring.vala b/src/host/host-create-update-recurring.vala
index 9e42a74..1ff0f3b 100644
--- a/src/host/host-create-update-recurring.vala
+++ b/src/host/host-create-update-recurring.vala
@@ -23,6 +23,37 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
YEARLY = 4
}
+ /**
+ * The message that must be passed to this Card when jumping to it.
+ *
+ * The proper master instance will be extracted. If the RRULE is provided, that will be
+ * used by the card, otherwise the master's RRULE (if any) will be used.
+ */
+ public class MessageIn : Object {
+ public new Component.Event event;
+ public Component.Event master;
+ public Component.RecurrenceRule? rrule;
+
+ public Message(Component.Event event, Component.RecurrenceRule? rrule) {
+ this.event = event;
+ master = event.is_master_instance ? event : (Component.Event) event.master;
+ rrule = rrule ?? master.rrule;
+ }
+ }
+
+ /**
+ * The message this card will pass to the next Card when jumping to it.
+ */
+ public class MessageOut : Object {
+ public Component.RecurrenceRule rrule;
+ public Component.Date start_date;
+
+ public MessageOut(Component.RecurrenceRule rrule, Component.Date start_date) {
+ this.rrule = rrule;
+ this.start_date = start_date;
+ }
+ }
+
public string card_id { get { return ID; } }
public string? title { get { return null; } }
@@ -100,8 +131,6 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
[GtkChild]
private Gtk.Button ok_button;
- private new Component.Event? event = null;
- private Component.Event? master = null;
private Gee.HashMap<Calendar.DayOfWeek, Gtk.CheckButton> on_day_checkbuttons = new Gee.HashMap<
Calendar.DayOfWeek, Gtk.CheckButton>();
private bool blocking_insert_text_numbers_only_signal = false;
@@ -153,32 +182,28 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
return true;
}
- public void jumped_to(Toolkit.Card? from, Toolkit.Card.Jump reason, Value? message) {
- if (message != null)
- event = (Component.Event) message;
-
- // *must* have an Event by this point, whether from before or due to this jump
- assert(event != null);
+ public void jumped_to(Toolkit.Card? from, Toolkit.Card.Jump reason, Value? msg) {
+ Message message = (Message) msg;
// need to use the master component in order to update the master RRULE
- if (!can_update_recurring(event)) {
+ if (!can_update_recurring(message.event)) {
jump_back();
return;
}
- update_controls();
+ update_controls(message);
}
public static bool can_update_recurring(Component.Event event) {
return event.is_master_instance || (event.master is Component.Event);
}
- private void update_controls() {
- master = (event.is_master_instance ? event : event.master) as Component.Event;
- assert(master != null);
+ private void update_controls(Message message) {
+ Component.Event master = message.master;
+ Component.RecurrenceRule? rrule = message.rrule;
- make_recurring_checkbutton.active = (master.rrule != null);
+ make_recurring_checkbutton.active = (rrule != null);
// some defaults that may not be set even if an RRULE is present
@@ -195,7 +220,7 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
checkbutton.active = false;
// set remaining defaults if not a recurring event
- if (master.rrule == null) {
+ if (rrule == null) {
repeats_combobox.active = Repeats.DAILY;
every_entry.text = "1";
never_radiobutton.active = true;
@@ -204,20 +229,24 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
}
// "Repeats" combobox
- switch (master.rrule.freq) {
+ switch (rrule.freq) {
case iCal.icalrecurrencetype_frequency.DAILY_RECURRENCE:
repeats_combobox.active = Repeats.DAILY;
break;
+ // TODO: Don't allow for editing weekly rules with anything but BYDAY or BYDAY where
+ // the position value is non-zero
case iCal.icalrecurrencetype_frequency.WEEKLY_RECURRENCE:
repeats_combobox.active = Repeats.WEEKLY;
break;
// TODO: Don't support MONTHLY RRULEs with multiple ByRules or ByRules we can't
// represent ... basically, non-simple repeating rules
+ // TODO: BYDAY should be the exact month-day of week for the DTSTART, MONTH_DAY should
+ // be the month-day of the month for the DTSTART
case iCal.icalrecurrencetype_frequency.MONTHLY_RECURRENCE:
- bool by_day = master.rrule.get_by_rule(Component.RecurrenceRule.ByRule.DAY).size > 0;
- bool by_monthday = master.rrule.get_by_rule(Component.RecurrenceRule.ByRule.MONTH_DAY).size
0;
+ bool by_day = rrule.get_by_rule(Component.RecurrenceRule.ByRule.DAY).size > 0;
+ bool by_monthday = rrule.get_by_rule(Component.RecurrenceRule.ByRule.MONTH_DAY).size > 0;
if (by_day && !by_monthday)
repeats_combobox.active = Repeats.DAY_OF_THE_WEEK;
@@ -237,12 +266,12 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
}
// "Every" entry
- every_entry.text = master.rrule.interval.to_string();
+ every_entry.text = rrule.interval.to_string();
// "On days" week day checkboxes are only visible if a WEEKLY event
if (master.rrule.is_weekly) {
Gee.Map<Calendar.DayOfWeek?, int> by_days =
-
Component.RecurrenceRule.decode_days(master.rrule.get_by_rule(Component.RecurrenceRule.ByRule.DAY));
+ Component.RecurrenceRule.decode_days(rrule.get_by_rule(Component.RecurrenceRule.ByRule.DAY));
// the presence of a "null" day means every or all days
if (by_days.has_key(null)) {
@@ -255,16 +284,16 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
}
// "Ends" choices
- if (!master.rrule.has_duration) {
+ if (!rrule.has_duration) {
never_radiobutton.active = true;
- } else if (master.rrule.count > 0) {
+ } else if (rrule.count > 0) {
after_radiobutton.active = true;
after_entry.text = master.rrule.count.to_string();
} else {
- assert(master.rrule.until_date != null || master.rrule.until_exact_time != null);
+ assert(rrule.until_date != null || rrule.until_exact_time != null);
ends_on_radiobutton.active = true;
- end_date = master.rrule.get_recurrence_end_date();
+ end_date = rrule.get_recurrence_end_date();
}
}
@@ -368,7 +397,76 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
[GtkCallback]
private void on_ok_button_clicked() {
- jump_back();
+ jump_to_card_by_name(CreateUpdateEvent.ID, make_rrule());
+ }
+
+ private MessageOut? make_message_out() {
+ if (!make_recurring_checkbutton.active)
+ return null;
+
+ iCal.icalrecurrencetype_frequency freq;
+ switch (repeats_combobox.active) {
+ case Repeats.DAILY:
+ freq = iCal.icalrecurrencetype_frequency.DAILY_RECURRENCE;
+ break;
+
+ case Repeats.WEEKLY:
+ freq = iCal.icalrecurrencetype_frequency.WEEKLY_RECURRENCE;
+ break;
+
+ case Repeats.DAY_OF_THE_WEEK:
+ case Repeats.DAY_OF_THE_MONTH:
+ freq = iCal.icalrecurrencetype_frequency.MONTHLY_RECURRENCE;
+ break;
+
+ case Repeats.YEARLY:
+ freq = iCal.icalrecurrencetype_frequency.YEARLY_RECURRENCE;
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ Component.RecurrenceRule rrule = new Component.RecurrenceRule(freq);
+ rrule.interval = Numeric.floor_int(int.parse(every_entry.text), 1);
+
+ // set start and end dates (which may actually be date-times)
+ if (never_radiobutton.active) {
+ // no duration
+ rrule.set_recurrence_end_date(null);
+ } else if (ends_on_radiobutton.active) {
+ rrule.set_recurrence_end_date(end_date);
+ } else {
+ assert(after_radiobutton.active);
+
+ rrule.set_recurrence_count(Numeric.floor_int(int.parse(after_entry.text), 1));
+ }
+
+ if (rrule.is_weekly) {
+ Gee.HashMap<Calendar.DayOfWeek?, int> by_day = new Gee.HashMap<Calendar.DayOfWeek?, int>();
+ foreach (Calendar.DayOfWeek dow in on_day_checkbuttons.keys) {
+ if (on_day_checkbuttons[dow].active)
+ by_day[dow] = 0;
+ }
+
+ rrule.set_by_rule(Component.RecurrenceRule.ByRule.DAY,
+ Component.RecurrenceRule.encode_days(by_day));
+ }
+
+ if (rrule.is_monthly) {
+ if (repeats_combobox.active == Repeats.DAY_OF_THE_WEEK) {
+ Gee.HashMap<Calendar.DayOfWeek?, int> by_day = new Gee.HashMap<Calendar.DayOfWeek?, int>();
+ by_day[start_date.day_of_week] =
start_date.week_of(Calendar.System.first_of_week).week_of_month;
+ rrule.set_by_rule(Component.RecurrenceRule.ByRule.DAY,
+ Component.RecurrenceRule.encode_days(by_day));
+ } else {
+ Gee.Collection<int> by_month_day = new Gee.ArrayList<int>();
+ by_month_day.add(start_date.day_of_month.value);
+ rrule.set_by_rule(Component.RecurrenceRule.ByRule.MONTH_DAY, by_month_day);
+ }
+ }
+
+ return new MessageOut(rrule, start_date);
}
}
diff --git a/src/host/host-quick-create-event.vala b/src/host/host-quick-create-event.vala
index 869e7f2..2bf45b8 100644
--- a/src/host/host-quick-create-event.vala
+++ b/src/host/host-quick-create-event.vala
@@ -44,7 +44,7 @@ public class QuickCreateEvent : Gtk.Grid, Toolkit.Card {
}
public void jumped_to(Toolkit.Card? from, Toolkit.Card.Jump reason, Value? message) {
- event = message as Component.Event;
+ event = (message != null) ? message as Component.Event : null;
// if initial date/times supplied, reveal to the user and change the example
string eg;
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index b20bc33..90e65a2 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -74,6 +74,10 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
}
private void build_display() {
+ debug("VEVENT:\n%s\n", event.source);
+ if (event.master != null)
+ debug("VEVENT MASTER:\n%s\n", event.master.source);
+
// summary
set_label(null, summary_text, event.summary);
diff --git a/src/rc/create-update-recurring.ui b/src/rc/create-update-recurring.ui
index 2a6456f..302c5ec 100644
--- a/src/rc/create-update-recurring.ui
+++ b/src/rc/create-update-recurring.ui
@@ -526,41 +526,4 @@
</packing>
</child>
</template>
- <object class="GtkBox" id="ends_on_box">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="spacing">4</property>
- <child>
- <object class="GtkRadioButton" id="ends_on_radiobutton1">
- <property name="label" translatable="yes" comments="As in, an event "ends on" a
date">_On</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0</property>
- <property name="active">True</property>
- <property name="draw_indicator">True</property>
- <property name="group">never_radiobutton</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="end_date_button1">
- <property name="label">(none)</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <signal name="clicked" handler="on_date_button_clicked" object="CaliforniaHostCreateUpdateRecurring"
swapped="no"/>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="fill">True</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
</interface>
diff --git a/src/util/util-numeric.vala b/src/util/util-numeric.vala
new file mode 100644
index 0000000..518ab0d
--- /dev/null
+++ b/src/util/util-numeric.vala
@@ -0,0 +1,17 @@
+/* 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.Numeric {
+
+/**
+ * Returns the value if it is greater than or equal to floor, floor otherwise.
+ */
+public inline int floor_int(int value, int floor) {
+ return (value >= floor) ? value : floor;
+}
+
+}
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]