Revision: 6114
Author: [email protected]
Date: Thu Sep 10 13:35:35 2009
Log: Introduces inline styles to ui.xml files, completing
http://code.google.com/p/google-web-toolkit/issues/detail?id=3984 to a
first approximation.

Only CssResource works so far, via <ui:style>. @Sprite won't work
until <ui:image> is implemented, and <ui:data> is still needed as well.

Reviewed by: spoon

http://code.google.com/p/google-web-toolkit/source/detail?r=6114

Modified:
  /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java
  /trunk/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
   
/trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java
   
/trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java
  /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css
  /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java
  /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml
  /trunk/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java

=======================================
--- /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java  Wed  
Sep  9 10:35:52 2009
+++ /trunk/user/src/com/google/gwt/uibinder/rebind/UiBinderParser.java  Thu  
Sep 10 13:35:35 2009
@@ -20,6 +20,7 @@
  import com.google.gwt.core.ext.typeinfo.JMethod;
  import com.google.gwt.core.ext.typeinfo.TypeOracle;
  import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.uibinder.parsers.NullInterpreter;
  import com.google.gwt.uibinder.rebind.messages.MessagesWriter;
  import com.google.gwt.uibinder.rebind.model.ImplicitClientBundle;
  import com.google.gwt.uibinder.rebind.model.ImplicitCssResource;
@@ -39,6 +40,7 @@
    private final MessagesWriter messagesWriter;
    private final FieldManager fieldManager;
    private final ImplicitClientBundle bundleClass;
