Merge trunk r9187 - introduce DirectionalTextHelper. Patch by: jat Review by: tomerigo Original public review at http://gwt-code-reviews.appspot.com/901801 git-svn-id: https://google-web-toolkit.googlecode.com/svn/releases/2.1@9311 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/Anchor.java b/user/src/com/google/gwt/user/client/ui/Anchor.java index bda6fce..35de8ef 100644 --- a/user/src/com/google/gwt/user/client/ui/Anchor.java +++ b/user/src/com/google/gwt/user/client/ui/Anchor.java
@@ -20,7 +20,8 @@ import com.google.gwt.dom.client.Element; import com.google.gwt.i18n.client.BidiUtils; import com.google.gwt.i18n.client.HasDirection; -import com.google.gwt.safehtml.client.HasSafeHtml; +import com.google.gwt.i18n.shared.DirectionEstimator; +import com.google.gwt.i18n.shared.HasDirectionEstimator; import com.google.gwt.safehtml.shared.SafeHtml; /** @@ -30,6 +31,14 @@ * If you want use this anchor only for changing history states, use * {@link Hyperlink} instead. * </p> + * + * <p> + * <h3>Built-in Bidi Text Support</h3> + * This widget is capable of automatically adjusting its direction according to + * its content. This feature is controlled by {@link #setDirectionEstimator} or + * passing a DirectionEstimator parameter to the constructor, and is off by + * default. + * </p> * * <h3>CSS Style Rules</h3> * <ul class='css'> @@ -39,7 +48,11 @@ * @see Hyperlink */ public class Anchor extends FocusWidget implements HasHorizontalAlignment, - HasName, HasHTML, HasWordWrap, HasDirection, HasSafeHtml { + HasName, HasHTML, HasWordWrap, HasDirection, + HasDirectionEstimator, HasDirectionalSafeHtml { + + public static final DirectionEstimator DEFAULT_DIRECTION_ESTIMATOR = + DirectionalTextHelper.DEFAULT_DIRECTION_ESTIMATOR; /** * Creates an Anchor widget that wraps an existing <a> element. @@ -63,6 +76,8 @@ return anchor; } + private final DirectionalTextHelper directionalTextHelper; + private HorizontalAlignmentConstant horzAlign; /** @@ -71,6 +86,45 @@ public Anchor() { setElement(Document.get().createAnchorElement()); setStyleName("gwt-Anchor"); + directionalTextHelper = new DirectionalTextHelper(getAnchorElement(), + /* is inline */ true); + } + + /** + * Creates an anchor for scripting. + * + * @param html the anchor's html + */ + public Anchor(SafeHtml html) { + this(html.asString(), true); + } + + /** + * Creates an anchor for scripting. + * + * The anchor's href is set to <code>javascript : ;</code>, based on the + * expectation that listeners will be added to the anchor. + * + * @param html the anchor's html + * @param dir the html's direction + */ + public Anchor(SafeHtml html, Direction dir) { + this(html.asString(), true, dir, "javascript:;"); + } + + /** + * Creates an anchor for scripting. + * + * The anchor's href is set to <code>javascript : ;</code>, based on the + * expectation that listeners will be added to the anchor. + * + * @param html the anchor's html + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used. + */ + public Anchor(SafeHtml html, DirectionEstimator directionEstimator) { + this(html.asString(), true, directionEstimator, "javascript:;"); } /** @@ -88,10 +142,14 @@ /** * Creates an anchor for scripting. * - * @param html the anchor's text + * The anchor's href is set to <code>javascript : ;</code>, based on the + * expectation that listeners will be added to the anchor. + * + * @param text the anchor's text + * @param dir the text's direction */ - public Anchor(SafeHtml html) { - this(html.asString(), true); + public Anchor(String text, Direction dir) { + this(text, dir, "javascript:;"); } /** @@ -101,6 +159,21 @@ * expectation that listeners will be added to the anchor. * * @param text the anchor's text + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used. + */ + public Anchor(String text, DirectionEstimator directionEstimator) { + this(text, directionEstimator, "javascript:;"); + } + + /** + * Creates an anchor for scripting. + * + * The anchor's href is set to <code>javascript:;</code>, based on the + * expectation that listeners will be added to the anchor. + * + * @param text the anchor's text * @param asHtml <code>true</code> to treat the specified text as html */ public Anchor(String text, boolean asHtml) { @@ -108,7 +181,7 @@ } /** - * Creates an anchor with its text and href (target URL) specified. + * Creates an anchor with its html and href (target URL) specified. * * @param html the anchor's html * @param href the url to which it will link @@ -118,6 +191,66 @@ } /** + * Creates an anchor with its html and href (target URL) specified. + * + * @param html the anchor's html + * @param dir the html's direction + * @param href the url to which it will link + */ + public Anchor(SafeHtml html, Direction dir, String href) { + this(html.asString(), true, dir, href); + } + + /** + * Creates an anchor with its html and href (target URL) specified. + * + * @param html the anchor's html + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used. + * @param href the url to which it will link + */ + public Anchor(SafeHtml html, DirectionEstimator directionEstimator, + String href) { + this(html.asString(), true, directionEstimator, href); + } + + /** + * Creates an anchor with its text and href (target URL) specified. + * + * @param text the anchor's text + * @param href the url to which it will link + */ + public Anchor(String text, String href) { + this(text, false, href); + } + + /** + * Creates an anchor with its text and href (target URL) specified. + * + * @param text the anchor's text + * @param dir the text's direction + * @param href the url to which it will link + */ + public Anchor(String text, Direction dir, String href) { + this(text, false, dir, href); + } + + /** + * Creates an anchor with its text and href (target URL) specified. + * + * @param text the anchor's text + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used. + * @param href the url to which it will link + */ + public Anchor(String text, DirectionEstimator directionEstimator, + String href) { + this(text, false, directionEstimator, href); + } + + /** * Creates an anchor with its text and href (target URL) specified. * * @param text the anchor's text @@ -126,11 +259,7 @@ */ public Anchor(String text, boolean asHTML, String href) { this(); - if (asHTML) { - setHTML(text); - } else { - setText(text); - } + directionalTextHelper.setTextOrHtml(text, asHTML); setHref(href); } @@ -149,6 +278,18 @@ } /** + * Creates a source anchor with a frame target. + * + * @param text the anchor's text + * @param href the url to which it will link + * @param target the target frame (e.g. "_blank" to open the link in a new + * window) + */ + public Anchor(String text, String href, String target) { + this(text, false, href, target); + } + + /** * Creates a source anchor (link to URI). * * That is, an anchor with an href attribute specifying the destination URI. @@ -165,30 +306,6 @@ } /** - * Creates an anchor with its text and href (target URL) specified. - * - * @param text the anchor's text - * @param href the url to which it will link - */ - public Anchor(String text, String href) { - this(); - setText(text); - setHref(href); - } - - /** - * Creates a source anchor with a frame target. - * - * @param text the anchor's text - * @param href the url to which it will link - * @param target the target frame (e.g. "_blank" to open the link in a new - * window) - */ - public Anchor(String text, String href, String target) { - this(text, false, href, target); - } - - /** * This constructor may be used by subclasses to explicitly use an existing * element. This element must be an <a> element. * @@ -197,12 +314,50 @@ protected Anchor(Element element) { AnchorElement.as(element); setElement(element); + directionalTextHelper = new DirectionalTextHelper(getAnchorElement(), + /* is inline */ true); + } + + /** + * Creates an anchor with its text, direction and href (target URL) specified. + * + * @param text the anchor's text + * @param asHTML <code>true</code> to treat the specified text as html + * @param dir the text's direction + * @param href the url to which it will link + */ + private Anchor(String text, boolean asHTML, Direction dir, String href) { + this(); + directionalTextHelper.setTextOrHtml(text, dir, asHTML); + setHref(href); + } + + /** + * Creates an anchor with its text, direction and href (target URL) specified. + * + * @param text the anchor's text + * @param asHTML <code>true</code> to treat the specified text as html + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used. + * @param href the url to which it will link + */ + private Anchor(String text, boolean asHTML, + DirectionEstimator directionEstimator, String href) { + this(); + directionalTextHelper.setDirectionEstimator(directionEstimator); + directionalTextHelper.setTextOrHtml(text, asHTML); + setHref(href); } public Direction getDirection() { return BidiUtils.getDirectionOnElement(getElement()); } + public DirectionEstimator getDirectionEstimator() { + return directionalTextHelper.getDirectionEstimator(); + } + public HorizontalAlignmentConstant getHorizontalAlignment() { return horzAlign; } @@ -240,7 +395,11 @@ } public String getText() { - return getElement().getInnerText(); + return directionalTextHelper.getTextOrHtml(false); + } + + public Direction getTextDirection() { + return directionalTextHelper.getTextDirection(); } public boolean getWordWrap() { @@ -252,8 +411,35 @@ getAnchorElement().setAccessKey(Character.toString(key)); } + /** + * @deprecated Use {@link #setDirectionEstimator} and / or pass explicit + * direction to {@link #setText}, {@link #setHTML} instead + */ + @Deprecated public void setDirection(Direction direction) { - BidiUtils.setDirectionOnElement(getElement(), direction); + directionalTextHelper.setDirection(direction); + } + + /** + * {@inheritDoc} + * <p> + * See note at {@link #setDirectionEstimator(DirectionEstimator)}. + */ + public void setDirectionEstimator(boolean enabled) { + directionalTextHelper.setDirectionEstimator(enabled); + } + + /** + * {@inheritDoc} + * <p> + * Note: DirectionEstimator should be set before the widget has any content; + * it's highly recommended to set it using a constructor. Reason: if the + * widget already has non-empty content, this will update its direction + * according to the new estimator's result. This may cause flicker, and thus + * should be avoided. + */ + public void setDirectionEstimator(DirectionEstimator directionEstimator) { + directionalTextHelper.setDirectionEstimator(directionEstimator); } @Override @@ -284,7 +470,11 @@ } public void setHTML(String html) { - getElement().setInnerHTML(html); + directionalTextHelper.setTextOrHtml(html, true); + } + + public void setHTML(SafeHtml html, Direction dir) { + directionalTextHelper.setTextOrHtml(html.asString(), dir, true); } public void setName(String name) { @@ -307,7 +497,11 @@ } public void setText(String text) { - getElement().setInnerText(text); + directionalTextHelper.setTextOrHtml(text, false); + } + + public void setText(String text, Direction dir) { + directionalTextHelper.setTextOrHtml(text, dir, false); } public void setWordWrap(boolean wrap) {
diff --git a/user/src/com/google/gwt/user/client/ui/DirectionalTextHelper.java b/user/src/com/google/gwt/user/client/ui/DirectionalTextHelper.java new file mode 100644 index 0000000..68d3fbd --- /dev/null +++ b/user/src/com/google/gwt/user/client/ui/DirectionalTextHelper.java
@@ -0,0 +1,245 @@ +/* + * Copyright 2010 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.client.Element; +import com.google.gwt.i18n.client.BidiUtils; +import com.google.gwt.i18n.client.HasDirection.Direction; +import com.google.gwt.i18n.shared.BidiFormatter; +import com.google.gwt.i18n.shared.DirectionEstimator; +import com.google.gwt.i18n.shared.HasDirectionEstimator; +import com.google.gwt.i18n.shared.WordCountDirectionEstimator; + +/** + * A helper class for displaying bidi (i.e. potentially opposite-direction) text + * or HTML in an element. + * Note: this class assumes that callers perform all their text/html and + * direction manipulations through it alone. + */ +public class DirectionalTextHelper implements HasDirectionEstimator { + + /** + * A default direction estimator instance. + */ + public static final DirectionEstimator DEFAULT_DIRECTION_ESTIMATOR = + WordCountDirectionEstimator.get(); + + /** + * The DirectionEstimator object. + */ + private DirectionEstimator directionEstimator; + + /** + * The target element. + */ + private final Element element; + + /** + * The initial direction of the element. + */ + private Direction initialElementDir; + + /** + * Whether direction was explicitly set on the last {@code setTextOrHtml} + * call. If so, {@link #setDirectionEstimator} will refrain from modifying the + * direction until {@link #setTextOrHtml} is called without specifying an + * explicit direction. + */ + private boolean isDirectionExplicitlySet; + + /** + * Whether the element is inline (e.g. a <span> element, but not a block + * element like <div>). + * This is needed because direction is handled differently for inline elements + * and for non-inline elements. + */ + private final boolean isElementInline; + + /** + * Whether the element contains a nested <span> element used to + * indicate the content's direction. + * <p> + * The element itself is used for this purpose when it is a block element + * (i.e. !isElementInline), but doing so on an inline element often results in + * garbling what follows it. Thus, when the element is inline, a nested + * <span> must be used to carry the content's direction, with an LRM or + * RLM character afterwards to prevent the garbling. + */ + private boolean isSpanWrapped; + + /** + * The direction of the element's content. + * Note: this may not match the direction attribute of the element itself. + * See + * {@link #setTextOrHtml(String, com.google.gwt.i18n.client.HasDirection.Direction, boolean) setTextOrHtml(String, Direction, boolean)} + * for details. + */ + private Direction textDir; + + /** + * @param element The widget's element holding text. + * @param isElementInline Whether the element is an inline element. + */ + public DirectionalTextHelper(Element element, boolean isElementInline) { + this.element = element; + this.isElementInline = isElementInline; + isSpanWrapped = false; + this.initialElementDir = BidiUtils.getDirectionOnElement(element); + textDir = initialElementDir; + // setDirectionEstimator shouldn't refresh appearance of initial empty text. + isDirectionExplicitlySet = true; + } + + public DirectionEstimator getDirectionEstimator() { + return directionEstimator; + } + + public Direction getTextDirection() { + return textDir; + } + + /** + * Get the inner text or html of the element, taking the inner span wrap into + * consideration, if needed. + * + * @param isHtml true to get the inner html, false to get the inner text + * @return the text or html + */ + public String getTextOrHtml(boolean isHtml) { + Element elem = isSpanWrapped ? element.getFirstChildElement() : element; + return isHtml ? elem.getInnerHTML() : elem.getInnerText(); + } + + /** + * Provides implementation for HasDirection's method setDirection (normally + * deprecated), dealing with backwards compatibility issues. + * @deprecated + */ + @Deprecated + public void setDirection(Direction direction) { + BidiUtils.setDirectionOnElement(element, direction); + initialElementDir = direction; + + /* + * For backwards compatibility, assure there's no span wrap, and update the + * content direction. + */ + setInnerTextOrHtml(getTextOrHtml(true), true); + isSpanWrapped = false; + textDir = initialElementDir; + isDirectionExplicitlySet = true; + } + + /** + * See note at + * {@link #setDirectionEstimator(com.google.gwt.i18n.shared.DirectionEstimator)}. + */ + public void setDirectionEstimator(boolean enabled) { + setDirectionEstimator(enabled ? DEFAULT_DIRECTION_ESTIMATOR : null); + } + + /** + * Note: if the element already has non-empty content, this will update + * its direction according to the new estimator's result. This may cause + * flicker, and thus should be avoided; DirectionEstimator should be set + * before the element has any content. + */ + public void setDirectionEstimator(DirectionEstimator directionEstimator) { + this.directionEstimator = directionEstimator; + /* + * Refresh appearance unless direction was explicitly set on last + * setTextOrHtml call. + */ + if (!isDirectionExplicitlySet) { + setTextOrHtml(getTextOrHtml(true), true); + } + } + + /** + * Sets the element's content to the given value (either plain text or HTML). + * If direction estimation is off, the direction is verified to match the + * element's initial direction. Otherwise, the direction is affected as + * described at + * {@link #setTextOrHtml(String, com.google.gwt.i18n.client.HasDirection.Direction, boolean) setTextOrHtml(String, Direction, boolean)}. + * + * @param content the element's new content + * @param isHtml whether the content is HTML + */ + public void setTextOrHtml(String content, boolean isHtml) { + if (directionEstimator == null) { + isSpanWrapped = false; + setInnerTextOrHtml(content, isHtml); + + /* + * Preserves the initial direction of the element. This is different from + * passing the direction parameter explicitly as DEFAULT, which forces the + * element to inherit the direction from its parent. + */ + if (textDir != initialElementDir) { + textDir = initialElementDir; + BidiUtils.setDirectionOnElement(element, initialElementDir); + } + } else { + setTextOrHtml(content, directionEstimator.estimateDirection(content, + isHtml), isHtml); + } + isDirectionExplicitlySet = false; + } + + /** + * Sets the element's content to the given value (either plain text or HTML), + * applying the given direction. + * <p> + * Implementation details: + * <ul> + * <li> If the element is a block element, sets its dir attribute according + * to the given direction. + * <li> Otherwise (i.e. the element is inline), the direction is set using a + * nested <span dir=...> element which holds the content of the element. + * This nested span may be followed by a zero-width Unicode direction + * character (LRM or RLM). This manipulation is necessary to prevent garbling + * in case the direction of the element is opposite to the direction of its + * context. See {@link com.google.gwt.i18n.shared.BidiFormatter} for more + * details. + * </ul> + * + * @param content the element's new content + * @param dir the content's direction + * @param isHtml whether the content is HTML + */ + public void setTextOrHtml(String content, Direction dir, boolean isHtml) { + textDir = dir; + // Set the text and the direction. + if (isElementInline) { + isSpanWrapped = true; + element.setInnerHTML(BidiFormatter.getInstanceForCurrentLocale( + true /* alwaysSpan */).spanWrapWithKnownDir(dir, content, isHtml)); + } else { + isSpanWrapped = false; + BidiUtils.setDirectionOnElement(element, dir); + setInnerTextOrHtml(content, isHtml); + } + isDirectionExplicitlySet = true; + } + + private void setInnerTextOrHtml(String content, boolean isHtml) { + if (isHtml) { + element.setInnerHTML(content); + } else { + element.setInnerText(content); + } + } +}
diff --git a/user/src/com/google/gwt/user/client/ui/HTML.java b/user/src/com/google/gwt/user/client/ui/HTML.java index 6fe54bf..e7e123d 100644 --- a/user/src/com/google/gwt/user/client/ui/HTML.java +++ b/user/src/com/google/gwt/user/client/ui/HTML.java
@@ -17,6 +17,7 @@ import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; +import com.google.gwt.i18n.shared.DirectionEstimator; import com.google.gwt.safehtml.shared.SafeHtml; /** @@ -32,6 +33,14 @@ * used properly. * </p> * + * <p> + * <h3>Built-in Bidi Text Support</h3> + * This widget is capable of automatically adjusting its direction according to + * its content. This feature is controlled by {@link #setDirectionEstimator} or + * passing a DirectionEstimator parameter to the constructor, and is off by + * default. + * </p> + * * <h3>CSS Style Rules</h3> * <ul class='css'> * <li>.gwt-HTML { }</li> @@ -86,18 +95,8 @@ } /** - * Creates an HTML widget with the specified HTML contents. - * - * @param html the new widget's HTML contents - */ - public HTML(String html) { - this(); - setHTML(html); - } - - /** - * Creates an HTML widget with the specified contents and with the - * specified direction. + * Creates an HTML widget with the specified contents and with the specified + * direction. * * @param html the new widget's SafeHtml contents * @param dir the content's direction. Note: {@code Direction.DEFAULT} means @@ -108,6 +107,31 @@ } /** + * Creates an HTML widget with the specified HTML contents and specifies a + * direction estimator. + * + * @param html the new widget's SafeHtml contents + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link Label#DEFAULT_DIRECTION_ESTIMATOR} can be used. + */ + public HTML(SafeHtml html, DirectionEstimator directionEstimator) { + this(); + setDirectionEstimator(directionEstimator); + setHTML(html); + } + + /** + * Creates an HTML widget with the specified HTML contents. + * + * @param html the new widget's HTML contents + */ + public HTML(String html) { + this(); + setHTML(html); + } + + /** * Creates an HTML widget with the specified HTML contents and with the * specified direction. * @@ -145,7 +169,7 @@ } public String getHTML() { - return getTextOrHtml(true); + return directionalTextHelper.getTextOrHtml(true); } /** @@ -156,21 +180,23 @@ * @param html the new widget's HTML content */ public void setHTML(String html) { - setTextOrHtml(html, true); + directionalTextHelper.setTextOrHtml(html, true); + updateHorizontalAlignment(); } /** * Sets the label's content to the given HTML, applying the given direction. * See - * {@link #setText(String, com.google.gwt.i18n.client.HasDirection.Direction) - * setText(String, Direction)} for details on potential effects on alignment. + * {@link #setText(String, com.google.gwt.i18n.client.HasDirection.Direction) setText(String, Direction)} + * for details on potential effects on alignment. * * @param html the new widget's HTML content * @param dir the content's direction. Note: {@code Direction.DEFAULT} means * direction should be inherited from the widget's parent element. */ public void setHTML(String html, Direction dir) { - setTextOrHtml(html, dir, true); + directionalTextHelper.setTextOrHtml(html, dir, true); + updateHorizontalAlignment(); } /** @@ -188,4 +214,8 @@ public void setHTML(SafeHtml html, Direction dir) { setHTML(html.asString(), dir); } + + protected String getTextOrHtml(boolean isHtml) { + return directionalTextHelper.getTextOrHtml(isHtml); + } }
diff --git a/user/src/com/google/gwt/user/client/ui/Hyperlink.java b/user/src/com/google/gwt/user/client/ui/Hyperlink.java index 9d8f2e0..08e25f8 100644 --- a/user/src/com/google/gwt/user/client/ui/Hyperlink.java +++ b/user/src/com/google/gwt/user/client/ui/Hyperlink.java
@@ -20,7 +20,9 @@ import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.HasClickHandlers; import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.safehtml.client.HasSafeHtml; +import com.google.gwt.i18n.client.HasDirection.Direction; +import com.google.gwt.i18n.shared.DirectionEstimator; +import com.google.gwt.i18n.shared.HasDirectionEstimator; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; @@ -46,6 +48,14 @@ * </p> * * <p> + * <h3>Built-in Bidi Text Support</h3> + * This widget is capable of automatically adjusting its direction according to + * its content. This feature is controlled by {@link #setDirectionEstimator} or + * passing a DirectionEstimator parameter to the constructor, and is off by + * default. + * </p> + * + * <p> * <img class='gallery' src='doc-files/Hyperlink.png'/> * </p> * @@ -62,10 +72,14 @@ */ @SuppressWarnings("deprecation") public class Hyperlink extends Widget implements HasHTML, SourcesClickEvents, - HasClickHandlers, HasSafeHtml { + HasClickHandlers, HasDirectionEstimator, HasDirectionalSafeHtml { + + public static final DirectionEstimator DEFAULT_DIRECTION_ESTIMATOR = + DirectionalTextHelper.DEFAULT_DIRECTION_ESTIMATOR; private static HyperlinkImpl impl = GWT.create(HyperlinkImpl.class); - + + protected final DirectionalTextHelper directionalTextHelper; private final Element anchorElem = DOM.createAnchor(); private String targetHistoryToken; @@ -88,21 +102,30 @@ } /** - * Creates a hyperlink with its text and target history token specified. + * Creates a hyperlink with its html and target history token specified. * - * @param text the hyperlink's text - * @param asHTML <code>true</code> to treat the specified text as html + * @param html the hyperlink's safe html + * @param dir the html's direction * @param targetHistoryToken the history token to which it will link * @see #setTargetHistoryToken */ - public Hyperlink(String text, boolean asHTML, String targetHistoryToken) { - this(); - if (asHTML) { - setHTML(text); - } else { - setText(text); - } - setTargetHistoryToken(targetHistoryToken); + public Hyperlink(SafeHtml html, Direction dir, String targetHistoryToken) { + this(html.asString(), true, dir, targetHistoryToken); + } + + /** + * Creates a hyperlink with its html and target history token specified. + * + * @param html the hyperlink's safe html + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used. + * @param targetHistoryToken the history token to which it will link + * @see #setTargetHistoryToken + */ + public Hyperlink(SafeHtml html, DirectionEstimator directionEstimator, + String targetHistoryToken) { + this(html.asString(), true, directionEstimator, targetHistoryToken); } /** @@ -114,8 +137,49 @@ * history processing) */ public Hyperlink(String text, String targetHistoryToken) { + this(text, false, targetHistoryToken); + } + + /** + * Creates a hyperlink with its text and target history token specified. + * + * @param text the hyperlink's text + * @param dir the text's direction + * @param targetHistoryToken the history token to which it will link, which + * may not be null (use {@link Anchor} instead if you don't need + * history processing) + */ + public Hyperlink(String text, Direction dir, String targetHistoryToken) { + this(text, false, dir, targetHistoryToken); + } + + /** + * Creates a hyperlink with its text and target history token specified. + * + * @param text the hyperlink's text + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used. + * @param targetHistoryToken the history token to which it will link, which + * may not be null (use {@link Anchor} instead if you don't need + * history processing) + */ + public Hyperlink(String text, DirectionEstimator directionEstimator, + String targetHistoryToken) { + this(text, false, directionEstimator, targetHistoryToken); + } + + /** + * Creates a hyperlink with its text and target history token specified. + * + * @param text the hyperlink's text + * @param asHTML <code>true</code> to treat the specified text as html + * @param targetHistoryToken the history token to which it will link + * @see #setTargetHistoryToken + */ + public Hyperlink(String text, boolean asHTML, String targetHistoryToken) { this(); - setText(text); + directionalTextHelper.setTextOrHtml(text, asHTML); setTargetHistoryToken(targetHistoryToken); } @@ -129,6 +193,43 @@ sinkEvents(Event.ONCLICK); setStyleName("gwt-Hyperlink"); + directionalTextHelper = new DirectionalTextHelper(anchorElem, + /* is inline */ true); + } + + /** + * Creates a hyperlink with its text target history token specified. + * + * @param text the hyperlink's text + * @param asHTML <code>true</code> to treat the specified text as html + * @param dir the text's direction + * @param targetHistoryToken the history token to which it will link + * @see #setTargetHistoryToken + */ + private Hyperlink(String text, boolean asHTML, Direction dir, + String targetHistoryToken) { + this(); + directionalTextHelper.setTextOrHtml(text, dir, asHTML); + setTargetHistoryToken(targetHistoryToken); + } + + /** + * Creates a hyperlink with its text and target history token specified. + * + * @param text the hyperlink's text + * @param asHTML <code>true</code> to treat the specified text as html + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used. + * @param targetHistoryToken the history token to which it will link + * @see #setTargetHistoryToken + */ + private Hyperlink(String text, boolean asHTML, + DirectionEstimator directionEstimator, String targetHistoryToken) { + this(); + directionalTextHelper.setDirectionEstimator(directionEstimator); + directionalTextHelper.setTextOrHtml(text, asHTML); + setTargetHistoryToken(targetHistoryToken); } /** @@ -151,8 +252,12 @@ ListenerWrapper.WrappedClickListener.add(this, listener); } + public DirectionEstimator getDirectionEstimator() { + return directionalTextHelper.getDirectionEstimator(); + } + public String getHTML() { - return DOM.getInnerHTML(anchorElem); + return directionalTextHelper.getTextOrHtml(true); } /** @@ -166,7 +271,11 @@ } public String getText() { - return DOM.getInnerText(anchorElem); + return directionalTextHelper.getTextOrHtml(false); + } + + public Direction getTextDirection() { + return directionalTextHelper.getTextDirection(); } @Override @@ -187,14 +296,40 @@ ListenerWrapper.WrappedClickListener.remove(this, listener); } - public void setHTML(String html) { - DOM.setInnerHTML(anchorElem, html); + /** + * {@inheritDoc} + * <p> + * See note at {@link #setDirectionEstimator(DirectionEstimator)}. + */ + public void setDirectionEstimator(boolean enabled) { + directionalTextHelper.setDirectionEstimator(enabled); + } + + /** + * {@inheritDoc} + * <p> + * Note: DirectionEstimator should be set before the widget has any content; + * it's highly recommended to set it using a constructor. Reason: if the + * widget already has non-empty content, this will update its direction + * according to the new estimator's result. This may cause flicker, and thus + * should be avoided. + */ + public void setDirectionEstimator(DirectionEstimator directionEstimator) { + directionalTextHelper.setDirectionEstimator(directionEstimator); } public void setHTML(SafeHtml html) { setHTML(html.asString()); } + public void setHTML(String html) { + directionalTextHelper.setTextOrHtml(html, true); + } + + public void setHTML(SafeHtml html, Direction dir) { + directionalTextHelper.setTextOrHtml(html.asString(), dir, true); + } + /** * Sets the history token referenced by this hyperlink. This is the history * token that will be passed to {@link History#newItem} when this link is @@ -211,7 +346,11 @@ } public void setText(String text) { - DOM.setInnerText(anchorElem, text); + directionalTextHelper.setTextOrHtml(text, false); + } + + public void setText(String text, Direction dir) { + directionalTextHelper.setTextOrHtml(text, dir, false); } /** @@ -228,4 +367,3 @@ ensureDebugId(getElement(), baseID, "wrapper"); } } -
diff --git a/user/src/com/google/gwt/user/client/ui/InlineHTML.java b/user/src/com/google/gwt/user/client/ui/InlineHTML.java index a1a0bc1..d12e526 100644 --- a/user/src/com/google/gwt/user/client/ui/InlineHTML.java +++ b/user/src/com/google/gwt/user/client/ui/InlineHTML.java
@@ -17,6 +17,8 @@ import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; + +import com.google.gwt.i18n.shared.DirectionEstimator; import com.google.gwt.safehtml.shared.SafeHtml; /** @@ -32,6 +34,14 @@ * used properly. * </p> * + * <p> + * <h3>Built-in Bidi Text Support</h3> + * This widget is capable of automatically adjusting its direction according to + * its content. This feature is controlled by {@link #setDirectionEstimator} or + * passing a DirectionEstimator parameter to the constructor, and is off by + * default. + * </p> + * * <h3>CSS Style Rules</h3> * <ul class='css'> * <li>.gwt-InlineHTML { }</li> @@ -80,16 +90,6 @@ } /** - * Creates an HTML widget with the specified HTML contents. - * - * @param html the new widget's HTML contents - */ - public InlineHTML(String html) { - this(); - setHTML(html); - } - - /** * Creates an HTML widget with the specified contents and with the * specified direction. * @@ -102,6 +102,31 @@ } /** + * Creates an HTML widget with the specified HTML contents and with a default + * direction estimator. + * + * @param html the new widget's SafeHtml contents + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link Label#DEFAULT_DIRECTION_ESTIMATOR} can be used. + */ + public InlineHTML(SafeHtml html, DirectionEstimator directionEstimator) { + this(); + setDirectionEstimator(directionEstimator); + setHTML(html); + } + + /** + * Creates an HTML widget with the specified HTML contents. + * + * @param html the new widget's HTML contents + */ + public InlineHTML(String html) { + this(); + setHTML(html); + } + + /** * Creates an HTML widget with the specified HTML contents and with the * specified direction. *
diff --git a/user/src/com/google/gwt/user/client/ui/InlineHyperlink.java b/user/src/com/google/gwt/user/client/ui/InlineHyperlink.java index 663d6f9..43b20be 100644 --- a/user/src/com/google/gwt/user/client/ui/InlineHyperlink.java +++ b/user/src/com/google/gwt/user/client/ui/InlineHyperlink.java
@@ -16,6 +16,8 @@ package com.google.gwt.user.client.ui; +import com.google.gwt.i18n.client.HasDirection.Direction; +import com.google.gwt.i18n.shared.DirectionEstimator; import com.google.gwt.safehtml.shared.SafeHtml; /** @@ -24,6 +26,14 @@ * {@link com.google.gwt.user.client.ui.Hyperlink}, save that it lays out * as an inline element, not block. * + * <p> + * <h3>Built-in Bidi Text Support</h3> + * This widget is capable of automatically adjusting its direction according to + * its content. This feature is controlled by {@link #setDirectionEstimator} or + * passing a DirectionEstimator parameter to the constructor, and is off by + * default. + * </p> + * * <h3>CSS Style Rules</h3> * <ul class='css'> * <li>.gwt-InlineHyperlink { }</li> @@ -52,22 +62,31 @@ } /** - * Creates a hyperlink with its text and target history token specified. - * - * @param text the hyperlink's text - * @param asHTML <code>true</code> to treat the specified text as html + * Creates a hyperlink with its html and target history token specified. + * + * @param html the hyperlink's html + * @param dir the html's direction * @param targetHistoryToken the history token to which it will link * @see #setTargetHistoryToken */ - public InlineHyperlink(String text, boolean asHTML, String targetHistoryToken) { - this(); - - if (asHTML) { - setHTML(text); - } else { - setText(text); - } - setTargetHistoryToken(targetHistoryToken); + public InlineHyperlink(SafeHtml html, Direction dir, + String targetHistoryToken) { + this(html.asString(), true, dir, targetHistoryToken); + } + + /** + * Creates a hyperlink with its html and target history token specified. + * + * @param html the hyperlink's html + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link Hyperlink#DEFAULT_DIRECTION_ESTIMATOR} can be used. + * @param targetHistoryToken the history token to which it will link + * @see #setTargetHistoryToken + */ + public InlineHyperlink(SafeHtml html, DirectionEstimator directionEstimator, + String targetHistoryToken) { + this(html.asString(), true, directionEstimator, targetHistoryToken); } /** @@ -79,4 +98,80 @@ public InlineHyperlink(String text, String targetHistoryToken) { this(text, false, targetHistoryToken); } + + /** + * Creates a hyperlink with its text and target history token specified. + * + * @param text the hyperlink's text + * @param dir the text's direction + * @param targetHistoryToken the history token to which it will link + */ + public InlineHyperlink(String text, Direction dir, + String targetHistoryToken) { + this(text, false, dir, targetHistoryToken); + } + + /** + * Creates a hyperlink with its text and target history token specified. + * + * @param text the hyperlink's text + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link Hyperlink#DEFAULT_DIRECTION_ESTIMATOR} can be used. + * @param targetHistoryToken the history token to which it will link + */ + public InlineHyperlink(String text, DirectionEstimator directionEstimator, + String targetHistoryToken) { + this(text, false, directionEstimator, targetHistoryToken); + } + + /** + * Creates a hyperlink with its text and target history token specified. + * + * @param text the hyperlink's text + * @param asHTML <code>true</code> to treat the specified text as html + * @param targetHistoryToken the history token to which it will link + * @see #setTargetHistoryToken + */ + public InlineHyperlink(String text, boolean asHTML, + String targetHistoryToken) { + this(); + directionalTextHelper.setTextOrHtml(text, asHTML); + setTargetHistoryToken(targetHistoryToken); + } + + /** + * Creates a hyperlink with its text and target history token specified. + * + * @param text the hyperlink's text + * @param asHTML <code>true</code> to treat the specified text as html + * @param dir the text's direction + * @param targetHistoryToken the history token to which it will link + * @see #setTargetHistoryToken + */ + private InlineHyperlink(String text, boolean asHTML, Direction dir, + String targetHistoryToken) { + this(); + directionalTextHelper.setTextOrHtml(text, dir, asHTML); + setTargetHistoryToken(targetHistoryToken); + } + + /** + * Creates a hyperlink with its text and target history token specified. + * + * @param text the hyperlink's text + * @param asHTML <code>true</code> to treat the specified text as html + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link Hyperlink#DEFAULT_DIRECTION_ESTIMATOR} can be used. + * @param targetHistoryToken the history token to which it will link + * @see #setTargetHistoryToken + */ + private InlineHyperlink(String text, boolean asHTML, + DirectionEstimator directionEstimator, String targetHistoryToken) { + this(); + directionalTextHelper.setDirectionEstimator(directionEstimator); + directionalTextHelper.setTextOrHtml(text, asHTML); + setTargetHistoryToken(targetHistoryToken); + } }
diff --git a/user/src/com/google/gwt/user/client/ui/InlineLabel.java b/user/src/com/google/gwt/user/client/ui/InlineLabel.java index 75bafab..a6b9efc 100644 --- a/user/src/com/google/gwt/user/client/ui/InlineLabel.java +++ b/user/src/com/google/gwt/user/client/ui/InlineLabel.java
@@ -18,12 +18,22 @@ import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; +import com.google.gwt.i18n.shared.DirectionEstimator; + /** * A widget that contains arbitrary text, <i>not</i> interpreted as HTML. * * This widget uses a <span> element, causing it to be displayed with * inline layout. * + * <p> + * <h3>Built-in Bidi Text Support</h3> + * This widget is capable of automatically adjusting its direction according to + * its content. This feature is controlled by {@link #setDirectionEstimator} or + * passing a DirectionEstimator parameter to the constructor, and is off by + * default. + * </p> + * * <h3>CSS Style Rules</h3> * <ul class='css'> * <li>.gwt-InlineLabel { }</li> @@ -85,6 +95,20 @@ } /** + * Creates a label with the specified text and a default direction estimator. + * + * @param text the new label's text + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link Label#DEFAULT_DIRECTION_ESTIMATOR} can be used. + */ + public InlineLabel(String text, DirectionEstimator directionEstimator) { + this(); + setDirectionEstimator(directionEstimator); + setText(text); + } + + /** * This constructor may be used by subclasses to explicitly use an existing * element. This element must be either a <div> <span> element. *
diff --git a/user/src/com/google/gwt/user/client/ui/Label.java b/user/src/com/google/gwt/user/client/ui/Label.java index 9811f76..9e13446 100644 --- a/user/src/com/google/gwt/user/client/ui/Label.java +++ b/user/src/com/google/gwt/user/client/ui/Label.java
@@ -42,10 +42,8 @@ import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.i18n.client.BidiUtils; import com.google.gwt.i18n.client.HasDirection; -import com.google.gwt.i18n.shared.BidiFormatter; import com.google.gwt.i18n.shared.DirectionEstimator; import com.google.gwt.i18n.shared.HasDirectionEstimator; -import com.google.gwt.i18n.shared.WordCountDirectionEstimator; import com.google.gwt.safehtml.shared.SafeHtml; /** @@ -54,6 +52,14 @@ * This widget uses a <div> element, causing it to be displayed with block * layout. * + * <p> + * <h3>Built-in Bidi Text Support</h3> + * This widget is capable of automatically adjusting its direction according to + * its content. This feature is controlled by {@link #setDirectionEstimator} or + * passing a DirectionEstimator parameter to the constructor, and is off by + * default. + * </p> + * * <h3>CSS Style Rules</h3> * <ul class='css'> * <li>.gwt-Label { }</li> @@ -70,6 +76,9 @@ SourcesMouseEvents, HasAllMouseHandlers, HasDirectionEstimator, HasAutoHorizontalAlignment, IsEditor<LeafValueEditor<String>> { + public static final DirectionEstimator DEFAULT_DIRECTION_ESTIMATOR = + DirectionalTextHelper.DEFAULT_DIRECTION_ESTIMATOR; + /** * Creates a Label widget that wraps an existing <div> or <span> * element. @@ -94,68 +103,28 @@ } /** + * The widget's DirectionalTextHelper object. + */ + final DirectionalTextHelper directionalTextHelper; + + /** * The widget's auto horizontal alignment policy. * @see HasAutoHorizontalAlignment */ private AutoHorizontalAlignmentConstant autoHorizontalAlignment; /** - * The direction of the widget's content. - * Note: this may not match the direction of the widget's top DOM element - * ({@code getElement()}). - * See {@link #setTextOrHtml(String, Direction, boolean)} for details. - */ - private Direction textDir; - - /** - * The widget's DirectionEstimator object. - */ - private DirectionEstimator directionEstimator; - - /** * The widget's horizontal alignment. */ private HorizontalAlignmentConstant horzAlign; /** - * The initial direction of the widget's element. - */ - private Direction initialElementDir; - - /** - * Whether the widget is inline (a <span> element). - * This is needed because direction is handled differently for inline elements - * and for non-inline elements. - * <p> - * In case Label supports types of elements other than span and div, this - * should get true for any element that is inline by default. Another approach - * could be calculating the element's display property, but this may have some - * overhead, and is problematic when the element is yet unattached. - */ - private boolean isElementInline; - - /** - * Whether the widget contains a nested <span> element used to - * indicate the content's direction. - * <p> - * The widget's top element is used for this purpose when it is a <div>, - * but doing so on an inline element often results in garbling what follows - * it. Thus, when the widget's top element is a <span>, a nested - * <span> must be used to carry the content's direction, with an LRM or - * RLM character afterwards to prevent the garbling. - */ - private boolean isSpanWrapped; - - /** * Creates an empty label. */ public Label() { setElement(Document.get().createDivElement()); setStyleName("gwt-Label"); - isElementInline = false; - isSpanWrapped = false; - textDir = Direction.DEFAULT; - initialElementDir = Direction.DEFAULT; + directionalTextHelper = new DirectionalTextHelper(getElement(), false); } /** @@ -173,7 +142,7 @@ * * @param text the new label's text * @param dir the text's direction. Note that {@code DEFAULT} means direction - * should be inherited from the widget's parent element. + * should be inherited from the widget's parent element. */ public Label(String text, Direction dir) { this(); @@ -181,6 +150,20 @@ } /** + * Creates a label with the specified text and a default direction estimator. + * + * @param text the new label's text + * @param directionEstimator A DirectionEstimator object used for automatic + * direction adjustment. For convenience, + * {@link #DEFAULT_DIRECTION_ESTIMATOR} can be used. + */ + public Label(String text, DirectionEstimator directionEstimator) { + this(); + setDirectionEstimator(directionEstimator); + setText(text); + } + + /** * Creates a label with the specified text. * * @param text the new label's text @@ -200,11 +183,10 @@ protected Label(Element element) { setElement(element); String tagName = element.getTagName(); - isElementInline = tagName.equalsIgnoreCase("span"); + boolean isElementInline = tagName.equalsIgnoreCase("span"); assert isElementInline || tagName.equalsIgnoreCase("div"); - isSpanWrapped = false; - initialElementDir = BidiUtils.getDirectionOnElement(element); - textDir = initialElementDir; + directionalTextHelper = new DirectionalTextHelper(getElement(), + isElementInline); } public HandlerRegistration addClickHandler(ClickHandler handler) { @@ -286,7 +268,7 @@ } public DirectionEstimator getDirectionEstimator() { - return directionEstimator; + return directionalTextHelper.getDirectionEstimator(); } /** @@ -297,11 +279,11 @@ } public String getText() { - return getTextOrHtml(false); + return directionalTextHelper.getTextOrHtml(false); } public Direction getTextDirection() { - return textDir; + return directionalTextHelper.getTextDirection(); } public boolean getWordWrap() { @@ -351,14 +333,7 @@ */ @Deprecated public void setDirection(Direction direction) { - BidiUtils.setDirectionOnElement(getElement(), direction); - initialElementDir = direction; - - // For backwards compatibility, assure there's no span wrap, and update the - // content direction. - setInnerTextOrHtml(getTextOrHtml(true), true); - isSpanWrapped = false; - textDir = initialElementDir; + directionalTextHelper.setDirection(direction); updateHorizontalAlignment(); } @@ -368,21 +343,22 @@ * See note at {@link #setDirectionEstimator(DirectionEstimator)}. */ public void setDirectionEstimator(boolean enabled) { - setDirectionEstimator(enabled ? WordCountDirectionEstimator.get() : null); + directionalTextHelper.setDirectionEstimator(enabled); + updateHorizontalAlignment(); } /** * {@inheritDoc} * <p> - * Note: if the widget already has non-empty content, this will update - * its direction according to the new estimator's result. This may cause - * flicker, and thus should be avoided; DirectionEstimator should be set - * before the widget has any content. + * Note: DirectionEstimator should be set before the widget has any content; + * it's highly recommended to set it using a constructor. Reason: if the + * widget already has non-empty content, this will update its direction + * according to the new estimator's result. This may cause flicker, and thus + * should be avoided. */ public void setDirectionEstimator(DirectionEstimator directionEstimator) { - this.directionEstimator = directionEstimator; - // Refresh appearance - setTextOrHtml(getTextOrHtml(true), true); + directionalTextHelper.setDirectionEstimator(directionEstimator); + updateHorizontalAlignment(); } /** @@ -405,13 +381,13 @@ * Doesn't change the widget's direction or horizontal alignment if {@code * directionEstimator} is null. Otherwise, the widget's direction is set using * the estimator, and its alignment may therefore change as described in - * {@link #setText(String, com.google.gwt.i18n.client.HasDirection.Direction) - * setText(String, Direction)}. - * + * {@link #setText(String, com.google.gwt.i18n.client.HasDirection.Direction) setText(String, Direction)}. + * * @param text the widget's new text */ public void setText(String text) { - setTextOrHtml(text, false); + directionalTextHelper.setTextOrHtml(text, false); + updateHorizontalAlignment(); } /** @@ -435,7 +411,8 @@ * direction should be inherited from the widget's parent element. */ public void setText(String text, Direction dir) { - setTextOrHtml(text, dir, false); + directionalTextHelper.setTextOrHtml(text, dir, false); + updateHorizontalAlignment(); } public void setWordWrap(boolean wrap) { @@ -444,9 +421,7 @@ } protected String getTextOrHtml(boolean isHtml) { - Element element = isSpanWrapped ? getElement().getFirstChildElement() - : getElement(); - return isHtml ? element.getInnerHTML() : element.getInnerText(); + return directionalTextHelper.getTextOrHtml(isHtml); } /** @@ -481,22 +456,8 @@ * @param isHtml whether the content is HTML */ protected void setTextOrHtml(String content, boolean isHtml) { - if (directionEstimator == null) { - isSpanWrapped = false; - setInnerTextOrHtml(content, isHtml); - - // Preserves the initial direction of the widget. This is different from - // passing the direction parameter explicitly as DEFAULT, which forces the - // widget to inherit the direction from its parent. - if (textDir != initialElementDir) { - textDir = initialElementDir; - BidiUtils.setDirectionOnElement(getElement(), initialElementDir); - updateHorizontalAlignment(); - } - } else { - setTextOrHtml(content, directionEstimator.estimateDirection(content, - isHtml), isHtml); - } + directionalTextHelper.setTextOrHtml(content, isHtml); + updateHorizontalAlignment(); } /** @@ -523,36 +484,17 @@ * @param isHtml whether the content is HTML */ protected void setTextOrHtml(String content, Direction dir, boolean isHtml) { - textDir = dir; - - // Set the text and the direction. - if (isElementInline) { - isSpanWrapped = true; - getElement().setInnerHTML(BidiFormatter.getInstanceForCurrentLocale( - true /* alwaysSpan */).spanWrapWithKnownDir(dir, content, isHtml)); - } else { - isSpanWrapped = false; - BidiUtils.setDirectionOnElement(getElement(), dir); - setInnerTextOrHtml(content, isHtml); - } - - // Update the horizontal alignment if needed. + directionalTextHelper.setTextOrHtml(content, dir, isHtml); updateHorizontalAlignment(); } - private void setInnerTextOrHtml(String content, boolean isHtml) { - if (isHtml) { - getElement().setInnerHTML(content); - } else { - getElement().setInnerText(content); - } - } - /** * Sets the horizontal alignment of the widget according to the current - * AutoHorizontalAlignment setting. + * AutoHorizontalAlignment setting. Should be invoked whenever the horizontal + * alignment may be affected, i.e. on every modification of the content or its + * direction. */ - private void updateHorizontalAlignment() { + protected void updateHorizontalAlignment() { HorizontalAlignmentConstant align; if (autoHorizontalAlignment == null) { align = null; @@ -562,8 +504,8 @@ /* autoHorizontalAlignment is a truly automatic policy, i.e. either ALIGN_CONTENT_START or ALIGN_CONTENT_END */ align = autoHorizontalAlignment == ALIGN_CONTENT_START ? - HorizontalAlignmentConstant.startOf(textDir) : - HorizontalAlignmentConstant.endOf(textDir); + HorizontalAlignmentConstant.startOf(getTextDirection()) : + HorizontalAlignmentConstant.endOf(getTextDirection()); } if (align != horzAlign) {
diff --git a/user/test/com/google/gwt/user/UISuite.java b/user/test/com/google/gwt/user/UISuite.java index 200e004..02b6946 100644 --- a/user/test/com/google/gwt/user/UISuite.java +++ b/user/test/com/google/gwt/user/UISuite.java
@@ -45,6 +45,7 @@ import com.google.gwt.user.client.ui.DefaultSuggestionDisplayTest; import com.google.gwt.user.client.ui.DelegatingKeyboardListenerCollectionTest; import com.google.gwt.user.client.ui.DialogBoxTest; +import com.google.gwt.user.client.ui.DirectionalTextHelperTest; import com.google.gwt.user.client.ui.DisclosurePanelTest; import com.google.gwt.user.client.ui.DockLayoutPanelRtlTest; import com.google.gwt.user.client.ui.DockLayoutPanelTest; @@ -147,6 +148,7 @@ suite.addTestSuite(DefaultSuggestionDisplayTest.class); suite.addTestSuite(DelegatingKeyboardListenerCollectionTest.class); suite.addTestSuite(DialogBoxTest.class); + suite.addTestSuite(DirectionalTextHelperTest.class); suite.addTestSuite(DisclosurePanelTest.class); suite.addTestSuite(DockLayoutPanelRtlTest.class); suite.addTestSuite(DockLayoutPanelTest.class); @@ -220,4 +222,3 @@ return suite; } } -
diff --git a/user/test/com/google/gwt/user/client/ui/DirectionalTextHelperTest.java b/user/test/com/google/gwt/user/client/ui/DirectionalTextHelperTest.java new file mode 100644 index 0000000..a5e9561 --- /dev/null +++ b/user/test/com/google/gwt/user/client/ui/DirectionalTextHelperTest.java
@@ -0,0 +1,193 @@ +/* + * Copyright 2010 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.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.i18n.client.BidiUtils; +import com.google.gwt.i18n.client.HasDirection.Direction; +import com.google.gwt.i18n.client.LocaleInfo; +import com.google.gwt.i18n.shared.AnyRtlDirectionEstimator; +import com.google.gwt.junit.client.GWTTestCase; + +/** + * Tests {@link DirectionalTextHelper}. + */ +public class DirectionalTextHelperTest extends GWTTestCase { + + private final String EN_TEXT = "abc"; + private final String IW_TEXT = "\u05e0\u05e1\u05e2"; + private final String EN_HTML = "<b style=\"color: red\">" + EN_TEXT + "</b>"; + private final String IW_HTML = "<b style=\"color: red\">" + IW_TEXT + "</b>"; + private Element element; + private DirectionalTextHelper directionalTextHelper; + + @Override + public String getModuleName() { + return "com.google.gwt.user.User"; + } + + // setDirection is deprecated; this only assures backwards compatibility. + public void testSetDirection() { + element = Document.get().createSpanElement(); + directionalTextHelper = new DirectionalTextHelper(element, + /* is inline? */ true); + + directionalTextHelper.setDirection(Direction.RTL); + assertDirection("element's direction is incorrect after setDirection", + Direction.RTL); + + directionalTextHelper.setTextOrHtml(EN_TEXT, Direction.LTR, false); + assertDirection("target's direction is incorrect after setText with a" + + "specific direction", Direction.LTR); + + directionalTextHelper.setTextOrHtml(EN_TEXT, false); + assertDirection("target's direction wasn't reverted to the direction set" + + "by last setDirection when calling setText with no direction argument" + + "and without a directionEstimator", Direction.RTL); + + // We also specifically assert that the direction of the topmost element + // matches the last setDirection. (this is needed only for inline elements). + assertEquals("element's direction does not match the direction set by " + + "last setDirection when calling setText with no direction argument " + + "and without a directionEstimator", Direction.RTL, + BidiUtils.getDirectionOnElement(element)); + } + + public void testSetDirectionEstimator() { + element = Document.get().createSpanElement(); + BidiUtils.setDirectionOnElement(element, Direction.LTR); + directionalTextHelper = new DirectionalTextHelper(element, + /* is inline? */ true); + directionalTextHelper.setDirectionEstimator(true); + + // If the element is span-wrapped, a redundant refresh occurred. + assertFalse("setDirectionEstimator(true) refreshed appearance before text" + + "had been received", isSpanWrapped()); + + directionalTextHelper.setDirectionEstimator(false); + directionalTextHelper.setTextOrHtml(IW_TEXT, false); + assertDirection("Original element's direction (LTR) was modified with no" + + "apparent reason", Direction.LTR); + + directionalTextHelper.setDirectionEstimator(true); + assertDirection("Direction was not refreshed on " + + "setDirectionEstimator(true) after receiving text with no explicit " + + "direction", Direction.RTL); + + directionalTextHelper.setTextOrHtml(IW_TEXT, Direction.LTR, false); + directionalTextHelper.setDirectionEstimator( + AnyRtlDirectionEstimator.get()); + assertDirection("Direction was refreshed on setDirectionEstimator after " + + "receiving text with explicit direction", Direction.LTR); + + directionalTextHelper.setTextOrHtml(IW_TEXT, false); + directionalTextHelper.setDirectionEstimator(false); + assertDirection("Direction was not reset to the initial element direction" + + " on turning off direction estimation when last call to setTextOrHtml" + + " did not declare explicit direction.", Direction.LTR); + } + + public void testSetDirectionEstimatorAndSetHtml() { + testSetTextOrHtml(true); + } + + public void testSetDirectionEstimatorAndSetText() { + testSetTextOrHtml(false); + } + + /** + * Asserts that both the {@link HasDirectionalText#getTextDirection} and the + * physical dir attribute match the expected direction. + * + * @param message Assertion message + * @param expected Expected direction + */ + private void assertDirection(String message, Direction expected) { + assertTrue("dir attribute mismatch: " + message, + expected == getElementDirection() || + /* For inline elements, empty dir attribute is acceptable if LTR is + * expected and the locale is not RTL. */ + isSpanWrapped() && getElementDirection() == Direction.DEFAULT && + (expected == Direction.RTL) == LocaleInfo.getCurrentLocale().isRTL()); + + assertEquals("textDir mismatch: " + message, expected, + directionalTextHelper.getTextDirection()); + } + + private Direction getElementDirection() { + Element elem = isSpanWrapped() ? element.getFirstChildElement() : element; + return BidiUtils.getDirectionOnElement(elem); + } + + // This will not work generally. It assumes that the widget's content isn't + // consist of a span tag. + private boolean isSpanWrapped() { + Element inner = element.getFirstChildElement(); + return inner != null && inner.getTagName().equalsIgnoreCase("span"); + } + + private void testSetTextOrHtml(boolean isHtml) { + String enContent = isHtml ? EN_HTML : EN_TEXT; + String iwContent = isHtml ? IW_HTML : IW_TEXT; + for (int i = 0; i < 2; i++) { + boolean isDiv = i == 0; + String id = isDiv ? "div widget: " : "span widget: "; + element = isDiv ? Document.get().createDivElement() : + Document.get().createSpanElement(); + directionalTextHelper = new DirectionalTextHelper(element, + /* is inline? */ !isDiv); + + directionalTextHelper.setTextOrHtml(enContent, isHtml); + assertDirection(id + "widget's direction is not DEFAULT upon " + + "standard initialization", Direction.DEFAULT); + + directionalTextHelper.setTextOrHtml(iwContent, Direction.RTL, isHtml); + assertDirection(id + "widget's direction is not RTL after it was" + + " explicitly set to RTL", Direction.RTL); + + directionalTextHelper.setTextOrHtml(enContent, isHtml); + assertDirection(id + "widget's direction was not specified, and no" + + " estimator specified, thus should return to initial value (DEFAULT)", + Direction.DEFAULT); + + // Toggling on direction estimation from now on. + directionalTextHelper.setDirectionEstimator(true); + + assertDirection(id + "widget's direction wasn't instantly updated" + + " to LTR on switching direction estimation on", Direction.LTR); + + directionalTextHelper.setTextOrHtml(iwContent, isHtml); + assertDirection(id + "widget's direction wasn't estimated as RTL", + Direction.RTL); + + directionalTextHelper.setTextOrHtml(iwContent, Direction.LTR, isHtml); + assertDirection(id + "widget's direction is not LTR after it was" + + " explicitly set to LTR (direction estimation is on)", Direction.LTR); + + directionalTextHelper.setTextOrHtml(iwContent, Direction.DEFAULT, isHtml); + assertDirection(id + "widget's direction is not DEFAULT after it" + + " was explicitly set to DEFAULT (direction estimation is on)", + Direction.DEFAULT); + + // TODO(jlabanca): Need a cross-browser way to test innerHTML. + // assertEquals(id + "retreived html is incorrect", iwContent, + // directionalTextHelper.getTextOrHtml(true).toLowerCase()); + assertEquals(id + "retreived text is incorrect", IW_TEXT, + directionalTextHelper.getTextOrHtml(false).toLowerCase()); + } + } +}
diff --git a/user/test/com/google/gwt/user/client/ui/HTMLTest.java b/user/test/com/google/gwt/user/client/ui/HTMLTest.java index ee84fc1..a483950 100644 --- a/user/test/com/google/gwt/user/client/ui/HTMLTest.java +++ b/user/test/com/google/gwt/user/client/ui/HTMLTest.java
@@ -15,10 +15,7 @@ */ package com.google.gwt.user.client.ui; -import com.google.gwt.dom.client.Element; -import com.google.gwt.i18n.client.BidiUtils; import com.google.gwt.i18n.client.HasDirection.Direction; -import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.safehtml.shared.SafeHtmlUtils; /** @@ -28,9 +25,6 @@ public class HTMLTest extends LabelTest { private static final String html = "<b>hello</b><i>world</i>"; - private final String EN_HTML = "<b style=\"color: red\">" + EN_TEXT + "</b>"; - private final String IW_HTML = "<b style=\"color: red\">" + IW_TEXT + "</b>"; - private HTML label; @Override public String getModuleName() { @@ -58,43 +52,6 @@ assertEquals(Direction.RTL, htmlElementRTL.getTextDirection()); } - // setDirection is deprecated; this only assures backwards compatibility. - public void testSetDirection() { - for (int i = 0; i < 2; i++) { - String id = i == 0 ? "div label: " : "span label: "; - label = HTML.wrap(i == 0 ? createAttachedDivElement() : - createAttachedSpanElement()); - label.setDirection(Direction.RTL); - assertLabelDirection(id + "label's direction is incorrect after " + - "setDirection", Direction.RTL); - - label.setText(EN_TEXT, Direction.LTR); - assertLabelDirection(id + "label's direction is incorrect after " + - "setText with a specific direction", Direction.LTR); - - label.setText(EN_TEXT); - assertLabelDirection(id + "label's direction wasn't reverted to the " + - "direction set by last setDirection when calling setText with no " + - "direction argument and without a directionEstimator", Direction.RTL); - if (i == 1) { - // For span element, we also specifically assert that the direction of - // the topmost element matches the last setDirection. - assertEquals(id + "element's direction does not match the direction " + - "set by last setDirection when calling setText with no direction " + - "argument and without a directionEstimator", Direction.RTL, - BidiUtils.getDirectionOnElement(label.getElement())); - } - } - } - - public void testSetDirectionEstimatorAndSetHtml() { - testSetDirectionEstimatorAndSetTextOrHtml(true); - } - - public void testSetDirectionEstimatorAndSetText() { - testSetDirectionEstimatorAndSetTextOrHtml(false); - } - public void testSetSafeHtml() { HTML htmlElement = new HTML("<b>foo</b>"); htmlElement.setHTML(SafeHtmlUtils.fromSafeConstant(html)); @@ -111,98 +68,4 @@ assertEquals(Direction.LTR, htmlElement.getDirection()); } - /** - * Asserts that both the {@link Label#getTextDirection} and the physical dir - * attribute match the expected direction. - * - * @param message Assertion message - * @param expected Expected direction - */ - private void assertLabelDirection(String message, Direction expected) { - assertTrue("attribute mismatch: " + message, - expected == getLabelDirection() || - /* For inline elements, empty dir attribute is acceptable if LTR is - * expected and the locale is not RTL. */ - isSpanWrapped() && getLabelDirection() == Direction.DEFAULT && - expected == Direction.LTR && !LocaleInfo.getCurrentLocale().isRTL()); - - assertEquals("textDir mismatch: " + message, expected, - label.getTextDirection()); - } - - private Direction getLabelDirection() { - Element element = isSpanWrapped() ? - label.getElement().getFirstChildElement() : label.getElement(); - - return BidiUtils.getDirectionOnElement(element); - } - - // This will not work generally. It assumes that the label's content isn't - // consist of a span tag. - private boolean isSpanWrapped() { - Element inner = label.getElement().getFirstChildElement(); - return inner != null && inner.getTagName().equalsIgnoreCase("span"); - } - - private void setLabelTextOrHtml(String content, boolean isHtml) { - if (isHtml) { - label.setHTML(content); - } else { - label.setText(content); - } - } - - private void setLabelTextOrHtml(String content, Direction dir, boolean isHtml) { - if (isHtml) { - label.setHTML(content, dir); - } else { - label.setText(content, dir); - } - } - - private void testSetDirectionEstimatorAndSetTextOrHtml(boolean isHtml) { - String enContent = isHtml ? EN_HTML : EN_TEXT; - String iwContent = isHtml ? IW_HTML : IW_TEXT; - for (int i = 0; i < 2; i++) { - String id = i == 0 ? "div label: " : "span label: "; - label = HTML.wrap(i == 0 ? createAttachedDivElement() : - createAttachedSpanElement()); - - setLabelTextOrHtml(enContent, isHtml); - assertLabelDirection(id + "label's direction is not DEFAULT upon " + - "standard initialization", Direction.DEFAULT); - - setLabelTextOrHtml(iwContent, Direction.RTL, isHtml); - assertLabelDirection(id + "label's direction is not RTL after it was" + - " explicitly set to RTL", Direction.RTL); - - setLabelTextOrHtml(enContent, isHtml); - assertLabelDirection(id + "label's direction was not specified, and no" + - " estimator specified, thus should return to initial value (DEFAULT)", - Direction.DEFAULT); - - label.setDirectionEstimator(true); - assertLabelDirection(id + "label's direction wasn't instantly updated" + - " to LTR on switching direction estimation on", Direction.LTR); - - setLabelTextOrHtml(iwContent, isHtml); - assertLabelDirection(id + "label's direction wasn't estimated as RTL", - Direction.RTL); - - setLabelTextOrHtml(iwContent, Direction.LTR, isHtml); - assertLabelDirection(id + "label's direction is not LTR after it was" + - " explicitly set to LTR (direction estimation is on)", Direction.LTR); - - setLabelTextOrHtml(iwContent, Direction.DEFAULT, isHtml); - assertLabelDirection(id + "label's direction is not DEFAULT after it" + - " was explicitly set to DEFAULT (direction estimation is on)", - Direction.DEFAULT); - - // TODO(jlabanca): Need a cross-browser way to test innerHTML. - // assertEquals(id + "retreived html is incorrect", iwContent, - // label.getHTML().toLowerCase()); - assertEquals(id + "retreived text is incorrect", IW_TEXT, - label.getText().toLowerCase()); - } - } }
diff --git a/user/test/com/google/gwt/user/client/ui/LabelTest.java b/user/test/com/google/gwt/user/client/ui/LabelTest.java index 00a949d..e697ea8 100644 --- a/user/test/com/google/gwt/user/client/ui/LabelTest.java +++ b/user/test/com/google/gwt/user/client/ui/LabelTest.java
@@ -92,7 +92,7 @@ // Initialize the div with a specific direction, to verify it remembers its // original direction on turning direction estimator off. BidiUtils.setDirectionOnElement(elem, Direction.LTR); - label = Label.wrap(createAttachedDivElement()); + label = Label.wrap(elem); label.setAutoHorizontalAlignment( HasAutoHorizontalAlignment.ALIGN_CONTENT_END); @@ -118,22 +118,40 @@ HasAutoHorizontalAlignment.ALIGN_CONTENT_START); label.setDirectionEstimator(false); - assertAlign("direction was supposed to be reset to the original " + - "ALIGN_LEFT after turning off direction estimator, and automatic " + - "horizontal alignment was to ALIGN_CONTENT_START", + assertAlign("horizontal alignment was supposed to be reset to the " + + "original ALIGN_LEFT after turning off direction estimator, and " + + "automatic horizontal alignment was to ALIGN_CONTENT_START", HasHorizontalAlignment.ALIGN_LEFT, HasAutoHorizontalAlignment.ALIGN_CONTENT_START); } + public void testSetDirection() { + Label label = new Label(createAttachedSpanElement()); + label.setDirectionEstimator(true); + label.setText(IW_TEXT); + + // Should be span wrapped. + assertTrue(label.getElement().getInnerHTML().toLowerCase().contains("span")); + + // Should not be span wrapped. + label.setDirection(Direction.RTL); + assertEquals(Direction.RTL, label.getDirection()); + assertFalse(label.getElement().getInnerHTML().toLowerCase().contains("span")); + + // Should not be span wrapped. + label.setDirection(Direction.LTR); + assertEquals(Direction.LTR, label.getDirection()); + } + @SuppressWarnings("deprecation") public void testSetSafeHtml() { Label label = new Label("foo"); label.setHTML(SafeHtmlUtils.fromSafeConstant(html1)); - + assertEquals(html1, label.getTextOrHtml(true).toLowerCase()); - + label.setHTML(SafeHtmlUtils.fromSafeConstant(html2), Direction.LTR); - + assertEquals(html2, label.getTextOrHtml(true).toLowerCase()); assertEquals(Direction.LTR, label.getDirection()); }