[california/wip/727120-webcal] First pass
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california/wip/727120-webcal] First pass
- Date: Thu, 27 Mar 2014 01:06:32 +0000 (UTC)
commit c777dc3ef6ace2a6a0c76adf0a353f1fc825b0f9
Author: Jim Nelson <jim yorba org>
Date: Wed Mar 26 18:05:42 2014 -0700
First pass
This gets the UI in place and some basic plumbing for the backend
code to create new calendars and subscriptions.
configure.ac | 4 +-
po/POTFILES.in | 2 +
src/Makefile.am | 8 +
src/application/california-application.vala | 11 ++
src/backing/backing-activator.vala | 52 ++++++
src/backing/backing-manager.vala | 20 ++-
src/backing/backing-webcal-subscribable.vala | 31 ++++
src/backing/backing.vala | 6 +-
src/backing/eds/backing-eds-store.vala | 63 +++++++-
.../webcal/backing-webcal-activator-pane.vala | 80 +++++++++
src/backing/webcal/backing-webcal-activator.vala | 24 +++
src/california-resources.xml | 3 +
src/host/host-create-update-event.vala | 5 +-
src/host/host-interaction.vala | 17 ++-
src/host/host-modal-window.vala | 9 +-
src/host/host-show-event.vala | 6 +-
src/manager/manager-calendar-list.vala | 2 +-
src/rc/app-menu.interface | 5 +
src/rc/webcal-subscribe.ui | 185 ++++++++++++++++++++
src/util/util-uri.vala | 71 ++++++++
20 files changed, 590 insertions(+), 14 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 4b8777f..c16a8ab 100644
--- a/configure.ac
+++ b/configure.ac
@@ -27,13 +27,15 @@ GLIB_REQUIRED=2.38.0
GTK_REQUIRED=3.10.7
GEE_REQUIRED=0.10.5
ECAL_REQUIRED=3.8.5
+LIBSOUP_REQUIRED=2.45
PKG_CHECK_MODULES(CALIFORNIA, \
glib-2.0 >= $GLIB_REQUIRED \
gobject-2.0 >= $GLIB_REQUIRED \
gtk+-3.0 >= $GTK_REQUIRED \
gee-0.8 >= $GEE_REQUIRED \
- libecal-1.2 >= $ECAL_REQUIRED
+ libecal-1.2 >= $ECAL_REQUIRED \
+ libsoup-2.4 >= $LIBSOUP_REQUIRED \
)
AC_SUBST(CALIFORNIA_CFLAGS)
AC_SUBST(CALIFORNIA_LIBS)
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b5cd281..bb73f5a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,6 +1,7 @@
[encoding: UTF-8]
# List of source files which contain translatable strings.
src/application/california-application.vala
+src/backing/backing.vala
src/calendar/calendar.vala
src/host/host-create-update-event.vala
src/host/host-main-window.vala
@@ -10,3 +11,4 @@ src/host/host-show-event.vala
[type: gettext/glade]src/rc/calendar-manager-list-item.ui
[type: gettext/glade]src/rc/create-update-event.ui
[type: gettext/glade]src/rc/show-event.ui
+[type: gettext/glade]src/rc/webcal-subscribe.ui
diff --git a/src/Makefile.am b/src/Makefile.am
index f523b5a..b8ee931 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -13,17 +13,22 @@ california_VALASOURCES = \
application/main.vala \
\
backing/backing.vala \
+ backing/backing-activator.vala \
backing/backing-calendar-source.vala \
backing/backing-calendar-source-subscription.vala \
backing/backing-error.vala \
backing/backing-manager.vala \
backing/backing-source.vala \
backing/backing-store.vala \
+ backing/backing-webcal-subscribable.vala \
\
backing/eds/backing-eds-calendar-source.vala \
backing/eds/backing-eds-calendar-source-subscription.vala \
backing/eds/backing-eds-store.vala \
\
+ backing/webcal/backing-webcal-activator.vala \
+ backing/webcal/backing-webcal-activator-pane.vala \
+ \
base/base-object.vala \
base/base-unit.vala \
\
@@ -78,6 +83,7 @@ california_VALASOURCES = \
util/util-gfx.vala \
util/util-memory.vala \
util/util-string.vala \
+ util/util-uri.vala \
\
view/view.vala \
view/view-controllable.vala \
@@ -99,6 +105,7 @@ california_RC = \
rc/calendar-manager-list-item.ui \
rc/create-update-event.ui \
rc/show-event.ui \
+ rc/webcal-subscribe.ui \
$(NULL)
california_OPTIONAL_VALAFLAGS =
@@ -118,6 +125,7 @@ california_VALAFLAGS = \
--pkg libedataserver-1.2 \
--pkg libecal-1.2 \
--pkg libical \
+ --pkg libsoup-2.4 \
$(NULL)
california_CFLAGS = \
diff --git a/src/application/california-application.vala b/src/application/california-application.vala
index 129b1ce..a92a8d2 100644
--- a/src/application/california-application.vala
+++ b/src/application/california-application.vala
@@ -29,6 +29,7 @@ public class Application : Gtk.Application {
null
};
+ public const string ACTION_NEW_CALENDAR = "app.new-calendar";
public const string ACTION_CALENDAR_MANAGER = "app.calendar-manager";
public const string ACTION_ABOUT = "app.about";
public const string ACTION_QUIT = "app.quit";
@@ -41,6 +42,7 @@ public class Application : Gtk.Application {
}
private static const ActionEntry[] action_entries = {
+ { "new-calendar", on_new_calendar },
{ "calendar-manager", on_calendar_manager },
{ "about", on_about },
{ "quit", on_quit }
@@ -85,6 +87,7 @@ public class Application : Gtk.Application {
try {
Host.init();
Manager.init();
+ Backing.init();
} catch (Error err) {
error_message(_("Unable to open California: %s").printf(err.message));
quit();
@@ -100,6 +103,7 @@ public class Application : Gtk.Application {
main_window = null;
// unit termination
+ Backing.terminate();
Manager.terminate();
Host.terminate();
@@ -127,6 +131,13 @@ public class Application : Gtk.Application {
dialog.destroy();
}
+ private void on_new_calendar() {
+ Host.ModalWindow modal = new Host.ModalWindow(main_window);
+ modal.content_area.add(Backing.Manager.instance.primary_activator.create_interaction(null));
+ modal.run();
+ modal.destroy();
+ }
+
private void on_calendar_manager() {
Manager.Window.display(main_window);
}
diff --git a/src/backing/backing-activator.vala b/src/backing/backing-activator.vala
new file mode 100644
index 0000000..ff6c60d
--- /dev/null
+++ b/src/backing/backing-activator.vala
@@ -0,0 +1,52 @@
+/* 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.Backing {
+
+/**
+ * Locates, validates, and authorizes access to a { link Source}.
+ *
+ * Actovators are decoupled from the Backing.Source itself because it's possible for Activators
+ * to be used for multiple backings. For example, a Google Calendar Activator can be used to
+ * locate the user's calendar information, which can then be passed on to the EDS or a GData
+ * backing.
+ */
+
+public abstract class Activator : BaseObject {
+ public const string PROP_TITLE = "title";
+ public const string PROP_STORE = "store";
+
+ /**
+ * The user-visible title of this { link Activator} indicating what service or type of service
+ * it can prepare a subscription for.
+ */
+ public string title { get; private set; }
+
+ /**
+ * The { link Store} this { link Activator} will create the new { link Source} in.
+ *
+ * It's up to the subclass to determine which Stores will work with its information.
+ */
+ public Store store { get; private set; }
+
+ protected Activator(string title, Store store) {
+ this.title = title;
+ this.store = store;
+ }
+
+ /**
+ * Return a { link Host.Interaction} that guides the user through the steps to create a
+ * { link Source}.
+ */
+ public abstract Host.Interaction create_interaction(Soup.URI? supplied_uri);
+
+ public override string to_string() {
+ return title;
+ }
+}
+
+}
+
diff --git a/src/backing/backing-manager.vala b/src/backing/backing-manager.vala
index 4ce555b..8a7929f 100644
--- a/src/backing/backing-manager.vala
+++ b/src/backing/backing-manager.vala
@@ -17,7 +17,10 @@ public class Manager : BaseObject {
public bool is_open { get; private set; default = false; }
+ public Activator primary_activator { get; private set; }
+
private Gee.List<Store> stores = new Gee.ArrayList<Store>();
+ private Gee.List<Activator> activators = new Gee.ArrayList<Activator>();
/**
* Fired when a { link Store} cannot be opened.
@@ -42,12 +45,27 @@ public class Manager : BaseObject {
*
* TODO: A plugin system may make sense here.
*/
- internal void register(Store store) {
+ internal void register_store(Store store) {
if (!stores.contains(store))
stores.add(store);
}
/**
+ * The various { link Activators} are registered in { link Backing.init}.
+ *
+ * This *must* be called prior to { link open_async} for them to be opened properly.
+ *
+ * TODO: A plugin system may make sense here.
+ */
+ internal void register_activator(Activator activator) {
+ if (!activators.contains(activator))
+ activators.add(activator);
+
+ if (primary_activator == null)
+ primary_activator = activator;
+ }
+
+ /**
* Asynchronously open the { link Manager}.
*
* This must be called before any other operation on the Manager (unless noted).
diff --git a/src/backing/backing-webcal-subscribable.vala b/src/backing/backing-webcal-subscribable.vala
new file mode 100644
index 0000000..970d119
--- /dev/null
+++ b/src/backing/backing-webcal-subscribable.vala
@@ -0,0 +1,31 @@
+/* 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.Backing {
+
+/**
+ * Interface allowing for a { link Store} to subscribe to WebCal calendars.
+ *
+ * See [[https://web.archive.org/web/20071222113039/http://larry.cannell.org/webcal]] for more
+ * information about WebCal.
+ */
+
+public interface WebCalSubscribable : Store {
+ /**
+ * Subscribe to a WebCal link, creating a new { link CalendarSource} in the process.
+ *
+ * "title" is the display name of the new subscription and should probably be supplied by the
+ * user.
+ *
+ * The CalendarSource is not returned; rather, callers should be subscribed to the
+ * { link Store.added} signal for notification.
+ */
+ public abstract async void subscribe_webcal_async(string title, Soup.URI uri,
+ string color, Cancellable? cancellable) throws Error;
+}
+
+}
+
diff --git a/src/backing/backing.vala b/src/backing/backing.vala
index d5c5e9d..acb3c2d 100644
--- a/src/backing/backing.vala
+++ b/src/backing/backing.vala
@@ -39,8 +39,10 @@ public void init() throws Error {
// internal class init
Manager.init();
- // Register all Stores here
- Manager.instance.register(new EdsStore());
+ // Register all Stores and Activators here
+ EdsStore eds_store = new EdsStore();
+ Manager.instance.register_store(eds_store);
+ Manager.instance.register_activator(new WebCalActivator(_("Web Calendar"), eds_store));
// open Manager, pumping event loop until it completes (possibly w/ error)
Manager.instance.open_async.begin(null, on_backing_manager_opened);
diff --git a/src/backing/eds/backing-eds-store.vala b/src/backing/eds/backing-eds-store.vala
index 79c7173..776b3a7 100644
--- a/src/backing/eds/backing-eds-store.vala
+++ b/src/backing/eds/backing-eds-store.vala
@@ -10,7 +10,7 @@ namespace California.Backing {
* An interface to the EDS source registry.
*/
-internal class EdsStore : Store {
+internal class EdsStore : Store, WebCalSubscribable {
private E.SourceRegistry? registry = null;
private Gee.HashMap<E.Source, Source> sources = new Gee.HashMap<E.Source, Source>();
@@ -41,6 +41,67 @@ internal class EdsStore : Store {
is_open = false;
}
+ /**
+ * @inheritDoc
+ *
+ * TODO: Authentication is not properly handled.
+ */
+ public async void subscribe_webcal_async(string title, Soup.URI uri, string color,
+ Cancellable? cancellable) throws Error {
+ if (!is_open)
+ throw new BackingError.UNAVAILABLE("EDS not open");
+
+ E.Source scratch = new E.Source(null, null);
+ scratch.parent = "webcal-stub";
+ scratch.enabled = true;
+ scratch.display_name = title;
+
+ // required
+ E.SourceCalendar? calendar = scratch.get_extension(E.SOURCE_EXTENSION_CALENDAR)
+ as E.SourceCalendar;
+ if (calendar == null)
+ throw new BackingError.UNAVAILABLE("No SourceCalendar extension for scratch source");
+ calendar.backend_name = "webcal";
+ calendar.selected = true;
+ calendar.color = color;
+
+ // required
+ E.SourceWebdav? webdav = scratch.get_extension(E.SOURCE_EXTENSION_WEBDAV_BACKEND)
+ as E.SourceWebdav;
+ if (webdav == null)
+ throw new BackingError.UNAVAILABLE("No SourceWebdav extension for scratch source");
+ webdav.resource_path = uri.path;
+ webdav.resource_query = uri.query;
+
+ // required
+ E.SourceAuthentication? auth = scratch.get_extension(E.SOURCE_EXTENSION_AUTHENTICATION)
+ as E.SourceAuthentication;
+ if (auth == null)
+ throw new BackingError.UNAVAILABLE("No SourceAuthentication extension for scratch source");
+ auth.host = uri.host;
+ auth.port = uri.port;
+ auth.method = "none";
+
+ // optional w/ baked-in defaults
+ E.SourceOffline? offline = scratch.get_extension(E.SOURCE_EXTENSION_OFFLINE)
+ as E.SourceOffline;
+ if (offline != null)
+ offline.stay_synchronized = true;
+
+ // optional w/ baked-in defaults
+ E.SourceRefresh? refresh = scratch.get_extension(E.SOURCE_EXTENSION_REFRESH)
+ as E.SourceRefresh;
+ if (refresh != null) {
+ refresh.enabled = true;
+ refresh.interval_minutes = 1;
+ }
+
+ List<E.Source> sources = new List<E.Source>();
+ sources.append(scratch);
+ // TODO: Properly bind async version of this call
+ registry.create_sources_sync(sources, cancellable);
+ }
+
public override Gee.List<Source> get_sources() {
Gee.List<Source> list = new Gee.ArrayList<Source>();
list.add_all(sources.values.read_only_view);
diff --git a/src/backing/webcal/backing-webcal-activator-pane.vala
b/src/backing/webcal/backing-webcal-activator-pane.vala
new file mode 100644
index 0000000..bb94266
--- /dev/null
+++ b/src/backing/webcal/backing-webcal-activator-pane.vala
@@ -0,0 +1,80 @@
+/* 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.Backing {
+
+[GtkTemplate (ui = "/org/yorba/california/rc/webcal-subscribe.ui")]
+internal class WebCalActivatorPane : Gtk.Grid, Host.Interaction {
+ public Gtk.Widget? default_widget { get { return subscribe_button; } }
+
+ [GtkChild]
+ private Gtk.ColorButton color_button;
+
+ [GtkChild]
+ private Gtk.Entry name_entry;
+
+ [GtkChild]
+ private Gtk.Entry url_entry;
+
+ [GtkChild]
+ private Gtk.Button subscribe_button;
+
+ private WebCalSubscribable store;
+
+ public WebCalActivatorPane(WebCalSubscribable store, Soup.URI? supplied_url) {
+ this.store = store;
+
+ if (supplied_url != null) {
+ url_entry.text = supplied_url.to_string(false);
+ url_entry.sensitive = false;
+ }
+
+ name_entry.bind_property("text-length", subscribe_button, "sensitive",
+ BindingFlags.SYNC_CREATE, on_entry_changed);
+ url_entry.bind_property("text-length", subscribe_button, "sensitive",
+ BindingFlags.SYNC_CREATE, on_entry_changed);
+ }
+
+ private bool on_entry_changed(Binding binding, Value source_value, ref Value target_value) {
+ // TODO: More thorough URL validation
+ target_value =
+ name_entry.text_length > 0
+ && url_entry.text_length > 0
+ && URI.is_valid(url_entry.text, { "http://", "https://", "webcal://" });
+
+ return true;
+ }
+
+ [GtkCallback]
+ private void on_cancel_button_clicked() {
+ dismissed(true);
+ }
+
+ [GtkCallback]
+ private void on_subscribe_button_clicked() {
+ sensitive = false;
+
+ subscribe_async.begin();
+ }
+
+ private async void subscribe_async() {
+ Gdk.Color color;
+ color_button.get_color(out color);
+
+ try {
+ yield store.subscribe_webcal_async(name_entry.text, URI.parse(url_entry.text),
+ Gfx.rgb_to_uint8_rgb_string(color), null);
+ completed();
+ } catch (Error err) {
+ debug("Unable to create subscription to %s: %s", url_entry.text, err.message);
+ }
+
+ dismissed(true);
+ }
+}
+
+}
+
diff --git a/src/backing/webcal/backing-webcal-activator.vala
b/src/backing/webcal/backing-webcal-activator.vala
new file mode 100644
index 0000000..8423c88
--- /dev/null
+++ b/src/backing/webcal/backing-webcal-activator.vala
@@ -0,0 +1,24 @@
+/* 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.Backing {
+
+internal class WebCalActivator : Activator {
+ private WebCalSubscribable webcal_store;
+
+ public WebCalActivator(string title, WebCalSubscribable store) {
+ base (title, store);
+
+ webcal_store = store;
+ }
+
+ public override Host.Interaction create_interaction(Soup.URI? supplied_uri) {
+ return new WebCalActivatorPane(webcal_store, supplied_uri);
+ }
+}
+
+}
+
diff --git a/src/california-resources.xml b/src/california-resources.xml
index 3a514fb..41751b3 100644
--- a/src/california-resources.xml
+++ b/src/california-resources.xml
@@ -15,5 +15,8 @@
<gresource prefix="/org/yorba/california">
<file compressed="false">rc/show-event.ui</file>
</gresource>
+ <gresource prefix="/org/yorba/california">
+ <file compressed="true">rc/webcal-subscribe.ui</file>
+ </gresource>
</gresources>
diff --git a/src/host/host-create-update-event.vala b/src/host/host-create-update-event.vala
index 55606ac..6ebe994 100644
--- a/src/host/host-create-update-event.vala
+++ b/src/host/host-create-update-event.vala
@@ -262,12 +262,13 @@ public class CreateUpdateEvent : Gtk.Grid, Interaction {
else
create_event(event);
- dismissed();
+ completed();
+ dismissed(true);
}
[GtkCallback]
private void on_cancel_button_clicked() {
- dismissed();
+ dismissed(true);
}
}
diff --git a/src/host/host-interaction.vala b/src/host/host-interaction.vala
index afebc36..efef7ce 100644
--- a/src/host/host-interaction.vala
+++ b/src/host/host-interaction.vala
@@ -27,12 +27,25 @@ public interface Interaction : Gtk.Widget {
public abstract Gtk.Widget? default_widget { get; }
/**
- * Fired when the user has cancelled, closed, or dismissed the { link Interaction}.
+ * Fired when the interaction is cancelled, closed, or dismissed the { link Interaction},
+ * whether due to programmatic reasons or by user request.
*
* This should be called by implementing classes even if other signals suggest or imply that
* the Interaction is dismissed, so a single signal handler can deal with cleanup.
*/
- public signal void dismissed();
+ public signal void dismissed(bool user_request);
+
+ /**
+ * Fired when the { link Interaction} has completed successfully.
+ *
+ * This should only be fired if the Interaction requires valid input from the user to perform
+ * some intensive operation. Merely displaying information and closing the Interaction
+ * should simply fire { link dismissed}.
+ *
+ * "completed" implies that dismissed will be called shortly thereafter, meaning all
+ * cleanup can be handled there.
+ */
+ public signal void completed();
}
}
diff --git a/src/host/host-modal-window.vala b/src/host/host-modal-window.vala
index 45992d5..96d9e9c 100644
--- a/src/host/host-modal-window.vala
+++ b/src/host/host-modal-window.vala
@@ -20,6 +20,7 @@ public class ModalWindow : Gtk.Dialog {
public Gtk.Box content_area { get; private set; }
private Interaction? primary = null;
+ private Gtk.ResponseType response_type = Gtk.ResponseType.CLOSE;
public ModalWindow(Gtk.Window? parent) {
transient_for = parent;
@@ -41,6 +42,7 @@ public class ModalWindow : Gtk.Dialog {
if (primary == null)
primary = interaction;
+ interaction.completed.connect(on_interaction_completed);
interaction.dismissed.connect(on_interaction_dismissed);
}
}
@@ -51,12 +53,17 @@ public class ModalWindow : Gtk.Dialog {
if (primary == interaction)
primary = null;
+ interaction.completed.disconnect(on_interaction_completed);
interaction.dismissed.disconnect(on_interaction_dismissed);
}
}
+ private void on_interaction_completed() {
+ response_type = Gtk.ResponseType.OK;
+ }
+
private void on_interaction_dismissed() {
- response(Gtk.ResponseType.CLOSE);
+ response(response_type);
}
public override void show() {
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index 60ac876..af612c1 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -127,18 +127,18 @@ public class ShowEvent : Gtk.Grid, Interaction {
[GtkCallback]
private void on_remove_button_clicked() {
remove_event(event);
- dismissed();
+ dismissed(true);
}
[GtkCallback]
private void on_update_button_clicked() {
update_event(event);
- dismissed();
+ dismissed(true);
}
[GtkCallback]
private void on_close_button_clicked() {
- dismissed();
+ dismissed(true);
}
}
diff --git a/src/manager/manager-calendar-list.vala b/src/manager/manager-calendar-list.vala
index 61ccf47..4f396a9 100644
--- a/src/manager/manager-calendar-list.vala
+++ b/src/manager/manager-calendar-list.vala
@@ -61,7 +61,7 @@ public class CalendarList : Gtk.Grid, Host.Interaction {
[GtkCallback]
private void on_close_button_clicked() {
- dismissed();
+ dismissed(true);
}
}
diff --git a/src/rc/app-menu.interface b/src/rc/app-menu.interface
index 564080b..d607e13 100644
--- a/src/rc/app-menu.interface
+++ b/src/rc/app-menu.interface
@@ -3,6 +3,11 @@
<menu id="app-menu">
<section>
<item>
+ <attribute name="label" translatable="yes">_Add a new calendar...</attribute>
+ <attribute name="action">app.new-calendar</attribute>
+ <attribute name="accel"><Primary>n</attribute>
+ </item>
+ <item>
<attribute name="label" translatable="yes">_Calendars</attribute>
<attribute name="action">app.calendar-manager</attribute>
<attribute name="accel"><Primary>l</attribute>
diff --git a/src/rc/webcal-subscribe.ui b/src/rc/webcal-subscribe.ui
new file mode 100644
index 0000000..863a23f
--- /dev/null
+++ b/src/rc/webcal-subscribe.ui
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.16.1 -->
+<interface>
+ <requires lib="gtk+" version="3.10"/>
+ <template class="CaliforniaBackingWebCalActivatorPane" parent="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">8</property>
+ <property name="column_spacing">8</property>
+ <child>
+ <object class="GtkLabel" id="pane_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_bottom">8</property>
+ <property name="label" translatable="yes">Subscribe to Web Calendar</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="button_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">end</property>
+ <property name="margin_top">8</property>
+ <property name="spacing">8</property>
+ <property name="homogeneous">True</property>
+ <property name="baseline_position">bottom</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</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_cancel_button_clicked"
object="CaliforniaBackingWebCalActivatorPane" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="subscribe_button">
+ <property name="label" translatable="yes">_Subscribe</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_subscribe_button_clicked"
object="CaliforniaBackingWebCalActivatorPane" swapped="no"/>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </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="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="activates_default">True</property>
+ <property name="width_chars">40</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="color_button">
+ <property name="width_request">16</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">end</property>
+ <property name="valign">end</property>
+ <property name="title" translatable="yes">Select a color for the Web calendar</property>
+ <property name="rgba">rgb(32,32,204)</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Name:</property>
+ <property name="use_underline">True</property>
+ <property name="ellipsize">start</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkEntry" id="url_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="activates_default">True</property>
+ <property name="width_chars">40</property>
+ <property name="input_purpose">url</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="url_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_right">8</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">False</property>
+ <property name="label" translatable="yes">_URL:</property>
+ <property name="use_underline">True</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/src/util/util-uri.vala b/src/util/util-uri.vala
new file mode 100644
index 0000000..3f9b004
--- /dev/null
+++ b/src/util/util-uri.vala
@@ -0,0 +1,71 @@
+/* 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.
+ */
+
+errordomain URIError {
+ INVALID
+}
+
+/**
+ * A collection of URI-related methods.
+ */
+
+namespace California.URI {
+
+/**
+ * Basic validation of a string intended to be parsed as an absolute URI.
+ *
+ * If null or an empty array is passed for "supported_schemes", then the only character checked
+ * for is the presence of a colon separating the scheme from the remainder of the URI.
+ *
+ * If "supported_schemes" are specified, then the entire scheme (name and separator) should be
+ * included, i.e. "http://", "mailto:", etc.
+ */
+public bool is_valid(string? uri, string[]? supported_schemes) {
+ // strip leading and trailing whitespace
+ string? stripped = (uri != null) ? uri.strip() : null;
+ if (String.is_empty(stripped))
+ return false;
+
+ if (supported_schemes == null || supported_schemes.length == 0) {
+ if (!stripped.contains(":"))
+ return false;
+ } else {
+ bool found = false;
+ foreach (string scheme in supported_schemes) {
+ if (stripped.has_prefix(scheme)) {
+ found = true;
+
+ break;
+ }
+ }
+
+ if (!found)
+ return false;
+ }
+
+ // finally, let Soup.URI decide
+ Soup.URI? parsed = new Soup.URI(uri);
+
+ return parsed != null;
+}
+
+/**
+ * Checked creation of a Soup.URI object.
+ *
+ * This shouldn't be used to create "empty" Soup.URI objects.
+ *
+ * @throws URIError
+ */
+public Soup.URI parse(string uri) throws Error {
+ Soup.URI? parsed = new Soup.URI(uri);
+ if (parsed == null)
+ throw new URIError.INVALID("Invalid URI: %s", uri);
+
+ return parsed;
+}
+
+}
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]