[california/wip/725786-edit-recurring] Basically working now! Some clean up and error checking remains
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california/wip/725786-edit-recurring] Basically working now! Some clean up and error checking remains
- Date: Wed, 16 Jul 2014 20:45:32 +0000 (UTC)
commit 44ce56620d93a7570237e18ba773c4baf96efdbb
Author: Jim Nelson <jim yorba org>
Date: Wed Jul 16 13:45:16 2014 -0700
Basically working now! Some clean up and error checking remains
src/component/component-event.vala | 11 ++
src/component/component-instance.vala | 17 ++++
src/host/host-create-update-event.vala | 90 +++++++++---------
src/host/host-create-update-recurring.vala | 137 ++++++++++++++--------------
src/host/host-main-window.vala | 4 +
src/host/host-quick-create-event.vala | 28 +++---
src/host/host-show-event.vala | 12 ++-
7 files changed, 173 insertions(+), 126 deletions(-)
---
diff --git a/src/component/component-event.vala b/src/component/component-event.vala
index 27e13cc..b09aca1 100644
--- a/src/component/component-event.vala
+++ b/src/component/component-event.vala
@@ -229,6 +229,17 @@ public class Event : Instance, Gee.Comparable<Event> {
}
/**
+ * @inheritDoc
+ */
+ public override Component.Instance clone() throws Error {
+ Component.Event cloned_event = new Component.Event(calendar_source, ical_component);
+ if (master != null)
+ cloned_event.master = new Component.Event(master.calendar_source, master.ical_component);
+
+ return cloned_event;
+ }
+
+ /**
* Returns a { link Calendar.DateSpan} for the { link Event}.
*
* This will return a DateSpan whether the Event is a DATE or DATE-TIME VEVENT.
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index a57c6d0..002dd08 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -19,6 +19,10 @@ namespace California.Component {
* The second is to update the mutable properties themselves, which will then update the underlying
* iCal component.
*
+ * Instances produced by { link Backing.CalendarSourceSubscription}s will be updated by the
+ * subscription if the Instance is updated or removed locally or remotely. Cloned Instances,
+ * however, are not automatically updated. See { link clone}.
+ *
* Alarms will be contained within Instance components. Timezones are handled separately.
*
* Instance also offers a number of methods to convert iCal structures into internal objects.
@@ -350,6 +354,19 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
}
/**
+ * Make a detached copy of the { link Instance}.
+ *
+ * This produces an exact copy of the Instance at the time of the call. Unlike Instances
+ * produced by { link Backing.CalendarSourceSubscription}s, cloned Instances are not
+ * automatically updated as local and/or remote changes are made. This makes them good for
+ * editing (where a number of changes are made and stored in the Instance, only being submitted
+ * when the user gives the okay).
+ *
+ * Cloning will also clone the { link master}, if present.
+ */
+ public abstract Component.Instance clone() throws Error;
+
+ /**
* Add a { link RecurrenceRule} to the { link Instance}.
*
* Pass null to make the Instance non-recurring.
diff --git a/src/host/host-create-update-event.vala b/src/host/host-create-update-event.vala
index 68f92ae..9de97b8 100644
--- a/src/host/host-create-update-event.vala
+++ b/src/host/host-create-update-event.vala
@@ -8,6 +8,9 @@ namespace California.Host {
/**
* A blank "form" of widgets for the user to enter or update event details.
+ *
+ * Message IN: If creating a new event, send Component.Event.blank() (pre-filled with any known
+ * details). If updating an existing event, send Component.Event.clone().
*/
[GtkTemplate (ui = "/org/yorba/california/rc/create-update-event.ui")]
@@ -66,7 +69,6 @@ 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;
@@ -142,24 +144,13 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
}
public void jumped_to(Toolkit.Card? from, Toolkit.Card.Jump reason, Value? message) {
- 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;
- }
+ // if no message, leave everything as it is
+ if (message == null)
+ return;
- assert(event != null);
+ event = (Component.Event) message;
- if (update)
- update_controls();
+ update_controls();
}
private void update_controls() {
@@ -304,34 +295,39 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
return;
}
- event.calendar_source = calendar_model.active;
- event.summary = summary_entry.text;
- event.location = location_entry.text;
- event.description = description_textview.buffer.text;
+ // create/update this instance of the event
+ create_update_event(event, true);
+ }
+
+ private void create_update_event(Component.Event target, bool update_dtstart) {
+ target.calendar_source = calendar_model.active;
+ target.summary = summary_entry.text;
+ target.location = location_entry.text;
+ target.description = description_textview.buffer.text;
- if (all_day_toggle.active) {
- event.set_event_date_span(selected_date_span);
- } else {
- // use existing timezone unless not specified in original event
- Calendar.Timezone tz = (event.exact_time_span != null)
- ? event.exact_time_span.start_exact_time.tz
- : Calendar.Timezone.local;
- event.set_event_exact_time_span(
- new Calendar.ExactTimeSpan(
- new Calendar.ExactTime(tz, selected_date_span.start_date,
- time_map.get(dtstart_time_combo.get_active_text())),
- new Calendar.ExactTime(tz, selected_date_span.end_date,
- time_map.get(dtend_time_combo.get_active_text()))
- )
- );
+ if (update_dtstart) {
+ if (all_day_toggle.active) {
+ target.set_event_date_span(selected_date_span);
+ } else {
+ // use existing timezone unless not specified in original event
+ Calendar.Timezone tz = (target.exact_time_span != null)
+ ? target.exact_time_span.start_exact_time.tz
+ : Calendar.Timezone.local;
+ target.set_event_exact_time_span(
+ new Calendar.ExactTimeSpan(
+ new Calendar.ExactTime(tz, selected_date_span.start_date,
+ time_map.get(dtstart_time_combo.get_active_text())),
+ new Calendar.ExactTime(tz, selected_date_span.end_date,
+ time_map.get(dtend_time_combo.get_active_text()))
+ )
+ );
+ }
}
- event.make_recurring(rrule);
-
if (is_update)
- update_event_async.begin(null);
+ update_event_async.begin(target, null);
else
- create_event_async.begin(null);
+ create_event_async.begin(target, null);
}
private void on_cancel_button_clicked() {
@@ -339,17 +335,19 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
}
private void on_update_all_button_clicked() {
+ create_update_event(event.is_master_instance ? event : (Component.Event) event.master, false);
}
private void on_update_this_button_clicked() {
+ create_update_event(event, true);
}
private void on_cancel_recurring_button_clicked() {
rotating_button_box.family = FAMILY_NORMAL;
}
- private async void create_event_async(Cancellable? cancellable) {
- if (event.calendar_source == null) {
+ private async void create_event_async(Component.Event target, Cancellable? cancellable) {
+ if (target.calendar_source == null) {
notify_failure(_("Unable to create event: calendar must be specified"));
return;
@@ -359,7 +357,7 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
Error? create_err = null;
try {
- yield event.calendar_source.create_component_async(event, cancellable);
+ yield event.calendar_source.create_component_async(target, cancellable);
} catch (Error err) {
create_err = err;
}
@@ -373,8 +371,8 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
}
// TODO: Delete from original source if not the same as the new source
- private async void update_event_async(Cancellable? cancellable) {
- if (event.calendar_source == null) {
+ private async void update_event_async(Component.Event target, Cancellable? cancellable) {
+ if (target.calendar_source == null) {
notify_failure(_("Unable to update event: calendar must be specified"));
return;
@@ -384,7 +382,7 @@ public class CreateUpdateEvent : Gtk.Grid, Toolkit.Card {
Error? update_err = null;
try {
- yield event.calendar_source.update_component_async(event, cancellable);
+ yield event.calendar_source.update_component_async(target, cancellable);
} catch (Error err) {
update_err = err;
}
diff --git a/src/host/host-create-update-recurring.vala b/src/host/host-create-update-recurring.vala
index 1ff0f3b..5e0589e 100644
--- a/src/host/host-create-update-recurring.vala
+++ b/src/host/host-create-update-recurring.vala
@@ -23,37 +23,6 @@ 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; } }
@@ -131,6 +100,8 @@ 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;
@@ -166,6 +137,11 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
on_day_checkbuttons[Calendar.DayOfWeek.THU] = thursday_checkbutton;
on_day_checkbuttons[Calendar.DayOfWeek.FRI] = friday_checkbutton;
on_day_checkbuttons[Calendar.DayOfWeek.SAT] = saturday_checkbutton;
+
+ /*
+ foreach (Gtk.CheckButton check_button in on_day_checkbuttons.keys)
+ check_button.notify["active"].connect(on_checkbox_active_changed);
+ */
}
private bool transform_repeats_active_to_on_days_visible(Binding binding, Value source_value,
@@ -182,28 +158,28 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
return true;
}
- public void jumped_to(Toolkit.Card? from, Toolkit.Card.Jump reason, Value? msg) {
- Message message = (Message) msg;
+ public void jumped_to(Toolkit.Card? from, Toolkit.Card.Jump reason, Value? message) {
+ assert(message != null);
+
+ event = (Component.Event) message;
+ master = event.is_master_instance ? event : (Component.Event) event.master;
// need to use the master component in order to update the master RRULE
- if (!can_update_recurring(message.event)) {
+ if (!can_update_recurring(event)) {
jump_back();
return;
}
- update_controls(message);
+ update_controls();
}
public static bool can_update_recurring(Component.Event event) {
return event.is_master_instance || (event.master is Component.Event);
}
- private void update_controls(Message message) {
- Component.Event master = message.master;
- Component.RecurrenceRule? rrule = message.rrule;
-
- make_recurring_checkbutton.active = (rrule != null);
+ private void update_controls() {
+ make_recurring_checkbutton.active = (master.rrule != null);
// some defaults that may not be set even if an RRULE is present
@@ -220,7 +196,7 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
checkbutton.active = false;
// set remaining defaults if not a recurring event
- if (rrule == null) {
+ if (master.rrule == null) {
repeats_combobox.active = Repeats.DAILY;
every_entry.text = "1";
never_radiobutton.active = true;
@@ -229,7 +205,7 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
}
// "Repeats" combobox
- switch (rrule.freq) {
+ switch (master.rrule.freq) {
case iCal.icalrecurrencetype_frequency.DAILY_RECURRENCE:
repeats_combobox.active = Repeats.DAILY;
break;
@@ -245,8 +221,8 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
// 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 = rrule.get_by_rule(Component.RecurrenceRule.ByRule.DAY).size > 0;
- bool by_monthday = rrule.get_by_rule(Component.RecurrenceRule.ByRule.MONTH_DAY).size > 0;
+ 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;
if (by_day && !by_monthday)
repeats_combobox.active = Repeats.DAY_OF_THE_WEEK;
@@ -266,12 +242,12 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
}
// "Every" entry
- every_entry.text = rrule.interval.to_string();
+ every_entry.text = master.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(rrule.get_by_rule(Component.RecurrenceRule.ByRule.DAY));
+
Component.RecurrenceRule.decode_days(master.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)) {
@@ -284,16 +260,16 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
}
// "Ends" choices
- if (!rrule.has_duration) {
+ if (!master.rrule.has_duration) {
never_radiobutton.active = true;
- } else if (rrule.count > 0) {
+ } else if (master.rrule.count > 0) {
after_radiobutton.active = true;
after_entry.text = master.rrule.count.to_string();
} else {
- assert(rrule.until_date != null || rrule.until_exact_time != null);
+ assert(master.rrule.until_date != null || master.rrule.until_exact_time != null);
ends_on_radiobutton.active = true;
- end_date = rrule.get_recurrence_end_date();
+ end_date = master.rrule.get_recurrence_end_date();
}
}
@@ -397,12 +373,16 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
[GtkCallback]
private void on_ok_button_clicked() {
- jump_to_card_by_name(CreateUpdateEvent.ID, make_rrule());
+ update_master();
+ jump_to_card_by_name(CreateUpdateEvent.ID, event);
}
- private MessageOut? make_message_out() {
- if (!make_recurring_checkbutton.active)
- return null;
+ private void update_master() {
+ if (!make_recurring_checkbutton.active) {
+ master.make_recurring(null);
+
+ return;
+ }
iCal.icalrecurrencetype_frequency freq;
switch (repeats_combobox.active) {
@@ -430,18 +410,6 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
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) {
@@ -449,8 +417,43 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
by_day[dow] = 0;
}
+ // although control sensitivity should prevent this from happening, be double-sure to
+ // prevent infinite loops below
+ if (by_day.size == 0)
+ by_day[start_date.day_of_week] = 0;
+
rrule.set_by_rule(Component.RecurrenceRule.ByRule.DAY,
Component.RecurrenceRule.encode_days(by_day));
+
+ // need to also update the start date to fall on one of the selected days of the week
+ // start by looking backward
+ Calendar.Date new_start_date = start_date.prior(true, (date) => {
+ return date.day_of_week in by_day.keys;
+ });
+
+ // if start date is prior to today's day, move forward
+ if (new_start_date.compare_to(Calendar.System.today) < 0) {
+ new_start_date = start_date.upcoming(true, (date) => {
+ return date.day_of_week in by_day.keys;
+ });
+ }
+
+ start_date = new_start_date;
+ }
+
+ // set start and end dates (which may actually be date-times, so use adjust)
+ if (never_radiobutton.active) {
+ // no duration
+ master.adjust_event_date_span(start_date.to_date_span());
+ rrule.set_recurrence_end_date(null);
+ } else if (ends_on_radiobutton.active) {
+ master.adjust_event_date_span(new Calendar.DateSpan(start_date, end_date));
+ rrule.set_recurrence_end_date(end_date);
+ } else {
+ assert(after_radiobutton.active);
+
+ master.adjust_event_date_span(start_date.to_date_span());
+ rrule.set_recurrence_count(Numeric.floor_int(int.parse(after_entry.text), 1));
}
if (rrule.is_monthly) {
@@ -466,7 +469,7 @@ public class CreateUpdateRecurring : Gtk.Grid, Toolkit.Card {
}
}
- return new MessageOut(rrule, start_date);
+ master.make_recurring(rrule);
}
}
diff --git a/src/host/host-main-window.vala b/src/host/host-main-window.vala
index e2ebb2f..a753a18 100644
--- a/src/host/host-main-window.vala
+++ b/src/host/host-main-window.vala
@@ -351,6 +351,10 @@ public class MainWindow : Gtk.ApplicationWindow {
Toolkit.spin_event_loop();
});
+ deck_window.deck.failure.connect((msg) => {
+ Application.instance.error_message(msg);
+ });
+
deck_window.show_all();
deck_window.run();
deck_window.destroy();
diff --git a/src/host/host-quick-create-event.vala b/src/host/host-quick-create-event.vala
index 2bf45b8..4ce6d9b 100644
--- a/src/host/host-quick-create-event.vala
+++ b/src/host/host-quick-create-event.vala
@@ -97,11 +97,7 @@ public class QuickCreateEvent : Gtk.Grid, Toolkit.Card {
string details = details_entry.text.strip();
if (String.is_empty(details)) {
- // jump to Create/Update dialog and remove this Card from the Deck ... this ensures
- // that if the user presses Cancel in the Create/Update dialog the Deck exits rather
- // than returns here (via jump_home_or_user_closed())
- jump_to_card_by_name(CreateUpdateEvent.ID, event);
- deck.remove_cards(iterate<Toolkit.Card>(this).to_array_list());
+ create_empty_event();
return;
}
@@ -110,14 +106,22 @@ public class QuickCreateEvent : Gtk.Grid, Toolkit.Card {
event);
event = parser.event;
- if (event.is_valid(true)) {
+ if (event.is_valid(true))
create_event_async.begin(null);
- } else {
- // see note above about why the Deck jumps to Create/Update and then this Card is
- // removed
- jump_to_card_by_name(CreateUpdateEvent.ID, event);
- deck.remove_cards(iterate<Toolkit.Card>(this).to_array_list());
- }
+ else
+ create_empty_event();
+ }
+
+ private void create_empty_event() {
+ // Must pass some kind of event to create/update, so use blank if required
+ if (event == null)
+ event = new Component.Event.blank();
+
+ // jump to Create/Update dialog and remove this Card from the Deck ... this ensures
+ // that if the user presses Cancel in the Create/Update dialog the Deck exits rather
+ // than returns here (via jump_home_or_user_closed())
+ jump_to_card_by_name(CreateUpdateEvent.ID, event);
+ deck.remove_cards(iterate<Toolkit.Card>(this).to_array_list());
}
private async void create_event_async(Cancellable? cancellable) {
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index 90e65a2..bcf151a 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -6,6 +6,10 @@
namespace California.Host {
+/**
+ * MESSAGE IN: Send the Component.Event to be displayed.
+ */
+
[GtkTemplate (ui = "/org/yorba/california/rc/show-event.ui")]
public class ShowEvent : Gtk.Grid, Toolkit.Card {
public const string ID = "ShowEvent";
@@ -64,6 +68,7 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
}
public void jumped_to(Toolkit.Card? from, Toolkit.Card.Jump reason, Value? message) {
+ // no message, don't update display
if (message == null)
return;
@@ -176,7 +181,12 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
[GtkCallback]
private void on_update_button_clicked() {
- jump_to_card_by_name(CreateUpdateEvent.ID, event);
+ // pass a clone of the existing event for editing
+ try {
+ jump_to_card_by_name(CreateUpdateEvent.ID, event.clone() as Component.Event);
+ } catch (Error err) {
+ notify_failure(_("Unable to update event: %s").printf(err.message));
+ }
}
[GtkCallback]
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]