[california] Export single event as an iCalendar: Bug #734155
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california] Export single event as an iCalendar: Bug #734155
- Date: Fri, 24 Oct 2014 22:19:58 +0000 (UTC)
commit 9db43223bdbe4f7ee42a560be31c52fc7c4c4a20
Author: Jim Nelson <jim yorba org>
Date: Fri Oct 24 15:18:37 2014 -0700
Export single event as an iCalendar: Bug #734155
A single event can be saved to a .ics file from the Show Event
popover. This is good enough for viewing/saving event source for
debugging, so this also closes bug #727265.
src/application/california-application.vala | 5 ++-
src/component/component-icalendar.vala | 66 +++++++++++++++++++++++++-
src/component/component.vala | 21 +++++++++
src/host/host-show-event.vala | 39 +++++++++++++++-
src/host/host.vala | 2 +
5 files changed, 128 insertions(+), 5 deletions(-)
---
diff --git a/src/application/california-application.vala b/src/application/california-application.vala
index d65c5d9..6e646ee 100644
--- a/src/application/california-application.vala
+++ b/src/application/california-application.vala
@@ -121,7 +121,10 @@ public class Application : Gtk.Application {
}
}
- private Host.MainWindow? main_window = null;
+ /**
+ * The { link MainWindow}.
+ */
+ public Host.MainWindow? main_window { get; private set; default = null; }
private Application() {
Object (application_id: ID);
diff --git a/src/component/component-icalendar.vala b/src/component/component-icalendar.vala
index a6daa9a..c629f8b 100644
--- a/src/component/component-icalendar.vala
+++ b/src/component/component-icalendar.vala
@@ -62,6 +62,8 @@ public class iCalendar : BaseObject {
/**
* VEVENTS within the VCALENDAR.
+ *
+ * Don't add { link Event} objects directly to this list.
*/
public Gee.List<Event> events { get; private set; default = new Gee.ArrayList<Event>(); }
@@ -72,11 +74,69 @@ public class iCalendar : BaseObject {
public iCal.icalcomponent ical_component { get { return _ical_component; } }
/**
- * Create an { link iCalendar} representation of the iCal component.
+ * Returns the iCal source for this { link iCalendar}.
+ */
+ public string source { get { return ical_component.as_ical_string(); } }
+
+ /**
+ * Creates a new { link iCalendar}.
+ *
+ * As iCalendar is currently immutable, { link Instance}s must be added here. It's possible
+ * later modifications will allow for Instances to be added and removed dynamically.
+ */
+ public iCalendar(iCal.icalproperty_method method, string? prodid, string? version, string? calscale,
+ Gee.List<Instance>? instances) {
+ this.prodid = prodid;
+ this.version = version;
+ this.calscale = calscale;
+ this.method = method;
+
+ _ical_component = new iCal.icalcomponent(iCal.icalcomponent_kind.VCALENDAR_COMPONENT);
+
+ if (prodid != null && !String.is_empty(prodid)) {
+ iCal.icalproperty prop = new iCal.icalproperty(iCal.icalproperty_kind.PRODID_PROPERTY);
+ prop.set_prodid(prodid);
+ _ical_component.add_property(prop);
+ }
+
+ if (version != null && !String.is_empty(version)) {
+ iCal.icalproperty prop = new iCal.icalproperty(iCal.icalproperty_kind.VERSION_PROPERTY);
+ prop.set_version(version);
+ _ical_component.add_property(prop);
+ }
+
+ if (calscale != null && !String.is_empty(calscale)) {
+ iCal.icalproperty prop = new iCal.icalproperty(iCal.icalproperty_kind.CALSCALE_PROPERTY);
+ prop.set_calscale(prodid);
+ _ical_component.add_property(prop);
+ }
+
+ // METHOD is required ... not checking for NONE, if that's how the user wants to go, so
+ // be it
+ iCal.icalproperty prop = new iCal.icalproperty(iCal.icalproperty_kind.METHOD_PROPERTY);
+ prop.set_method(method);
+ _ical_component.add_property(prop);
+
+ //
+ // contained components
+ //
+
+ foreach (Instance instance in instances) {
+ // store copies because ownership is not being transferred
+ _ical_component.add_component(instance.ical_component.clone());
+
+ Event? event = instance as Event;
+ if (event != null)
+ events.add(event);
+ }
+ }
+
+ /**
+ * Create an { link iCalendar} representation of an existing iCal component.
*
* @throws ComponentError.INVALID if root is not a VCALENDAR.
*/
- private iCalendar(owned iCal.icalcomponent root) throws Error {
+ private iCalendar.take(owned iCal.icalcomponent root) throws Error {
if (root.isa() != iCal.icalcomponent_kind.VCALENDAR_COMPONENT)
throw new ComponentError.INVALID("Not a VCALENDAR");
@@ -133,7 +193,7 @@ public class iCalendar : BaseObject {
if (ical_component == null)
throw new ComponentError.INVALID("Unable to parse VCALENDAR (%db)".printf(str.length));
- return new iCalendar((owned) ical_component);
+ return new iCalendar.take((owned) ical_component);
}
public override string to_string() {
diff --git a/src/component/component.vala b/src/component/component.vala
index d1004f1..7327f2d 100644
--- a/src/component/component.vala
+++ b/src/component/component.vala
@@ -15,6 +15,23 @@
namespace California.Component {
+/**
+ * iCalendar PRODID (Product Identifier).
+ *
+ * { link init} ''must'' be called before referencing this string.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.7.3]]
+ * and [[https://en.wikipedia.org/wiki/Formal_Public_Identifier]]
+ */
+public static string ICAL_PRODID;
+
+/**
+ * iCalendar version this application adheres to.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.7.4]]
+ */
+public const string ICAL_VERSION = "2.0";
+
private int init_count = 0;
private string TODAY;
@@ -47,6 +64,8 @@ public void init() throws Error {
Collection.init();
Calendar.init();
+ ICAL_PRODID = "-//Yorba Foundation//NONSGML California Calendar %s//EN".printf(Application.VERSION);
+
// Used by quick-add to indicate the user wants to create an event for today.
// For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
TODAY = _("today").casefold();
@@ -187,6 +206,8 @@ public void terminate() {
UNIT_WEEKDAYS = UNIT_WEEKENDS = UNIT_YEARS = UNIT_MONTHS = UNIT_WEEKS = UNIT_DAYS = UNIT_HOURS
= UNIT_MINS = null;
+ ICAL_PRODID = null;
+
Calendar.terminate();
Collection.terminate();
}
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index 937606a..76de1d1 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -69,6 +69,8 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
Gtk.IconSize.BUTTON);
private Gtk.Button remove_button = new Gtk.Button.from_icon_name("user-trash-symbolic",
Gtk.IconSize.BUTTON);
+ private Gtk.Button export_button = new Gtk.Button.from_icon_name("document-save-symbolic",
+ Gtk.IconSize.BUTTON);
private Gtk.Label delete_label = new Gtk.Label(_("Delete"));
private Gtk.Button remove_all_button = new Gtk.Button.with_mnemonic(_("A_ll Events"));
@@ -82,10 +84,12 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
Calendar.System.instance.today_changed.connect(build_display);
update_button.tooltip_text = _("Edit event");
+ export_button.tooltip_text = _("Export event as .ics");
remove_button.tooltip_text = _("Delete event");
- update_button.relief = remove_button.relief = Gtk.ReliefStyle.NONE;
+ update_button.relief = remove_button.relief = export_button.relief = Gtk.ReliefStyle.NONE;
action_box.pack_end(update_button, false, false);
+ action_box.pack_end(export_button, false, false);
action_box.pack_end(remove_button, false, false);
remove_this_button.get_style_context().add_class("destructive-action");
@@ -93,6 +97,7 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
remove_all_button.get_style_context().add_class("destructive-action");
update_button.clicked.connect(on_update_button_clicked);
+ export_button.clicked.connect(on_export_button_clicked);
remove_button.clicked.connect(on_remove_button_clicked);
remove_all_button.clicked.connect(on_remove_all_button_clicked);
remove_this_button.clicked.connect(on_remove_this_button_clicked);
@@ -242,6 +247,38 @@ public class ShowEvent : Gtk.Grid, Toolkit.Card {
notify_user_closed();
}
+ private void on_export_button_clicked() {
+ // Export as a self-contained iCalendar
+ Component.iCalendar icalendar = new Component.iCalendar(iCal.icalproperty_method.PUBLISH,
+ Component.ICAL_PRODID, Component.ICAL_VERSION, null,
+ iterate<Component.Instance>(event).to_array_list());
+
+ Gtk.FileChooserDialog dialog = new Gtk.FileChooserDialog(_("Export event as .ics"),
+ Application.instance.main_window, Gtk.FileChooserAction.SAVE,
+ _("_Cancel"), Gtk.ResponseType.CANCEL,
+ _("E_xport"), Gtk.ResponseType.ACCEPT);
+ // This is the suggested filename for saving (exporting) an event. The .ics file extension
+ // should always be present no matter the translation, as many systems rely on it to detect
+ // the file type
+ dialog.set_current_name(_("event.ics"));
+ dialog.do_overwrite_confirmation = true;
+
+ dialog.show_all();
+ int response = dialog.run();
+ string filename = dialog.get_filename();
+ dialog.destroy();
+
+ if (response != Gtk.ResponseType.ACCEPT)
+ return;
+
+ try {
+ FileUtils.set_contents(filename, icalendar.source);
+ } catch (Error err) {
+ Application.instance.error_message(Application.instance.main_window,
+ _("Unable to export event as file: %s").printf(err.message));
+ }
+ }
+
private async void remove_events_async(Component.DateTime? rid,
Backing.CalendarSource.AffectedInstances affected) {
Gdk.Cursor? cursor = Toolkit.set_busy(this);
diff --git a/src/host/host.vala b/src/host/host.vala
index a1fada4..0e5d330 100644
--- a/src/host/host.vala
+++ b/src/host/host.vala
@@ -23,12 +23,14 @@ public void init() throws Error {
Backing.init();
Calendar.init();
Toolkit.init();
+ Component.init();
}
public void terminate() {
if (!Unit.do_terminate(ref init_count))
return;
+ Component.terminate();
View.terminate();
Backing.terminate();
Calendar.terminate();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]