Goktug Gokdogan has uploaded a new change for review.

  https://gwt-review.googlesource.com/2582


Change subject: Introduces generic Composite widgets.
......................................................................

Introduces generic Composite widgets.

Fixes issue 4665.

Adds TypedComposite & TypedResizeComposite widgets that are type
safe. Composite can be 'safely' extended to add delegation to methods
for more specific types.

Adds FocusComposite and PanelComposite based on the new Composite
class.

Change-Id: I41e5c07e978d442db7d8402c57605cec1b3ea09e
---
M user/src/com/google/gwt/user/client/ui/Composite.java
A user/src/com/google/gwt/user/client/ui/FocusComposite.java
A user/src/com/google/gwt/user/client/ui/PanelComposite.java
M user/src/com/google/gwt/user/client/ui/ResizeComposite.java
A user/src/com/google/gwt/user/client/ui/TypedComposite.java
A user/src/com/google/gwt/user/client/ui/TypedResizeComposite.java
6 files changed, 469 insertions(+), 165 deletions(-)



diff --git a/user/src/com/google/gwt/user/client/ui/Composite.java b/user/src/com/google/gwt/user/client/ui/Composite.java
index 257832d..834d4fe 100644
--- a/user/src/com/google/gwt/user/client/ui/Composite.java
+++ b/user/src/com/google/gwt/user/client/ui/Composite.java
@@ -15,181 +15,21 @@
  */
 package com.google.gwt.user.client.ui;

-import com.google.gwt.dom.builder.shared.HtmlBuilderFactory;
-import com.google.gwt.dom.builder.shared.HtmlSpanBuilder;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.event.logical.shared.AttachEvent;
-import com.google.gwt.safehtml.shared.SafeHtml;
-import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Event;
-
 /**
* A type of widget that can wrap another widget, hiding the wrapped widget's * methods. When added to a panel, a composite behaves exactly as if the widget
  * it wraps had been added.
- *
  * <p>
- * The composite is useful for creating a single widget out of an aggregate of
- * multiple other widgets contained in a single panel.
- * </p>
- *
+ * If you want initWidget/getWidget to be type safe please use
+ * {@link TypedComposite} instead.
  * <p>
  * <h3>Example</h3>
  * {@example com.google.gwt.examples.CompositeExample}
  * </p>
+ *
+ * @see TypedComposite
  */
