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 -~----------~----~----~----~------~----~------~--~---