+  private final JClassType cssResourceType;

    public UiBinderParser(UiBinderWriter writer, MessagesWriter  
messagesWriter,
        FieldManager fieldManager, TypeOracle oracle,
@@ -48,7 +50,8 @@
      this.messagesWriter = messagesWriter;
      this.fieldManager = fieldManager;
      this.bundleClass = bundleClass;
-  }
+    this.cssResourceType =  
oracle.findType(CssResource.class.getCanonicalName());
+ }

    /**
     * Parses the root UiBinder element, and kicks off the parsing of the  
rest of
@@ -56,7 +59,7 @@
     */
    public String parse(XMLElement elem) throws UnableToCompleteException {
      // TODO(rjrjr) Clearly need to break these find* methods out into  
their own
-    // parsers, need registration scheme for ui binder's own parsers
+    // parsers, an so need a registration scheme for uibinder-specific  
parsers
      findStyles(elem);
      findResources(elem);
      messagesWriter.findMessagesConfig(elem);
@@ -66,8 +69,16 @@

    private JClassType consumeCssResourceType(XMLElement elem)
        throws UnableToCompleteException {
-    JClassType publicType = consumeTypeAttribute(elem);
-    JClassType cssResourceType =  
oracle.findType(CssResource.class.getCanonicalName());
+    String typeName = elem.consumeAttribute("type", null);
+    if (typeName == null) {
+      return cssResourceType;
+    }
+
+    JClassType publicType = oracle.findType(typeName);
+    if (publicType == null) {
+      writer.die("In %s, no such type %s", elem, typeName);
+    }
+
      if (!publicType.isAssignableTo(cssResourceType)) {
        writer.die("In %s, type %s does not extend %s", elem,
            publicType.getQualifiedSourceName(),
@@ -131,13 +142,18 @@
    }

    private void createStyle(XMLElement elem) throws  
UnableToCompleteException {
-    // Won't be required for long
-    String source = elem.consumeRequiredAttribute("source");
+    String body =  elem.consumeInnerText(new NullInterpreter<String>());
+    if (body.length() > 0 && elem.hasAttribute("source")) {
+      writer.die("In %s, cannot use both a source attribute and inline css  
text.", elem);
+    }
+
+    String source = elem.consumeAttribute("source");
      String name = elem.consumeAttribute("field", "style");
      JClassType publicType = consumeCssResourceType(elem);

      ImplicitCssResource cssMethod = bundleClass.createCssResource(name,  
source,
-        publicType);
+        publicType, body);
+
      FieldWriter field = fieldManager.registerFieldOfGeneratedType(
          cssMethod.getPackageName(), cssMethod.getClassName(),
          cssMethod.getName());
=======================================
--- /trunk/user/src/com/google/gwt/uibinder/rebind/XMLElement.java      Thu Sep 
  
3 16:03:13 2009
+++ /trunk/user/src/com/google/gwt/uibinder/rebind/XMLElement.java      Thu Sep 
 
10 13:35:35 2009
@@ -1,12 +1,12 @@
  /*
   * Copyright 2008 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
@@ -47,7 +47,7 @@
    public interface Interpreter<T> {
      /**
       * Given an XMLElement, return its filtered value.
-     *
+     *
       * @throws UnableToCompleteException on error
       */
      T interpretElement(XMLElement elem) throws UnableToCompleteException;
@@ -115,7 +115,7 @@
    /**
     * Consumes the given attribute and returns its trimmed value, or null  
if it
     * was unset. The returned string is not escaped.
-   *
+   *
     * @param name the attribute's full name (including prefix)
     * @return the attribute's value, or null
     */
@@ -128,7 +128,7 @@
    /**
     * Consumes the given attribute and returns its trimmed value, or the  
given
     * default value if it was unset. The returned string is not escaped.
-   *
+   *
     * @param name the attribute's full name (including prefix)
     * @param defaultValue the value to return if the attribute was unset
     * @return the attribute's value, or defaultValue
@@ -143,7 +143,7 @@

    /**
     * Consumes the given attribute as a boolean value.
-   *
+   *
     * @throws UnableToCompleteException
     */
    public boolean consumeBooleanAttribute(String attr)
@@ -177,9 +177,9 @@
     * Consumes and returns all child elements selected by the interpreter.  
Note
     * that text nodes are not elements, and so are not presented for
     * interpretation, and are not consumed.
-   *
+   *
     * @param interpreter Should return true for any child that should be  
consumed
-   *          and returned.
+   *          and returned by the consumeChildElements call
     * @throws UnableToCompleteException
     */
    public Collection<XMLElement> consumeChildElements(
@@ -217,7 +217,7 @@
     * The odds are you want to use
     * {...@link com.google.gwt.templates.parsers.HtmlInterpreter} for an HTML  
value,
     * or {...@link com.google.gwt.templates.parsers.TextInterpreter} for text.
-   *
+   *
     * @param interpreter Called for each element, expected to return a  
string
     *          replacement for it, or null if it should be left as is
     */
@@ -250,7 +250,7 @@
     * This call requires an interpreter to make sense of any special  
children.
     * The odds are you want to use
     * {...@link com.google.gwt.templates.parsers.TextInterpreter}
-   *
+   *
     * @throws UnableToCompleteException If any elements present are not  
consumed
     *           by the interpreter
     */
@@ -292,7 +292,7 @@
     */
    public String consumeOpeningTag() {
      String rtn = getOpeningTag();
-
+
      for (int i = getAttributeCount() - 1; i >= 0; i--) {
        getAttribute(i).consumeValue();
      }
@@ -306,7 +306,7 @@
        throws UnableToCompleteException {
      String value = consumeAttribute(name);
      if ("".equals(value)) {
-      writer.die("In %s, missing required attribute name\"%s\"", this);
+      writer.die("In %s, missing required attribute name\"%s\"", this,  
name);
      }
      return value;
    }
@@ -319,8 +319,9 @@
      XMLElement ret = null;
      for (XMLElement child : consumeChildElements()) {
        if (ret != null) {
-        throw new RuntimeException(getLocalName()
-            + " may only contain a single child element.");
+        throw new RuntimeException(String.format(
+            "%s may only contain a single child element, but found"
+                + "%s and %s.", getLocalName(), ret, child));
        }

        ret = child;
=======================================
---  
/trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java  
 
Wed Sep  9 10:35:52 2009
+++  
/trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitClientBundle.java  
 
Thu Sep 10 13:35:35 2009
@@ -44,8 +44,8 @@
    public ImplicitClientBundle(String packageName, String  
uiBinderImplClassName,
        String fieldName, MortalLogger logger) {
      this.packageName = packageName;
-    this.className = uiBinderImplClassName + "GenBundle";
-    this.cssBaseName = uiBinderImplClassName + "GenCss";
+    this.className = uiBinderImplClassName + "_GenBundle";
+    this.cssBaseName = uiBinderImplClassName + "_GenCss";
      this.fieldName = fieldName;
      this.logger = logger;
    }
@@ -57,12 +57,13 @@
     * @param source path to the .css file resource
     * @param extendedInterface the public interface implemented by this
     *          CssResource, or null
+   * @param body the inline css text
     * @return
     */
    public ImplicitCssResource createCssResource(String name, String source,
-      JClassType extendedInterface) {
+      JClassType extendedInterface, String body) {
      ImplicitCssResource css = new ImplicitCssResource(packageName,  
cssBaseName
-        + name, name, source, extendedInterface, logger);
+        + name, name, source, extendedInterface, body, logger);
      cssMethods.add(css);
      return css;
    }
=======================================
---  
/trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java   
 
Wed Sep  9 10:35:52 2009
+++  
/trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java   
 
Thu Sep 10 13:35:35 2009
@@ -20,8 +20,14 @@
  import com.google.gwt.resources.css.ExtractClassNamesVisitor;
  import com.google.gwt.resources.css.GenerateCssAst;
  import com.google.gwt.resources.css.ast.CssStylesheet;
+import com.google.gwt.resources.ext.ResourceGeneratorUtil;
  import com.google.gwt.uibinder.rebind.MortalLogger;

+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.Set;

@@ -36,16 +42,33 @@
    private final String name;
    private final String source;
    private final JClassType extendedInterface;
+  private final String body;
    private final MortalLogger logger;
+  private File generatedFile;

    public ImplicitCssResource(String packageName, String className, String  
name,
-      String source, JClassType extendedInterface, MortalLogger logger) {
+      String source, JClassType extendedInterface, String body,
+      MortalLogger logger) {
      this.packageName = packageName;
      this.className = className;
      this.name = name;
-    this.source = source;
      this.extendedInterface = extendedInterface;
+    this.body = body;
      this.logger = logger;
+
+    if (body.length() > 0) {
+      assert "".equals(source); // Enforced for real by the parser
+
+      /*
+       * We're going to write the inline body to a temporary File and  
register
+       * it with the CssResource world under the name in this.source, via
+       * ResourceGeneratorUtil.addNamedFile(). When CssResourceGenerator  
sees
+       * this name in an @Source annotation it will know to use the  
registered
+       * file rather than load a resource.
+       */
+      source = String.format("uibinder:%s.%s.css", packageName, className);
+    }
+    this.source = source;
    }

    /**
@@ -58,29 +81,20 @@
    /**
     * @return the set of CSS classnames in the underlying .css files
     *
-   * @throws UnableToCompleteException if the user has called for a .css  
file we can't find.
+   * @throws UnableToCompleteException if the user has called for a .css  
file we
+   *           can't find.
     */
    public Set<String> getCssClassNames() throws UnableToCompleteException {
-    /*
-     * TODO(rjrjr,bobv) refactor ResourceGeneratorUtil.findResources so we  
can
-     * find them the same way ClientBundle does. For now, just look  
relative to
-     * this package
-     */
-
-    ClassLoader classLoader = ImplicitCssResource.class.getClassLoader();
-    String path = packageName.replace(".", "/");
-
-    String[] sources = source.split(" ");
-    URL[] urls = new URL[sources.length];
-    int i = 0;
-
-    for (String s : sources) {
-      String resourcePath = path + '/' + s;
-      URL found = classLoader.getResource(resourcePath);
-      if (null == found) {
-        logger.die("Unable to find resource: " + resourcePath);
-      }
-      urls[i++] = found;
+    URL[] urls;
+
+    if (body.length() == 0) {
+      urls = getExternalCss();
+    } else {
+      try {
+        urls = new URL[] {getGeneratedFile().toURL()};
+      } catch (MalformedURLException e) {
+        throw new RuntimeException(e);
+      }
      }

      CssStylesheet sheet = GenerateCssAst.exec(logger.getTreeLogger(),  
urls);
@@ -111,9 +125,52 @@
    }

    /**
-   * @return the user declared names of the associated .css files
+   * @return the name of the .css file(s), separate by white space
     */
    public String getSource() {
      return source;
    }
-}
+
+  private URL[] getExternalCss() throws UnableToCompleteException {
+    /*
+     * TODO(rjrjr,bobv) refactor ResourceGeneratorUtil.findResources so we  
can
+     * find them the same way ClientBundle does. For now, just look  
relative to
+     * this package
+     */
+
+    ClassLoader classLoader = ImplicitCssResource.class.getClassLoader();
+    String path = packageName.replace(".", "/");
+
+    String[] sources = source.split(" ");
+    URL[] urls = new URL[sources.length];
+    int i = 0;
+
+    for (String s : sources) {
+      String resourcePath = path + '/' + s;
+      URL found = classLoader.getResource(resourcePath);
+      if (null == found) {
+        logger.die("Unable to find resource: " + resourcePath);
+      }
+      urls[i++] = found;
+    }
+    return urls;
+  }
+
+  private File getGeneratedFile() {
+    if (generatedFile == null) {
+      try {
+        File f = File.createTempFile(String.format("uiBinder_%s_%s",
+            packageName, className), ".css");
+
+        BufferedWriter out = new BufferedWriter(new FileWriter(f));
+        out.write(body);
+        out.close();
+        generatedFile = f;
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+      ResourceGeneratorUtil.addNamedFile(getSource(), generatedFile);
+    }
+    return generatedFile;
+  }
+}
=======================================
--- /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css     
 
Wed Sep  9 10:35:52 2009
+++ /trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.css     
 
Thu Sep 10 13:35:35 2009
@@ -7,6 +7,6 @@
   * Demonstrates that the ui.xml has access to styles that
   * do not back any declared CssResource api
   */
-.privateStyle {
+.privateColor {
    color: SteelBlue;
  }
=======================================
---  
/trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java        
 
Wed Sep  9 10:35:52 2009
+++  
/trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.java        
 
Thu Sep 10 13:35:35 2009
@@ -111,6 +111,8 @@
    @UiField DListElement widgetCrazyDefinitionList;
    @UiField HTMLPanel customTagHtmlPanel;
    @UiField ParagraphElement privateStyleParagraph;
+  @UiField ParagraphElement reallyPrivateStyleParagraph;
+  @UiField SpanElement totallyPrivateStyleSpan;

    public WidgetBasedUi() {
      this.bundledLabel = new Label();
=======================================
---  
/trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml      
 
Wed Sep  9 10:35:52 2009
+++  
/trunk/user/src/com/google/gwt/uibinder/sample/client/WidgetBasedUi.ui.xml      
 
Thu Sep 10 13:35:35 2009
@@ -69,7 +69,30 @@
  <!--
    Tests creating a CssResource from an external file.
   -->
-<ui:style field='myStyle' source='WidgetBasedUi.css'  
type='com.google.gwt.uibinder.sample.client.WidgetBasedUi.Style'/>
+<ui:style field='myStyle' source='WidgetBasedUi.css'
+    type='com.google.gwt.uibinder.sample.client.WidgetBasedUi.Style'/>
+
+<ui:style field='myOtherStyle'  
type='com.google.gwt.uibinder.sample.client.WidgetBasedUi.Style'>
+  /* because we extend WidgetBasedUi.Style and it's tagged @Shared, we can  
refine the menubar
+     style defined in other files */
+
+  .menuBar.psychedelic {
+    background-color: Yellow;
+  }
+
+  /* Visible only to this template  */
+  .privateColor {
+    color: Purple;
+  }
+</ui:style>
+
+<ui:style field='myTotallyPrivateStyle'>
+  /* An inline style tied to no public type */
+
+  .superPrivateColor {
+    background-color: Gold;
+  }
+</ui:style>

  <gwt:DockPanel ui:field="root" width="100%">
    <gwt:Dock direction='NORTH'>
@@ -149,7 +172,7 @@
                  Piggledy
                </ui:msg>
                </div>
-            <gwt:MenuBar vertical="true" styleName="{myStyle.menuBar}">
+            <gwt:MenuBar vertical="true" styleName="{myStyle.menuBar}  
{myOtherStyle.psychedelic}">
                <gwt:MenuItem>delta</gwt:MenuItem>
                <gwt:MenuItem>echo</gwt:MenuItem>
                <gwt:MenuItem>foxtrot</gwt:MenuItem>
@@ -264,8 +287,14 @@
          This stylish paragraph also gets its good looks from the
          external ClientBundle.
        </p>
-      <p ui:field='privateStyleParagraph' class='{myStyle.privateStyle}'>
-        This one is has a private style, visible only inside the ui.xml  
file.
+      <p ui:field='privateStyleParagraph' class='{myStyle.privateColor}'>
+        This one is has a private style, defined out in WidgetBaseUi.css  
and used only by this ui.xml file.
+      </p>
+      <p ui:field='reallyPrivateStyleParagraph'  
class='{myOtherStyle.privateColor}'>
+        And this style is defined right here inside the ui.xml file.
+        <span ui:field='totallyPrivateStyleSpan'  
class='{myTotallyPrivateStyle.superPrivateColor}'>
+          (This one too, but even more so.)
+        </span>
        </p>
        <h2>Evolving</h2>
        <p>
=======================================
---  
/trunk/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java        
 
Wed Sep  9 10:35:52 2009
+++  
/trunk/user/test/com/google/gwt/uibinder/sample/client/UiBinderTest.java        
 
Thu Sep 10 13:35:35 2009
@@ -265,10 +265,21 @@
      assertTrue(((HTML) north).getHTML().contains("Title area"));
    }

-  public void testPrivateStyle() {
+  public void testPrivateStyleFromExternalCss() {
      ParagraphElement p = widgetUi.privateStyleParagraph;
      assertTrue("Some kind of class should be set",  
p.getClassName().length() > 0);
    }
+
+  public void testPrivateStylesFromInlineCss() {
+    ParagraphElement p = widgetUi.reallyPrivateStyleParagraph;
+    assertTrue("Some kind of class should be set",
+        p.getClassName().length() > 0);
+    assertFalse("Should be a different style than privateStyleParagraph's",
+         
widgetUi.privateStyleParagraph.getClassName().equals(p.getClassName()));
+
+    assertTrue("Some kind of class should be set",
+        widgetUi.totallyPrivateStyleSpan.getClassName().length() > 0);
+  }

    @DoNotRunWith(Platform.Htmlunit)
    public void testRadioButton() {

--~--~---------~--~----~------------~-------~--~----~
http://groups.google.com/group/Google-Web-Toolkit-Contributors
-~----------~----~----~----~------~----~------~--~---

Reply via email to