[california] Remove calendar: Bug #727001



commit a0b0d9e34e231885f7dae9ddcdd66c014e3181d5
Author: Jim Nelson <jim yorba org>
Date:   Fri Sep 12 12:08:48 2014 -0700

    Remove calendar: Bug #727001
    
    User can now remove a calendar via the calendar manager.  The warning
    text depends on if the calendar is local (which is a permanent delete)
    or network (in which case, the network data is untouched).

 po/POTFILES.in                                     |    3 +
 po/POTFILES.skip                                   |    1 +
 src/Makefile.am                                    |    2 +
 .../backing-calendar-source-subscription.vala      |    2 +-
 src/backing/backing-calendar-source.vala           |    4 +-
 src/backing/backing-source.vala                    |   29 +++++-
 src/backing/backing-store.vala                     |   15 +++
 .../backing-eds-calendar-source-subscription.vala  |   24 ++++
 src/backing/eds/backing-eds-calendar-source.vala   |   11 +-
 src/backing/eds/backing-eds-store.vala             |   42 +++++++-
 src/california-resources.xml                       |    3 +
 src/manager/manager-calendar-list.vala             |   20 ++--
 src/manager/manager-remove-calendar.vala           |   79 +++++++++++++
 src/manager/manager-window.vala                    |    2 +-
 src/rc/calendar-manager-list.ui                    |    7 +-
 src/rc/remove-calendar.ui                          |  119 ++++++++++++++++++++
 16 files changed, 341 insertions(+), 22 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0cb24e1..59e4bc7 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -20,6 +20,8 @@ src/host/host-import-calendar.vala
 src/host/host-main-window.vala
 src/host/host-quick-create-event.vala
 src/host/host-show-event.vala
+src/manager/manager-calendar-list-item.vala
+src/manager/manager-remove-calendar.vala
 src/view/month/month-controller.vala
 src/view/week/week-controller.vala
 [type: gettext/glade]src/rc/activator-list.ui
@@ -37,5 +39,6 @@ src/view/week/week-controller.vala
 [type: gettext/glade]src/rc/google-login.ui
 [type: gettext/glade]src/rc/main-window-title.ui
 [type: gettext/glade]src/rc/quick-create-event.ui
+[type: gettext/glade]src/rc/remove-calendar.ui
 [type: gettext/glade]src/rc/show-event.ui
 [type: gettext/glade]src/rc/window-menu.interface
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 00e9fcb..1c3ae7c 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -18,6 +18,7 @@ src/host/host-main-window.c
 src/host/host-quick-create-event.c
 src/host/host-show-event.c
 src/manager/manager-calendar-list-item.c
+src/manager/manager-remove-calendar.c
 src/view/month/month-controller.c
 src/view/week/week-controller.c
 
diff --git a/src/Makefile.am b/src/Makefile.am
index 11179c6..ea6468a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -113,6 +113,7 @@ california_VALASOURCES = \
        manager/manager.vala \
        manager/manager-calendar-list.vala \
        manager/manager-calendar-list-item.vala \
+       manager/manager-remove-calendar.vala \
        manager/manager-window.vala \
        \
        tests/tests.vala \
@@ -205,6 +206,7 @@ california_RC = \
        rc/google-login.ui \
        rc/main-window-title.ui \
        rc/quick-create-event.ui \
+       rc/remove-calendar.ui \
        rc/show-event.ui \
        rc/window-menu.interface \
        $(NULL)
