Change-Id: I2a72e5001dbee4b6396344a0286ea4aa042e15aatags/7.4.0.beta1
@@ -20,6 +20,7 @@ import java.io.Serializable; | |||
import java.lang.reflect.Method; | |||
import java.util.Collection; | |||
import org.jsoup.nodes.Attributes; | |||
import org.jsoup.nodes.Element; | |||
import com.vaadin.event.Action; | |||
@@ -38,6 +39,7 @@ import com.vaadin.shared.MouseEventDetails; | |||
import com.vaadin.shared.ui.button.ButtonServerRpc; | |||
import com.vaadin.shared.ui.button.ButtonState; | |||
import com.vaadin.ui.Component.Focusable; | |||
import com.vaadin.ui.declarative.DesignAttributeHandler; | |||
import com.vaadin.ui.declarative.DesignContext; | |||
import com.vaadin.util.ReflectTools; | |||
@@ -675,8 +677,25 @@ public class Button extends AbstractComponent implements | |||
public void synchronizeFromDesign(Element design, | |||
DesignContext designContext) { | |||
super.synchronizeFromDesign(design, designContext); | |||
Button def = designContext.getDefaultInstance(this.getClass()); | |||
Attributes attr = design.attributes(); | |||
String content = design.html(); | |||
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()); | |||
} | |||
} | |||
/* | |||
@@ -687,7 +706,12 @@ public class Button extends AbstractComponent implements | |||
@Override | |||
protected Collection<String> getCustomAttributes() { | |||
Collection<String> result = super.getCustomAttributes(); | |||
result.add("tabindex"); | |||
result.add("plain-text"); | |||
result.add("caption"); | |||
result.add("icon-alt"); | |||
result.add("click-shortcut"); | |||
result.add("html-content-allowed"); | |||
return result; | |||
} | |||
@@ -701,9 +725,27 @@ public class Button extends AbstractComponent implements | |||
@Override | |||
public void synchronizeToDesign(Element design, DesignContext designContext) { | |||
super.synchronizeToDesign(design, designContext); | |||
Attributes attr = design.attributes(); | |||
Button def = designContext.getDefaultInstance(this.getClass()); | |||
String content = getCaption(); | |||
if (content != null) { | |||
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); | |||
} | |||
} | |||
} |
@@ -40,6 +40,9 @@ import org.jsoup.nodes.Attributes; | |||
import org.jsoup.nodes.Element; | |||
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.FileResource; | |||
import com.vaadin.server.FontAwesome; | |||
@@ -316,7 +319,6 @@ public class DesignAttributeHandler implements Serializable { | |||
* Formats the given design attribute value. The method is provided to | |||
* ensure consistent number formatting for design attribute values | |||
* | |||
* @since 7.4 | |||
* @param number | |||
* the number to be formatted | |||
* @return the formatted number | |||
@@ -325,6 +327,64 @@ public class DesignAttributeHandler implements Serializable { | |||
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 | |||
* | |||
@@ -407,6 +467,9 @@ public class DesignAttributeHandler implements Serializable { | |||
return Enum.valueOf((Class<? extends Enum>) targetType, | |||
value.toUpperCase()); | |||
} | |||
if (targetType == ShortcutAction.class) { | |||
return readShortcutAction(value); | |||
} | |||
return null; | |||
} | |||
@@ -449,9 +512,10 @@ public class DesignAttributeHandler implements Serializable { | |||
return formatFloat(((Float) value).floatValue()); | |||
} else if (sourceType == Double.class || sourceType == Double.TYPE) { | |||
return formatDouble(((Double) value).doubleValue()); | |||
} else if (sourceType == ShortcutAction.class) { | |||
return formatShortcutAction((ShortcutAction) value); | |||
} else { | |||
return value.toString(); | |||
} | |||
} | |||
@@ -510,7 +574,8 @@ public class DesignAttributeHandler implements Serializable { | |||
private static final List<Class<?>> supportedClasses = Arrays | |||
.asList(new Class<?>[] { String.class, Boolean.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. | |||
@@ -562,4 +627,104 @@ public class DesignAttributeHandler implements Serializable { | |||
} | |||
} | |||
/** | |||
* 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); | |||
} | |||
} | |||
} |
@@ -46,6 +46,10 @@ | |||
<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 --> | |||
<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 --> | |||
<v-tab-sheet tabindex=5> |
@@ -246,7 +246,9 @@ public class TestSynchronizeToDesign extends TestCase { | |||
} | |||
private AbstractComponent getComponent() { | |||
return new Button(); | |||
Button button = new Button(); | |||
button.setHtmlContentAllowed(true); | |||
return button; | |||
} | |||
private AbstractComponent getPanel() { |
@@ -15,6 +15,8 @@ | |||
*/ | |||
package com.vaadin.tests.server.component.button; | |||
import java.lang.reflect.Field; | |||
import junit.framework.TestCase; | |||
import org.jsoup.nodes.Attributes; | |||
@@ -22,7 +24,10 @@ import org.jsoup.nodes.Element; | |||
import org.jsoup.parser.Tag; | |||
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.ClickShortcut; | |||
import com.vaadin.ui.NativeButton; | |||
import com.vaadin.ui.declarative.DesignContext; | |||
@@ -62,6 +67,27 @@ public class TestSynchronizeFromDesign extends TestCase { | |||
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 | |||
* content is null, the created button should have empty content. | |||
@@ -84,6 +110,10 @@ public class TestSynchronizeFromDesign extends TestCase { | |||
} | |||
} | |||
private Element createButtonWithAttributes(Attributes attributes) { | |||
return new Element(Tag.valueOf("v-button"), "", attributes); | |||
} | |||
private Element createElement(String elementName, String content, | |||
String caption) { | |||
Attributes attributes = new Attributes(); |
@@ -17,9 +17,13 @@ package com.vaadin.tests.server.component.button; | |||
import junit.framework.TestCase; | |||
import org.jsoup.nodes.Attributes; | |||
import org.jsoup.nodes.Element; | |||
import org.jsoup.parser.Tag; | |||
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.NativeButton; | |||
import com.vaadin.ui.declarative.DesignContext; | |||
@@ -48,8 +52,25 @@ public class TestSynchronizeToDesign extends TestCase { | |||
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) { | |||
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); | |||
assertEquals("Wrong tag name for button.", "v-button", e1.tagName()); | |||
assertEquals("Unexpected content in the v-button element.", content, | |||
@@ -57,6 +78,7 @@ public class TestSynchronizeToDesign extends TestCase { | |||
assertTrue("The v-button element should not have attributes.", e1 | |||
.attributes().size() == 0); | |||
NativeButton b2 = new NativeButton(content); | |||
b2.setHtmlContentAllowed(true); | |||
Element e2 = ctx.createNode(b2); | |||
assertEquals("Wrong tag name for button.", "v-native-button", | |||
e2.tagName()); |