Change-Id: I2a72e5001dbee4b6396344a0286ea4aa042e15aatags/7.4.0.beta1
import java.lang.reflect.Method; | import java.lang.reflect.Method; | ||||
import java.util.Collection; | import java.util.Collection; | ||||
import org.jsoup.nodes.Attributes; | |||||
import org.jsoup.nodes.Element; | import org.jsoup.nodes.Element; | ||||
import com.vaadin.event.Action; | import com.vaadin.event.Action; | ||||
import com.vaadin.shared.ui.button.ButtonServerRpc; | import com.vaadin.shared.ui.button.ButtonServerRpc; | ||||
import com.vaadin.shared.ui.button.ButtonState; | import com.vaadin.shared.ui.button.ButtonState; | ||||
import com.vaadin.ui.Component.Focusable; | import com.vaadin.ui.Component.Focusable; | ||||
import com.vaadin.ui.declarative.DesignAttributeHandler; | |||||
import com.vaadin.ui.declarative.DesignContext; | import com.vaadin.ui.declarative.DesignContext; | ||||
import com.vaadin.util.ReflectTools; | import com.vaadin.util.ReflectTools; | ||||
public void synchronizeFromDesign(Element design, | public void synchronizeFromDesign(Element design, | ||||
DesignContext designContext) { | DesignContext designContext) { | ||||
super.synchronizeFromDesign(design, designContext); | super.synchronizeFromDesign(design, designContext); | ||||
Button def = designContext.getDefaultInstance(this.getClass()); | |||||
Attributes attr = design.attributes(); | |||||
String content = design.html(); | String content = design.html(); | ||||
setCaption(content); | setCaption(content); | ||||
// tabindex | |||||
setTabIndex(DesignAttributeHandler.readAttribute("tabindex", attr, | |||||
def.getTabIndex(), Integer.class)); | |||||
// plain-text (default is html) | |||||
setHtmlContentAllowed(!DesignAttributeHandler.readAttribute( | |||||
"plain-text", attr, false, Boolean.class)); | |||||
setIconAlternateText(DesignAttributeHandler.readAttribute("icon-alt", | |||||
attr, def.getIconAlternateText(), String.class)); | |||||
// click-shortcut | |||||
removeClickShortcut(); | |||||
ShortcutAction action = DesignAttributeHandler.readAttribute( | |||||
"click-shortcut", attr, null, ShortcutAction.class); | |||||
if (action != null) { | |||||
setClickShortcut(action.getKeyCode(), action.getModifiers()); | |||||
} | |||||
} | } | ||||
/* | /* | ||||
@Override | @Override | ||||
protected Collection<String> getCustomAttributes() { | protected Collection<String> getCustomAttributes() { | ||||
Collection<String> result = super.getCustomAttributes(); | Collection<String> result = super.getCustomAttributes(); | ||||
result.add("tabindex"); | |||||
result.add("plain-text"); | |||||
result.add("caption"); | result.add("caption"); | ||||
result.add("icon-alt"); | |||||
result.add("click-shortcut"); | |||||
result.add("html-content-allowed"); | |||||
return result; | return result; | ||||
} | } | ||||
@Override | @Override | ||||
public void synchronizeToDesign(Element design, DesignContext designContext) { | public void synchronizeToDesign(Element design, DesignContext designContext) { | ||||
super.synchronizeToDesign(design, designContext); | super.synchronizeToDesign(design, designContext); | ||||
Attributes attr = design.attributes(); | |||||
Button def = designContext.getDefaultInstance(this.getClass()); | |||||
String content = getCaption(); | String content = getCaption(); | ||||
if (content != null) { | if (content != null) { | ||||
design.html(content); | design.html(content); | ||||
} | } | ||||
// tabindex | |||||
DesignAttributeHandler.writeAttribute("tabindex", attr, getTabIndex(), | |||||
def.getTabIndex(), Integer.class); | |||||
// plain-text (default is html) | |||||
if (!isHtmlContentAllowed()) { | |||||
design.attr("plain-text", ""); | |||||
} | |||||
// icon-alt | |||||
DesignAttributeHandler.writeAttribute("icon-alt", attr, | |||||
getIconAlternateText(), def.getIconAlternateText(), | |||||
String.class); | |||||
// click-shortcut | |||||
if (clickShortcut != null) { | |||||
DesignAttributeHandler.writeAttribute("click-shortcut", attr, | |||||
clickShortcut, null, ShortcutAction.class); | |||||
} | |||||
} | } | ||||
} | } |
import org.jsoup.nodes.Element; | import org.jsoup.nodes.Element; | ||||
import org.jsoup.nodes.Node; | import org.jsoup.nodes.Node; | ||||
import com.vaadin.event.ShortcutAction; | |||||
import com.vaadin.event.ShortcutAction.KeyCode; | |||||
import com.vaadin.event.ShortcutAction.ModifierKey; | |||||
import com.vaadin.server.ExternalResource; | import com.vaadin.server.ExternalResource; | ||||
import com.vaadin.server.FileResource; | import com.vaadin.server.FileResource; | ||||
import com.vaadin.server.FontAwesome; | import com.vaadin.server.FontAwesome; | ||||
* Formats the given design attribute value. The method is provided to | * Formats the given design attribute value. The method is provided to | ||||
* ensure consistent number formatting for design attribute values | * ensure consistent number formatting for design attribute values | ||||
* | * | ||||
* @since 7.4 | |||||
* @param number | * @param number | ||||
* the number to be formatted | * the number to be formatted | ||||
* @return the formatted number | * @return the formatted number | ||||
return getDecimalFormat().format(number); | return getDecimalFormat().format(number); | ||||
} | } | ||||
/** | |||||
* Convert ShortcutAction to attribute string presentation | |||||
* | |||||
* @param shortcut | |||||
* the shortcut action | |||||
* @return the action as attribute string presentation | |||||
*/ | |||||
private static String formatShortcutAction(ShortcutAction shortcut) { | |||||
StringBuilder sb = new StringBuilder(); | |||||
// handle modifiers | |||||
if (shortcut.getModifiers() != null) { | |||||
for (int modifier : shortcut.getModifiers()) { | |||||
sb.append(ShortcutKeyMapper.getStringForKeycode(modifier)) | |||||
.append("-"); | |||||
} | |||||
} | |||||
// handle keycode | |||||
sb.append(ShortcutKeyMapper.getStringForKeycode(shortcut.getKeyCode())); | |||||
return sb.toString(); | |||||
} | |||||
/** | |||||
* Reads shortcut action from attribute presentation | |||||
* | |||||
* @param attributeValue | |||||
* attribute presentation of shortcut action | |||||
* @return shortcut action with keycode and modifier keys from attribute | |||||
* value | |||||
*/ | |||||
private static ShortcutAction readShortcutAction(String attributeValue) { | |||||
if (attributeValue.length() == 0) { | |||||
return null; | |||||
} | |||||
String[] parts = attributeValue.split("-"); | |||||
// handle keycode | |||||
String keyCodePart = parts[parts.length - 1]; | |||||
int keyCode = ShortcutKeyMapper.getKeycodeForString(keyCodePart); | |||||
if (keyCode < 0) { | |||||
throw new IllegalArgumentException("Invalid shortcut definition " | |||||
+ attributeValue); | |||||
} | |||||
// handle modifiers | |||||
int[] modifiers = null; | |||||
if (parts.length > 1) { | |||||
modifiers = new int[parts.length - 1]; | |||||
} | |||||
for (int i = 0; i < parts.length - 1; i++) { | |||||
int modifier = ShortcutKeyMapper.getKeycodeForString(parts[i]); | |||||
if (modifier > 0) { | |||||
modifiers[i] = modifier; | |||||
} else { | |||||
throw new IllegalArgumentException( | |||||
"Invalid shortcut definition " + attributeValue); | |||||
} | |||||
} | |||||
return new ShortcutAction(null, keyCode, modifiers); | |||||
} | |||||
/** | /** | ||||
* Creates the decimal format used when writing attributes to the design | * Creates the decimal format used when writing attributes to the design | ||||
* | * | ||||
return Enum.valueOf((Class<? extends Enum>) targetType, | return Enum.valueOf((Class<? extends Enum>) targetType, | ||||
value.toUpperCase()); | value.toUpperCase()); | ||||
} | } | ||||
if (targetType == ShortcutAction.class) { | |||||
return readShortcutAction(value); | |||||
} | |||||
return null; | return null; | ||||
} | } | ||||
return formatFloat(((Float) value).floatValue()); | return formatFloat(((Float) value).floatValue()); | ||||
} else if (sourceType == Double.class || sourceType == Double.TYPE) { | } else if (sourceType == Double.class || sourceType == Double.TYPE) { | ||||
return formatDouble(((Double) value).doubleValue()); | return formatDouble(((Double) value).doubleValue()); | ||||
} else if (sourceType == ShortcutAction.class) { | |||||
return formatShortcutAction((ShortcutAction) value); | |||||
} else { | } else { | ||||
return value.toString(); | return value.toString(); | ||||
} | } | ||||
} | } | ||||
private static final List<Class<?>> supportedClasses = Arrays | private static final List<Class<?>> supportedClasses = Arrays | ||||
.asList(new Class<?>[] { String.class, Boolean.class, | .asList(new Class<?>[] { String.class, Boolean.class, | ||||
Integer.class, Byte.class, Short.class, Long.class, | Integer.class, Byte.class, Short.class, Long.class, | ||||
Character.class, Float.class, Double.class, Resource.class }); | |||||
Character.class, Float.class, Double.class, Resource.class, | |||||
ShortcutAction.class }); | |||||
/** | /** | ||||
* Returns true if the specified value type is supported by this class. | * Returns true if the specified value type is supported by this class. | ||||
} | } | ||||
} | } | ||||
/** | |||||
* Provides mappings between shortcut keycodes and their representation in | |||||
* design attributes | |||||
* | |||||
* @since 7.4 | |||||
* @author Vaadin Ltd | |||||
*/ | |||||
private static class ShortcutKeyMapper implements Serializable { | |||||
private static Map<Integer, String> keyCodeMap = Collections | |||||
.synchronizedMap(new HashMap<Integer, String>()); | |||||
private static Map<String, Integer> presentationMap = Collections | |||||
.synchronizedMap(new HashMap<String, Integer>()); | |||||
static { | |||||
// map modifiers | |||||
mapKey(ModifierKey.ALT, "alt"); | |||||
mapKey(ModifierKey.CTRL, "ctrl"); | |||||
mapKey(ModifierKey.META, "meta"); | |||||
mapKey(ModifierKey.SHIFT, "shift"); | |||||
// map keys | |||||
mapKey(KeyCode.ENTER, "enter"); | |||||
mapKey(KeyCode.ESCAPE, "escape"); | |||||
mapKey(KeyCode.PAGE_UP, "pageup"); | |||||
mapKey(KeyCode.PAGE_DOWN, "pagedown"); | |||||
mapKey(KeyCode.TAB, "tab"); | |||||
mapKey(KeyCode.ARROW_LEFT, "left"); | |||||
mapKey(KeyCode.ARROW_UP, "up"); | |||||
mapKey(KeyCode.ARROW_RIGHT, "right"); | |||||
mapKey(KeyCode.ARROW_DOWN, "down"); | |||||
mapKey(KeyCode.BACKSPACE, "backspace"); | |||||
mapKey(KeyCode.DELETE, "delete"); | |||||
mapKey(KeyCode.INSERT, "insert"); | |||||
mapKey(KeyCode.END, "end"); | |||||
mapKey(KeyCode.HOME, "home"); | |||||
mapKey(KeyCode.F1, "f1"); | |||||
mapKey(KeyCode.F2, "f2"); | |||||
mapKey(KeyCode.F3, "f3"); | |||||
mapKey(KeyCode.F4, "f4"); | |||||
mapKey(KeyCode.F5, "f5"); | |||||
mapKey(KeyCode.F6, "f6"); | |||||
mapKey(KeyCode.F7, "f7"); | |||||
mapKey(KeyCode.F8, "f8"); | |||||
mapKey(KeyCode.F9, "f9"); | |||||
mapKey(KeyCode.F10, "f10"); | |||||
mapKey(KeyCode.F11, "f11"); | |||||
mapKey(KeyCode.F12, "f12"); | |||||
mapKey(KeyCode.NUM0, "0"); | |||||
mapKey(KeyCode.NUM1, "1"); | |||||
mapKey(KeyCode.NUM2, "2"); | |||||
mapKey(KeyCode.NUM3, "3"); | |||||
mapKey(KeyCode.NUM4, "4"); | |||||
mapKey(KeyCode.NUM5, "5"); | |||||
mapKey(KeyCode.NUM6, "6"); | |||||
mapKey(KeyCode.NUM7, "7"); | |||||
mapKey(KeyCode.NUM8, "8"); | |||||
mapKey(KeyCode.NUM9, "9"); | |||||
mapKey(KeyCode.SPACEBAR, "spacebar"); | |||||
mapKey(KeyCode.A, "a"); | |||||
mapKey(KeyCode.B, "b"); | |||||
mapKey(KeyCode.C, "c"); | |||||
mapKey(KeyCode.D, "d"); | |||||
mapKey(KeyCode.E, "e"); | |||||
mapKey(KeyCode.F, "f"); | |||||
mapKey(KeyCode.G, "g"); | |||||
mapKey(KeyCode.H, "h"); | |||||
mapKey(KeyCode.I, "i"); | |||||
mapKey(KeyCode.J, "j"); | |||||
mapKey(KeyCode.K, "k"); | |||||
mapKey(KeyCode.L, "l"); | |||||
mapKey(KeyCode.M, "m"); | |||||
mapKey(KeyCode.N, "n"); | |||||
mapKey(KeyCode.O, "o"); | |||||
mapKey(KeyCode.P, "p"); | |||||
mapKey(KeyCode.Q, "q"); | |||||
mapKey(KeyCode.R, "r"); | |||||
mapKey(KeyCode.S, "s"); | |||||
mapKey(KeyCode.T, "t"); | |||||
mapKey(KeyCode.U, "u"); | |||||
mapKey(KeyCode.V, "v"); | |||||
mapKey(KeyCode.X, "x"); | |||||
mapKey(KeyCode.Y, "y"); | |||||
mapKey(KeyCode.Z, "z"); | |||||
} | |||||
private static void mapKey(int keyCode, String presentation) { | |||||
keyCodeMap.put(keyCode, presentation); | |||||
presentationMap.put(presentation, keyCode); | |||||
} | |||||
private static int getKeycodeForString(String attributePresentation) { | |||||
Integer code = presentationMap.get(attributePresentation); | |||||
return code != null ? code.intValue() : -1; | |||||
} | |||||
private static String getStringForKeycode(int keyCode) { | |||||
return keyCodeMap.get(keyCode); | |||||
} | |||||
} | |||||
} | } |
<v-password-field null-representation="" null-setting-allowed maxlength=10 columns=5 input-prompt="Please enter a value" text-change-event-mode="eager" text-change-timeout=2 value="foo" /> | <v-password-field null-representation="" null-setting-allowed maxlength=10 columns=5 input-prompt="Please enter a value" text-change-event-mode="eager" text-change-timeout=2 value="foo" /> | ||||
<!-- text area --> | <!-- text area --> | ||||
<v-text-area rows=5 wordwrap=false >test value</v-text-area> | <v-text-area rows=5 wordwrap=false >test value</v-text-area> | ||||
<!-- button --> | |||||
<v-button click-shortcut="ctrl-shift-o" disable-on-click tabindex=1 icon="http://vaadin.com/image.png" icon-alt="ok" plain-text>OK</v-button> | |||||
<!-- native button --> | |||||
<v-button click-shortcut="ctrl-shift-o" disable-on-click tabindex=1 icon="http://vaadin.com/image.png" icon-alt="ok" plain-text>OK</v-button> | |||||
<!-- tabsheet --> | <!-- tabsheet --> | ||||
<v-tab-sheet tabindex=5> | <v-tab-sheet tabindex=5> |
} | } | ||||
private AbstractComponent getComponent() { | private AbstractComponent getComponent() { | ||||
return new Button(); | |||||
Button button = new Button(); | |||||
button.setHtmlContentAllowed(true); | |||||
return button; | |||||
} | } | ||||
private AbstractComponent getPanel() { | private AbstractComponent getPanel() { |
*/ | */ | ||||
package com.vaadin.tests.server.component.button; | package com.vaadin.tests.server.component.button; | ||||
import java.lang.reflect.Field; | |||||
import junit.framework.TestCase; | import junit.framework.TestCase; | ||||
import org.jsoup.nodes.Attributes; | import org.jsoup.nodes.Attributes; | ||||
import org.jsoup.parser.Tag; | import org.jsoup.parser.Tag; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import com.vaadin.event.ShortcutAction.KeyCode; | |||||
import com.vaadin.event.ShortcutAction.ModifierKey; | |||||
import com.vaadin.ui.Button; | import com.vaadin.ui.Button; | ||||
import com.vaadin.ui.Button.ClickShortcut; | |||||
import com.vaadin.ui.NativeButton; | import com.vaadin.ui.NativeButton; | ||||
import com.vaadin.ui.declarative.DesignContext; | import com.vaadin.ui.declarative.DesignContext; | ||||
createAndTestButtons(null, "Click me"); | createAndTestButtons(null, "Click me"); | ||||
} | } | ||||
@Test | |||||
public void testAttributes() throws IllegalArgumentException, | |||||
SecurityException, IllegalAccessException, NoSuchFieldException { | |||||
Attributes attributes = new Attributes(); | |||||
attributes.put("tabindex", "3"); | |||||
attributes.put("plain-text", ""); | |||||
attributes.put("icon-alt", "OK"); | |||||
attributes.put("click-shortcut", "ctrl-shift-o"); | |||||
Button button = (Button) ctx | |||||
.createChild(createButtonWithAttributes(attributes)); | |||||
assertEquals(3, button.getTabIndex()); | |||||
assertEquals(false, button.isHtmlContentAllowed()); | |||||
assertEquals("OK", button.getIconAlternateText()); | |||||
Field field = Button.class.getDeclaredField("clickShortcut"); | |||||
field.setAccessible(true); | |||||
ClickShortcut value = (ClickShortcut) field.get(button); | |||||
assertEquals(KeyCode.O, value.getKeyCode()); | |||||
assertEquals(ModifierKey.CTRL, value.getModifiers()[0]); | |||||
assertEquals(ModifierKey.SHIFT, value.getModifiers()[1]); | |||||
} | |||||
/* | /* | ||||
* Test both Button and NativeButton. Caption should always be ignored. If | * Test both Button and NativeButton. Caption should always be ignored. If | ||||
* content is null, the created button should have empty content. | * content is null, the created button should have empty content. | ||||
} | } | ||||
} | } | ||||
private Element createButtonWithAttributes(Attributes attributes) { | |||||
return new Element(Tag.valueOf("v-button"), "", attributes); | |||||
} | |||||
private Element createElement(String elementName, String content, | private Element createElement(String elementName, String content, | ||||
String caption) { | String caption) { | ||||
Attributes attributes = new Attributes(); | Attributes attributes = new Attributes(); |
import junit.framework.TestCase; | import junit.framework.TestCase; | ||||
import org.jsoup.nodes.Attributes; | |||||
import org.jsoup.nodes.Element; | import org.jsoup.nodes.Element; | ||||
import org.jsoup.parser.Tag; | |||||
import org.junit.Test; | import org.junit.Test; | ||||
import com.vaadin.event.ShortcutAction.KeyCode; | |||||
import com.vaadin.event.ShortcutAction.ModifierKey; | |||||
import com.vaadin.ui.Button; | import com.vaadin.ui.Button; | ||||
import com.vaadin.ui.NativeButton; | import com.vaadin.ui.NativeButton; | ||||
import com.vaadin.ui.declarative.DesignContext; | import com.vaadin.ui.declarative.DesignContext; | ||||
createAndTestButtons("<b>Click</b>"); | createAndTestButtons("<b>Click</b>"); | ||||
} | } | ||||
@Test | |||||
public void testAttributes() { | |||||
Button button = new Button(); | |||||
button.setTabIndex(3); | |||||
button.setIconAlternateText("OK"); | |||||
button.setClickShortcut(KeyCode.O, ModifierKey.CTRL, ModifierKey.SHIFT); | |||||
Element e = new Element(Tag.valueOf("v-button"), "", new Attributes()); | |||||
button.synchronizeToDesign(e, ctx); | |||||
assertEquals("3", e.attr("tabindex")); | |||||
assertTrue("Button is plain text by default", e.hasAttr("plain-text")); | |||||
assertEquals("OK", e.attr("icon-alt")); | |||||
assertEquals("ctrl-shift-o", e.attr("click-shortcut")); | |||||
} | |||||
private void createAndTestButtons(String content) { | private void createAndTestButtons(String content) { | ||||
Button b1 = new Button(content); | Button b1 = new Button(content); | ||||
// we need to set this on, since the plain-text attribute will appear | |||||
// otherwise | |||||
b1.setHtmlContentAllowed(true); | |||||
Element e1 = ctx.createNode(b1); | Element e1 = ctx.createNode(b1); | ||||
assertEquals("Wrong tag name for button.", "v-button", e1.tagName()); | assertEquals("Wrong tag name for button.", "v-button", e1.tagName()); | ||||
assertEquals("Unexpected content in the v-button element.", content, | assertEquals("Unexpected content in the v-button element.", content, | ||||
assertTrue("The v-button element should not have attributes.", e1 | assertTrue("The v-button element should not have attributes.", e1 | ||||
.attributes().size() == 0); | .attributes().size() == 0); | ||||
NativeButton b2 = new NativeButton(content); | NativeButton b2 = new NativeButton(content); | ||||
b2.setHtmlContentAllowed(true); | |||||
Element e2 = ctx.createNode(b2); | Element e2 = ctx.createNode(b2); | ||||
assertEquals("Wrong tag name for button.", "v-native-button", | assertEquals("Wrong tag name for button.", "v-native-button", | ||||
e2.tagName()); | e2.tagName()); |