-public abstract class Composite extends Widget implements IsRenderable {
-
-  private Widget widget;
-
-  private IsRenderable renderable;
-
-  private Element elementToWrap;
-
-  @Override
-  public void claimElement(Element element) {
-    if (renderable != null) {
-      renderable.claimElement(element);
-      setElement(widget.getElement());
-    } else {
-      this.elementToWrap = element;
-    }
-  }
-
-  @Override
-  public void initializeClaimedElement() {
-    if (renderable != null) {
-      renderable.initializeClaimedElement();
-    } else {
- elementToWrap.getParentNode().replaceChild(widget.getElement(), elementToWrap);
-    }
-  }
-
-  @Override
-  public boolean isAttached() {
-    if (widget != null) {
-      return widget.isAttached();
-    }
-    return false;
-  }
-
-  @Override
-  public void onBrowserEvent(Event event) {
-    // Fire any handler added to the composite itself.
-    super.onBrowserEvent(event);
-
-    // Delegate events to the widget.
-    widget.onBrowserEvent(event);
-  }
-
-  @Override
-  public SafeHtml render(RenderableStamper stamper) {
-    if (renderable != null) {
-      return renderable.render(stamper);
-    } else {
-      HtmlSpanBuilder spanBuilder = HtmlBuilderFactory.get()
-          .createSpanBuilder();
-      stamper.stamp(spanBuilder).end();
-      return spanBuilder.asSafeHtml();
-    }
-  }
-
-  @Override
-  public void render(RenderableStamper stamper, SafeHtmlBuilder builder) {
-    if (renderable != null) {
-      renderable.render(stamper, builder);
-    } else {
-      builder.append(render(stamper));
-    }
-  }
-
-  /**
-   * Provides subclasses access to the topmost widget that defines this
-   * composite.
-   *
-   * @return the widget
-   */
-  protected Widget getWidget() {
-    return widget;
-  }
-
-  /**
- * Sets the widget to be wrapped by the composite. The wrapped widget must be - * set before calling any {@link Widget} methods on this object, or adding it
-   * to a panel. This method may only be called once for a given composite.
-   *
-   * @param widget the widget to be wrapped
-   */
-  protected void initWidget(Widget widget) {
-    // Validate. Make sure the widget is not being set twice.
-    if (this.widget != null) {
-      throw new IllegalStateException("Composite.initWidget() may only be "
-          + "called once.");
-    }
-
-    if (widget instanceof IsRenderable) {
- // In case the Widget being wrapped is an IsRenderable, we save that fact.
-      this.renderable = (IsRenderable) widget;
-    }
-
-    // Detach the new child.
-    widget.removeFromParent();
-
-    // Use the contained widget's element as the composite's element,
-    // effectively merging them within the DOM.
-    Element elem = widget.getElement();
-    setElement(elem);
-
-    if (PotentialElement.isPotential(elem)) {
-      PotentialElement.as(elem).setResolver(this);
-    }
-
-    // Logical attach.
-    this.widget = widget;
-
-    // Adopt.
-    widget.setParent(this);
-  }
-
-  @Override
-  protected void onAttach() {
-    if (!isOrWasAttached()) {
-      widget.sinkEvents(eventsToSink);
-      eventsToSink = -1;
-    }
-
-    widget.onAttach();
-
- // Clobber the widget's call to setEventListener(), causing all events to - // be routed to this composite, which will delegate back to the widget by - // default (note: it's not necessary to clear this in onDetach(), because
-    // the widget's onDetach will do so).
-    DOM.setEventListener(getElement(), this);
-
-    // Call onLoad() directly, because we're not calling super.onAttach().
-    onLoad();
-    AttachEvent.fire(this, true);
-  }
-
-  @Override
-  protected void onDetach() {
-    try {
-      onUnload();
-      AttachEvent.fire(this, false);
-    } finally {
- // We don't want an exception in user code to keep us from calling the
-      // super implementation (or event listeners won't get cleaned up and
-      // the attached flag will be wrong).
-      widget.onDetach();
-    }
-  }
-
-  @Override
-  protected Element resolvePotentialElement() {
-    setElement(widget.resolvePotentialElement());
-    return getElement();
-  }
+public abstract class Composite extends TypedComposite<Widget> {

   /**
    * This method was for initializing the Widget to be wrapped by this
diff --git a/user/src/com/google/gwt/user/client/ui/FocusComposite.java b/user/src/com/google/gwt/user/client/ui/FocusComposite.java
new file mode 100644
index 0000000..a94dd5a
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/FocusComposite.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.GestureChangeHandler;
+import com.google.gwt.event.dom.client.GestureEndHandler;
+import com.google.gwt.event.dom.client.GestureStartHandler;
+import com.google.gwt.event.dom.client.HasAllFocusHandlers;
+import com.google.gwt.event.dom.client.HasAllGestureHandlers;
+import com.google.gwt.event.dom.client.HasAllKeyHandlers;
+import com.google.gwt.event.dom.client.HasAllMouseHandlers;
+import com.google.gwt.event.dom.client.HasAllTouchHandlers;
+import com.google.gwt.event.dom.client.HasClickHandlers;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.event.dom.client.MouseOutHandler;
+import com.google.gwt.event.dom.client.MouseOverHandler;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.event.dom.client.MouseWheelHandler;
+import com.google.gwt.event.dom.client.TouchCancelHandler;
+import com.google.gwt.event.dom.client.TouchEndHandler;
+import com.google.gwt.event.dom.client.TouchMoveHandler;
+import com.google.gwt.event.dom.client.TouchStartHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+
+/**
+ * A {@link Composite} that wraps {@link Focusable} widgets.
+ *
+ * @param <T> type of the widget wrapped
+ */
+public abstract class FocusComposite<T extends FocusWidget> extends TypedComposite<T> + implements Focusable, HasAllFocusHandlers, HasAllKeyHandlers, HasClickHandlers,
+    HasAllMouseHandlers, HasAllGestureHandlers, HasAllTouchHandlers {
+
+  @Override
+  public int getTabIndex() {
+    return getCheckedWidget().getTabIndex();
+  }
+
+  @Override
+  public void setAccessKey(char key) {
+    getCheckedWidget().setAccessKey(key);
+  }
+
+  @Override
+  public void setFocus(boolean focused) {
+    getCheckedWidget().setFocus(focused);
+  }
+
+  @Override
+  public void setTabIndex(int index) {
+    getCheckedWidget().setTabIndex(index);
+  }
+
+  @Override
+  public HandlerRegistration addFocusHandler(FocusHandler handler) {
+    return getCheckedWidget().addFocusHandler(handler);
+  }
+
+  @Override
+  public HandlerRegistration addBlurHandler(BlurHandler handler) {
+    return getCheckedWidget().addBlurHandler(handler);
+  }
+
+  @Override
+  public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
+    return getCheckedWidget().addKeyDownHandler(handler);
+  }
+
+  @Override
+  public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
+    return getCheckedWidget().addKeyPressHandler(handler);
+  }
+
+  @Override
+  public HandlerRegistration addKeyUpHandler(KeyUpHandler handler) {
+    return getCheckedWidget().addKeyUpHandler(handler);
+  }
+
+  @Override
+  public HandlerRegistration addClickHandler(ClickHandler handler) {
+    return getCheckedWidget().addClickHandler(handler);
+  }
+
+  @Override
+ public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) {
+    return getCheckedWidget().addMouseDownHandler(handler);
+  }
+
+  @Override
+ public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler) {
+    return getCheckedWidget().addMouseMoveHandler(handler);
+  }
+
+  @Override
+  public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) {
+    return getCheckedWidget().addMouseOutHandler(handler);
+  }
+
+  @Override
+ public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) {
+    return getCheckedWidget().addMouseOverHandler(handler);
+  }
+
+  @Override
+  public HandlerRegistration addMouseUpHandler(MouseUpHandler handler) {
+    return getCheckedWidget().addMouseUpHandler(handler);
+  }
+
+  @Override
+ public HandlerRegistration addMouseWheelHandler(MouseWheelHandler handler) {
+    return getCheckedWidget().addMouseWheelHandler(handler);
+  }
+
+  @Override
+ public HandlerRegistration addGestureStartHandler(GestureStartHandler handler) {
+    return getCheckedWidget().addGestureStartHandler(handler);
+  }
+
+  @Override
+ public HandlerRegistration addGestureChangeHandler(GestureChangeHandler handler) {
+    return getCheckedWidget().addGestureChangeHandler(handler);
+  }
+
+  @Override
+ public HandlerRegistration addGestureEndHandler(GestureEndHandler handler) {
+    return getCheckedWidget().addGestureEndHandler(handler);
+  }
+
+  @Override
+ public HandlerRegistration addTouchCancelHandler(TouchCancelHandler handler) {
+    return getCheckedWidget().addTouchCancelHandler(handler);
+  }
+
+  @Override
+  public HandlerRegistration addTouchEndHandler(TouchEndHandler handler) {
+    return getCheckedWidget().addTouchEndHandler(handler);
+  }
+
+  @Override
+ public HandlerRegistration addTouchMoveHandler(TouchMoveHandler handler) {
+    return getCheckedWidget().addTouchMoveHandler(handler);
+  }
+
+  @Override
+ public HandlerRegistration addTouchStartHandler(TouchStartHandler handler) {
+    return getCheckedWidget().addTouchStartHandler(handler);
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/PanelComposite.java b/user/src/com/google/gwt/user/client/ui/PanelComposite.java
new file mode 100644
index 0000000..a68532c
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/PanelComposite.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+import java.util.Iterator;
+
+
+/**
+ * A {@link Composite} that wraps {@link HasWidgets} widgets.
+ * <p>
+ * This class is especially useful with {@code UIBinder} as it lets this
+ * composite to be used as parent in the templates.
+ *
+ * @param <T> type of the widget wrapped
+ */
+public class PanelComposite<T extends Widget & HasWidgets> extends TypedComposite<T>
+    implements HasWidgets.ForIsWidget {
+
+  @Override
+  public void add(Widget w) {
+    getCheckedWidget().add(w);
+  }
+
+  @Override
+  public void add(IsWidget w) {
+    this.add(asWidgetOrNull(w));
+  }
+
+  @Override
+  public boolean remove(Widget w) {
+    return getCheckedWidget().remove(w);
+  }
+
+  @Override
+  public boolean remove(IsWidget w) {
+    return this.remove(asWidgetOrNull(w));
+  }
+
+  @Override
+  public void clear() {
+    getCheckedWidget().clear();
+  }
+
+  @Override
+  public Iterator<Widget> iterator() {
+    return getCheckedWidget().iterator();
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/ResizeComposite.java b/user/src/com/google/gwt/user/client/ui/ResizeComposite.java
index 1054249..2b809bc 100644
--- a/user/src/com/google/gwt/user/client/ui/ResizeComposite.java
+++ b/user/src/com/google/gwt/user/client/ui/ResizeComposite.java
@@ -19,6 +19,9 @@
* A {@link Composite} implementation that implements {@link RequiresResize} and * automatically delegates that interface's methods to its wrapped widget, which
  * must itself implement {@link RequiresResize}.
+ * <p>
+ * If you want initWidget/getWidget to be type safe please use
+ * {@link TypedResizeComposite} instead.
  */
 public abstract class ResizeComposite extends Composite implements
     RequiresResize {
diff --git a/user/src/com/google/gwt/user/client/ui/TypedComposite.java b/user/src/com/google/gwt/user/client/ui/TypedComposite.java
new file mode 100644
index 0000000..0a3fae0
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/TypedComposite.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.dom.builder.shared.HtmlBuilderFactory;
+import com.google.gwt.dom.builder.shared.HtmlSpanBuilder;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.logical.shared.AttachEvent;
+import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+
+/**
+ * A type of widget that can wrap another widget, hiding the wrapped widget's + * methods. When added to a panel, a composite behaves exactly as if the widget
+ * it wraps had been added.
+ *
+ * <p>
+ * <h3>Example</h3>
+ * {@example com.google.gwt.examples.CompositeExample}
+ * </p>
+ *
+ * @param <T> type of the widget wrapped
+ */
+public abstract class TypedComposite<T extends Widget> extends Widget implements IsRenderable {
+
+  private T widget;
+
+  private IsRenderable renderable;
+
+  private Element elementToWrap;
+
+  @Override
+  public void claimElement(Element element) {
+    if (renderable != null) {
+      renderable.claimElement(element);
+      setElement(widget.getElement());
+    } else {
+      this.elementToWrap = element;
+    }
+  }
+
+  @Override
+  public void initializeClaimedElement() {
+    if (renderable != null) {
+      renderable.initializeClaimedElement();
+    } else {
+ elementToWrap.getParentNode().replaceChild(widget.getElement(), elementToWrap);
+    }
+  }
+
+  @Override
+  public boolean isAttached() {
+    if (widget != null) {
+      return widget.isAttached();
+    }
+    return false;
+  }
+
+  @Override
+  public void onBrowserEvent(Event event) {
+    // Fire any handler added to the composite itself.
+    super.onBrowserEvent(event);
+
+    // Delegate events to the widget.
+    widget.onBrowserEvent(event);
+  }
+
+  @Override
+  public SafeHtml render(RenderableStamper stamper) {
+    if (renderable != null) {
+      return renderable.render(stamper);
+    } else {
+      HtmlSpanBuilder spanBuilder = HtmlBuilderFactory.get()
+          .createSpanBuilder();
+      stamper.stamp(spanBuilder).end();
+      return spanBuilder.asSafeHtml();
+    }
+  }
+
+  @Override
+  public void render(RenderableStamper stamper, SafeHtmlBuilder builder) {
+    if (renderable != null) {
+      renderable.render(stamper, builder);
+    } else {
+      builder.append(render(stamper));
+    }
+  }
+
+  /**
+   * Provides subclasses access to the topmost widget that defines this
+   * composite.
+   *
+   * @return the widget
+   */
+  protected T getWidget() {
+    return widget;
+  }
+
+  /**
+ * A {@link #getWidget()} that fail fast if this composite is not initialized yet.
+   */
+  protected T getCheckedWidget() {
+    if (widget == null) {
+      throw new IllegalStateException("initWidget() is not called yet");
+    }
+    return widget;
+  }
+
+  /**
+ * Sets the widget to be wrapped by the composite. The wrapped widget must be + * set before calling any {@link Widget} methods on this object, or adding it
+   * to a panel. This method may only be called once for a given composite.
+   *
+   * @param widget the widget to be wrapped
+   */
+  protected void initWidget(T widget) {
+    // Validate. Make sure the widget is not being set twice.
+    if (this.widget != null) {
+      throw new IllegalStateException("Composite.initWidget() may only be "
+          + "called once.");
+    }
+
+    if (widget instanceof IsRenderable) {
+ // In case the Widget being wrapped is an IsRenderable, we save that fact.
+      this.renderable = (IsRenderable) widget;
+    }
+
+    // Detach the new child.
+    widget.removeFromParent();
+
+    // Use the contained widget's element as the composite's element,
+    // effectively merging them within the DOM.
+    Element elem = widget.getElement();
+    setElement(elem);
+
+    if (PotentialElement.isPotential(elem)) {
+      PotentialElement.as(elem).setResolver(this);
+    }
+
+    // Logical attach.
+    this.widget = widget;
+
+    // Adopt.
+    widget.setParent(this);
+  }
+
+  @Override
+  protected void onAttach() {
+    if (!isOrWasAttached()) {
+      widget.sinkEvents(eventsToSink);
+      eventsToSink = -1;
+    }
+
+    widget.onAttach();
+
+ // Clobber the widget's call to setEventListener(), causing all events to + // be routed to this composite, which will delegate back to the widget by + // default (note: it's not necessary to clear this in onDetach(), because
+    // the widget's onDetach will do so).
+    DOM.setEventListener(getElement(), this);
+
+    // Call onLoad() directly, because we're not calling super.onAttach().
+    onLoad();
+    AttachEvent.fire(this, true);
+  }
+
+  @Override
+  protected void onDetach() {
+    try {
+      onUnload();
+      AttachEvent.fire(this, false);
+    } finally {
+ // We don't want an exception in user code to keep us from calling the
+      // super implementation (or event listeners won't get cleaned up and
+      // the attached flag will be wrong).
+      widget.onDetach();
+    }
+  }
+
+  @Override
+  protected Element resolvePotentialElement() {
+    setElement(widget.resolvePotentialElement());
+    return getElement();
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/TypedResizeComposite.java b/user/src/com/google/gwt/user/client/ui/TypedResizeComposite.java
new file mode 100644
index 0000000..e6d65a8
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/TypedResizeComposite.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+/**
+ * A {@link TypedComposite} implementation that implements {@link RequiresResize} and + * automatically delegates that interface's methods to its wrapped widget, which
+ * must itself implement {@link RequiresResize}.
+ *
+ * @param <T> type of the widget wrapped
+ */
+public abstract class TypedResizeComposite<T extends Widget & RequiresResize>
+    extends TypedComposite<T> implements RequiresResize {
+
+  @Override
+  public void onResize() {
+    getCheckedWidget().onResize();
+  }
+}

--
To view, visit https://gwt-review.googlesource.com/2582
To unsubscribe, visit https://gwt-review.googlesource.com/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I41e5c07e978d442db7d8402c57605cec1b3ea09e
Gerrit-PatchSet: 1
Gerrit-Project: gwt
Gerrit-Branch: master
Gerrit-Owner: Goktug Gokdogan <gok...@google.com>

--
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors
--- You received this message because you are subscribed to the Google Groups "Google Web Toolkit Contributors" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to google-web-toolkit-contributors+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to