diff --git a/src/backing/backing-calendar-source-subscription.vala 
b/src/backing/backing-calendar-source-subscription.vala
index 9286dca..bb3da65 100644
--- a/src/backing/backing-calendar-source-subscription.vala
+++ b/src/backing/backing-calendar-source-subscription.vala
@@ -22,7 +22,7 @@ public abstract class CalendarSourceSubscription : BaseObject {
     /**
      * The { link CalendarSource} providing this subscription's information.
      */
-    public CalendarSource calendar { get; private set; }
+    public unowned CalendarSource calendar { get; private set; }
     
     /**
      * The date-time window.
diff --git a/src/backing/backing-calendar-source.vala b/src/backing/backing-calendar-source.vala
index fbfb6dd..aa5755d 100644
--- a/src/backing/backing-calendar-source.vala
+++ b/src/backing/backing-calendar-source.vala
@@ -39,8 +39,8 @@ public abstract class CalendarSource : Source {
         ALL
     }
     
-    protected CalendarSource(string id, string title) {
-        base (id, title);
+    protected CalendarSource(Store store, string id, string title) {
+        base (store, id, title);
     }
     
     /**
diff --git a/src/backing/backing-source.vala b/src/backing/backing-source.vala
index c2a0dbc..26a889f 100644
--- a/src/backing/backing-source.vala
+++ b/src/backing/backing-source.vala
@@ -31,6 +31,11 @@ public abstract class Source : BaseObject, Gee.Comparable<Source> {
     public string id { get; private set; }
     
     /**
+     * The { link Store} that owns the { link Source}.
+     */
+    public unowned Store store { get; private set; }
+    
+    /**
      * True if the { link Source} is unavailable for use due to being removed from it's
      * { link Backing.Store}.
      *
@@ -64,16 +69,35 @@ public abstract class Source : BaseObject, Gee.Comparable<Source> {
      * If true, write operations (create, update, remove) should not be attempted.
      *
      * It's possible this can change at run-time by the backend.
+     *
+     * @see is_removable
      */
     public bool read_only { get; protected set; }
     
     /**
+     * Whether the { link Source} can be removed.
+     *
+     * If true, do not attempt to remove this Source from the { link Store}.
+     *
+     * It's possible this can change at run-time by the backend.
+     *
+     * @see read_only
+     */
+    public bool is_removable { get; protected set; }
+    
+    /**
+     * Whether the { link Source} is local-only or has network backing.
+     */
+    public bool is_local { get; protected set; }
+    
+    /**
      * The suggested color to use when displaying the { link Source} or information about or from
      * it.
      */
     public string color { get; set; }
     
-    protected Source(string id, string title) {
+    protected Source(Store store, string id, string title) {
+        this.store = store;
         this.id = id;
         this.title = title;
     }
@@ -87,7 +111,8 @@ public abstract class Source : BaseObject, Gee.Comparable<Source> {
      * @see is_unavailable
      */
     internal void set_unavailable() {
-        is_available = false;
+        if (is_available)
+            is_available = false;
     }
     
     /**
diff --git a/src/backing/backing-store.vala b/src/backing/backing-store.vala
index 527b0a2..e29d3a9 100644
--- a/src/backing/backing-store.vala
+++ b/src/backing/backing-store.vala
@@ -72,6 +72,21 @@ public abstract class Store : BaseObject {
     internal abstract async void close_async(Cancellable? cancellable) throws Error;
     
     /**
+     * Asynchronously remove the { link Source} from the { link Store}.
+     *
+     * This is a permanent deletion of local data and the Source will no longer be available to the
+     * user.  There is no mechanism here for optionally deleting the account or calendar on the
+     * network backend, if any.
+     *
+     * This Store ''must'' be the same as { link Source.store}.  INVALID is thrown otherwise.
+     *
+     * This operation is guaranteed not to succeed if { link Source.is_removable} is false.
+     *
+     * The Store must be open before calling this method.
+     */
+    public abstract async void remove_source_async(Source source, Cancellable? cancellable) throws Error;
+    
+    /**
      * Return a read-ony list of all { link Source}s managed by this { link Store}.
      *
      * The Sources will be sorted by their titles in lexiographic order.
diff --git a/src/backing/eds/backing-eds-calendar-source-subscription.vala 
b/src/backing/eds/backing-eds-calendar-source-subscription.vala
index 963f9c8..14ab0f3 100644
--- a/src/backing/eds/backing-eds-calendar-source-subscription.vala
+++ b/src/backing/eds/backing-eds-calendar-source-subscription.vala
@@ -26,6 +26,8 @@ internal class EdsCalendarSourceSubscription : CalendarSourceSubscription {
         
         this.view = view;
         this.sexp = sexp;
+        
+        eds_calendar.notify[Source.PROP_IS_AVAILABLE].connect(() => { stop(eds_calendar); });
     }
     
     ~EdsCalendarSourceSubscription() {
@@ -72,6 +74,28 @@ internal class EdsCalendarSourceSubscription : CalendarSourceSubscription {
         }
     }
     
+    private void stop(EdsCalendarSource calendar_source) {
+        if (!started || calendar_source.is_available)
+            return;
+        
+        try {
+            // wait for start to complete, for sanity's sake
+            wait_until_started();
+        } catch (Error err) {
+            // call it a day
+            return;
+        }
+        
+        try {
+            view.stop();
+        } catch (Error err) {
+            debug("Unable to stop E.CalClientView for %s: %s", to_string(), err.message);
+        }
+        
+        started = false;
+        active = false;
+    }
+    
     private void internal_start(Cancellable? cancellable) throws Error {
         // prepare flags and fields of interest .. don't want known events delivered via signals
         view.set_fields_of_interest(null);
diff --git a/src/backing/eds/backing-eds-calendar-source.vala 
b/src/backing/eds/backing-eds-calendar-source.vala
index ad587a1..d3219fb 100644
--- a/src/backing/eds/backing-eds-calendar-source.vala
+++ b/src/backing/eds/backing-eds-calendar-source.vala
@@ -13,16 +13,17 @@ namespace California.Backing {
 internal class EdsCalendarSource : CalendarSource {
     private const int UPDATE_DELAY_MSEC = 500;
     
-    private E.Source eds_source;
-    private E.SourceCalendar eds_calendar;
+    internal E.Source eds_source;
+    internal E.SourceCalendar eds_calendar;
+    
     private E.CalClient? client = null;
     private Scheduled? scheduled_source_write = null;
     private Scheduled? scheduled_source_read = null;
     private Gee.HashSet<string> dirty_read_properties = new Gee.HashSet<string>();
     private Cancellable? source_write_cancellable = null;
     
-    public EdsCalendarSource(E.Source eds_source, E.SourceCalendar eds_calendar) {
-        base (eds_source.uid, eds_source.display_name);
+    public EdsCalendarSource(EdsStore store, E.Source eds_source, E.SourceCalendar eds_calendar) {
+        base (store, eds_source.uid, eds_source.display_name);
         
         this.eds_source = eds_source;
         this.eds_calendar = eds_calendar;
@@ -41,6 +42,8 @@ internal class EdsCalendarSource : CalendarSource {
         title = eds_source.display_name;
         visible = eds_calendar.selected;
         color = eds_calendar.color;
+        is_local = eds_calendar.backend_name == "local";
+        is_removable = eds_source.removable;
         
         // when changed within the app, need to write it back out
         notify[PROP_TITLE].connect(on_title_changed);
diff --git a/src/backing/eds/backing-eds-store.vala b/src/backing/eds/backing-eds-store.vala
index 70f8b71..8c192a4 100644
--- a/src/backing/eds/backing-eds-store.vala
+++ b/src/backing/eds/backing-eds-store.vala
@@ -46,6 +46,46 @@ internal class EdsStore : Store, WebCalSubscribable, CalDAVSubscribable {
     /**
      * @inheritDoc
      */
+    public override async void remove_source_async(Source source, Cancellable? cancellable) throws Error {
+        if (!is_open)
+            throw new BackingError.UNAVAILABLE("EDS not open");
+        
+        if (source.store != this) {
+            throw new BackingError.INVALID("Attempted to remove source %s from wrong store %s",
+                source.to_string(), to_string());
+        }
+        
+        EdsCalendarSource? calendar_source = source as EdsCalendarSource;
+        if (calendar_source == null)
+            throw new BackingError.INVALID("Unknown EDS source %s", source.to_string());
+        
+        //
+        // don't use remove_eds_source because that closes the source in the background; need to
+        // shut it down then remove it from the backing
+        //
+        
+        // remove internally
+        if (!sources.unset(calendar_source.eds_source)) {
+            throw new BackingError.INVALID("EDS source %s not registered to store %s", source.to_string(),
+                to_string());
+        }
+        
+        // make unavailable; this removes events
+        calendar_source.set_unavailable();
+        
+        // report dropped
+        source_removed(calendar_source);
+        
+        // close source; this shuts down outstanding subscriptions
+        yield calendar_source.close_async(cancellable);
+        
+        // remove from EDS
+        yield calendar_source.eds_source.remove(cancellable);
+    }
+    
+    /**
+     * @inheritDoc
+     */
     public async void subscribe_webcal_async(string title, Soup.URI uri, string? username, string color,
         Cancellable? cancellable) throws Error {
         yield subscribe_eds_async(title, uri, username, color, "webcal", cancellable);
@@ -131,7 +171,7 @@ internal class EdsStore : Store, WebCalSubscribable, CalDAVSubscribable {
         if (eds_calendar == null)
             return;
         
-        EdsCalendarSource calendar = new EdsCalendarSource(eds_source, eds_calendar);
+        EdsCalendarSource calendar = new EdsCalendarSource(this, eds_source, eds_calendar);
         try {
             yield calendar.open_async(null);
         } catch (Error err) {
diff --git a/src/california-resources.xml b/src/california-resources.xml
index 0be1eb7..c09f7c7 100644
--- a/src/california-resources.xml
+++ b/src/california-resources.xml
@@ -49,6 +49,9 @@
         <file compressed="true">rc/quick-create-event.ui</file>
     </gresource>
     <gresource prefix="/org/yorba/california">
+        <file compressed="true">rc/remove-calendar.ui</file>
+    </gresource>
+    <gresource prefix="/org/yorba/california">
         <file compressed="false">rc/show-event.ui</file>
     </gresource>
     <gresource prefix="/org/yorba/california">
diff --git a/src/manager/manager-calendar-list.vala b/src/manager/manager-calendar-list.vala
index b64763f..78cbf4a 100644
--- a/src/manager/manager-calendar-list.vala
+++ b/src/manager/manager-calendar-list.vala
@@ -52,13 +52,9 @@ internal class CalendarList : Gtk.Grid, Toolkit.Card {
         Backing.Manager.instance.notify[Backing.Manager.PROP_IS_OPEN].connect(on_manager_opened_closed);
         
         model.bind_property(Toolkit.ListBoxModel.PROP_SELECTED, edit_button, "sensitive",
-            BindingFlags.SYNC_CREATE, transform_selected_to_sensitive);
+            BindingFlags.SYNC_CREATE, transform_selected_to_edit_sensitive);
         model.bind_property(Toolkit.ListBoxModel.PROP_SELECTED, remove_button, "sensitive",
-            BindingFlags.SYNC_CREATE, transform_selected_to_sensitive);
-        
-        // TODO: Remove this when deleting a calendar is implemented
-        remove_button.visible = false;
-        remove_button.no_show_all = true;
+            BindingFlags.SYNC_CREATE, transform_selected_to_remove_sensitive);
     }
     
     ~CalendarList() {
@@ -68,8 +64,14 @@ internal class CalendarList : Gtk.Grid, Toolkit.Card {
         Backing.Manager.instance.notify[Backing.Manager.PROP_IS_OPEN].disconnect(on_manager_opened_closed);
     }
     
-    private bool transform_selected_to_sensitive(Binding binding, Value source_value, ref Value 
target_value) {
-        target_value = model.selected != null;
+    private bool transform_selected_to_edit_sensitive(Binding binding, Value source_value, ref Value 
target_value) {
+        target_value = model.selected != null && !model.selected.read_only;
+        
+        return true;
+    }
+    
+    private bool transform_selected_to_remove_sensitive(Binding binding, Value source_value, ref Value 
target_value) {
+        target_value = model.selected != null && model.selected.is_removable;
         
         return true;
     }
@@ -140,6 +142,8 @@ internal class CalendarList : Gtk.Grid, Toolkit.Card {
     
     [GtkCallback]
     private void on_remove_button_clicked() {
+        if (model.selected != null)
+            jump_to_card_by_name(RemoveCalendar.ID, model.selected);
     }
     
     [GtkCallback]
diff --git a/src/manager/manager-remove-calendar.vala b/src/manager/manager-remove-calendar.vala
new file mode 100644
index 0000000..bc83107
--- /dev/null
+++ b/src/manager/manager-remove-calendar.vala
@@ -0,0 +1,79 @@
+/* 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.Manager {
+
+[GtkTemplate (ui = "/org/yorba/california/rc/remove-calendar.ui")]
+private class RemoveCalendar : Gtk.Grid, Toolkit.Card {
+    public const string ID = "RemoveCalendar";
+    
+    public string card_id { get { return ID; } }
+    
+    public string? title { get { return null; } }
+    
+    public Gtk.Widget? default_widget { get { return null; } }
+    
+    public Gtk.Widget? initial_focus { get { return null; } }
+    
+    [GtkChild]
+    private Gtk.Label explanation_label;
+    
+    private Backing.CalendarSource? source = null;
+    
+    public RemoveCalendar() {
+    }
+    
+    public void jumped_to(Toolkit.Card? from, Toolkit.Card.Jump reason, Value? message) {
+        source = message as Backing.CalendarSource;
+        if (source == null) {
+            jump_back();
+            
+            return;
+        }
+        
+        string fmt;
+        if (source.is_local)
+            fmt = _("This will remove the %s local calendar from your computer.  All associated information 
will be deleted permanently.");
+        else
+            fmt = _("This will remove the %s network calendar from your computer.  This will not affect 
information stored on the server.");
+        
+        explanation_label.label = fmt.printf("<b>" + GLib.Markup.escape_text(source.title) + "</b>");
+    }
+    
+    [GtkCallback]
+    private void on_cancel_button_clicked() {
+        jump_back();
+    }
+    
+    [GtkCallback]
+    private void on_remove_button_clicked() {
+        remove_calendar_source.begin();
+    }
+    
+    private async void remove_calendar_source() {
+        if (source == null)
+            return;
+        
+        Gdk.Cursor? cursor = Toolkit.set_busy(this);
+        
+        Error? remove_err = null;
+        try {
+            yield source.store.remove_source_async(source, null);
+        } catch (Error err) {
+            remove_err = err;
+        }
+        
+        Toolkit.set_unbusy(this, cursor);
+        
+        if (remove_err == null)
+            jump_back();
+        else
+            report_error(_("Unable to remove calendar: %s").printf(remove_err.message));
+    }
+}
+
+}
+
diff --git a/src/manager/manager-window.vala b/src/manager/manager-window.vala
index 08e3546..597761d 100644
--- a/src/manager/manager-window.vala
+++ b/src/manager/manager-window.vala
@@ -16,7 +16,7 @@ public class Window : Toolkit.DeckWindow {
     private Window(Gtk.Window? window) {
         base (window, null);
         
-        deck.add_cards(iterate<Toolkit.Card>(calendar_list).to_array_list());
+        deck.add_cards(iterate<Toolkit.Card>(calendar_list, new RemoveCalendar()).to_array_list());
         Activator.prepare_deck(deck, null);
     }
     
diff --git a/src/rc/calendar-manager-list.ui b/src/rc/calendar-manager-list.ui
index 3f76e72..622e696 100644
--- a/src/rc/calendar-manager-list.ui
+++ b/src/rc/calendar-manager-list.ui
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.16.1 -->
+<!-- Generated with glade 3.18.3 -->
 <interface>
   <requires lib="gtk+" version="3.10"/>
   <template class="CaliforniaManagerCalendarList" parent="GtkGrid">
@@ -22,7 +22,8 @@
             <child>
               <object class="GtkListBox" id="calendar_list_box">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
+                <property name="can_focus">True</property>
+                <property name="has_focus">True</property>
                 <property name="hexpand">True</property>
                 <property name="vexpand">True</property>
                 <property name="activate_on_single_click">False</property>
@@ -120,7 +121,7 @@
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <property name="receives_default">True</property>
-            <property name="tooltip_text" translatable="yes">Delete calendar</property>
+            <property name="tooltip_text" translatable="yes">Remove calendar</property>
             <property name="halign">start</property>
             <property name="valign">center</property>
             <property name="hexpand">False</property>
diff --git a/src/rc/remove-calendar.ui b/src/rc/remove-calendar.ui
new file mode 100644
index 0000000..814fe3b
--- /dev/null
+++ b/src/rc/remove-calendar.ui
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+  <requires lib="gtk+" version="3.12"/>
+  <template class="CaliforniaManagerRemoveCalendar" parent="GtkGrid">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="row_spacing">8</property>
+    <child>
+      <object class="GtkButtonBox" id="buttonbox1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="halign">end</property>
+        <property name="margin_top">8</property>
+        <property name="spacing">8</property>
+        <property name="homogeneous">True</property>
+        <property name="layout_style">start</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="CaliforniaManagerRemoveCalendar" swapped="no"/>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="remove_button">
+            <property name="label" translatable="yes">_Remove</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_remove_button_clicked" 
object="CaliforniaManagerRemoveCalendar" swapped="no"/>
+            <style>
+              <class name="destructive-action"/>
+            </style>
+          </object>
+          <packing>
+            <property name="expand">True</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">2</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="explanation_label">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="margin_left">24</property>
+        <property name="margin_right">8</property>
+        <property name="margin_top">8</property>
+        <property name="vexpand">True</property>
+        <property name="xalign">0</property>
+        <property name="yalign">0</property>
+        <property name="label">(empty)</property>
+        <property name="use_markup">True</property>
+        <property name="wrap">True</property>
+        <property name="width_chars">32</property>
+        <property name="max_width_chars">32</property>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkBox" id="box1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="spacing">8</property>
+        <child>
+          <object class="GtkImage" id="image1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="pixel_size">60</property>
+            <property name="icon_name">dialog-warning-symbolic</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="title_label">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">Are you sure you want to remove this 
calendar?</property>
+            <attributes>
+              <attribute name="weight" value="bold"/>
+            </attributes>
+          </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">0</property>
+      </packing>
+    </child>
+  </template>
+</interface>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]