/* * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. * Copyright (C) 2011 Nicolas Peransin. All rights reserved. * Use is subject to license terms. */ package org.mypsycho.swing.app.reflect; import java.awt.MenuItem; import java.awt.MenuShortcut; import java.awt.event.KeyEvent; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import javax.swing.AbstractButton; import javax.swing.JLabel; import org.mypsycho.beans.DescriptorExtension; /** * An internal helper class that configures the text and mnemonic * properties for instances of AbstractButton, JLabel, and * javax.swing.Action. It's used like this: * * <pre> * MnemonicText.configure(myButton, "Save &As") * </pre> * * The configure method unconditionally sets three properties on the * target object: * <ul> * <li>the label text, "Save As" * <li>the mnemonic key code, VK_A * <li>the index of the mnemonic character, 5 * </ul> * If the mnemonic marker character isn't present, then the second * two properties are cleared to VK_UNDEFINED (0) and -1 respectively. * <p> */ public abstract class MnemonicProperty<T> extends DescriptorExtension { public static class Menu extends MnemonicProperty<MenuItem> { public Menu() throws IntrospectionException { super(MenuItem.class, new PropertyDescriptor("label", MenuItem.class)); } @Override public Object get(Object bean) { return ((javax.swing.Action) bean).getValue(javax.swing.Action.NAME); } @Override void configure(MenuItem target, String text, int key, int index) { target.setLabel(text); if (key != KeyEvent.VK_UNDEFINED) { target.setShortcut(new MenuShortcut(key)); } } } public static class Action extends MnemonicProperty<javax.swing.Action> { public Action() throws IntrospectionException { super(javax.swing.Action.class, false); } @Override public Object get(Object bean) { return ((javax.swing.Action) bean).getValue(javax.swing.Action.NAME); } @Override void configure(javax.swing.Action target, String text, int key, int index) { target.putValue(javax.swing.Action.NAME, text); if (key != KeyEvent.VK_UNDEFINED) { target.putValue(javax.swing.Action.MNEMONIC_KEY, key); } if (index != -1) { target.putValue(javax.swing.Action.DISPLAYED_MNEMONIC_INDEX_KEY, index); } } } public static class Button extends MnemonicProperty<AbstractButton> { // include all menuItem public Button() throws IntrospectionException { super(AbstractButton.class, true); } @Override public Object get(Object bean) { return ((AbstractButton) bean).getText(); } @Override void configure(AbstractButton target, String text, int key, int index) { target.setText(text); if (key != KeyEvent.VK_UNDEFINED) { target.setMnemonic(key); } if (index != -1) { target.setDisplayedMnemonicIndex(index); } } } public static class Label extends MnemonicProperty<JLabel> { public Label() throws IntrospectionException { super(JLabel.class, true); } @Override public Object get(Object bean) { return ((JLabel) bean).getText(); } @Override void configure(JLabel target, String text, int key, int index) { target.setText(text); if (key != KeyEvent.VK_UNDEFINED) { target.setDisplayedMnemonic(key); } if (index != -1) { target.setDisplayedMnemonicIndex(index); } } } protected MnemonicProperty(Class<T> type, PropertyDescriptor override) throws IntrospectionException { super(type, "text", override); } /** * */ protected MnemonicProperty(Class<T> type, boolean override) throws IntrospectionException { super(type, "text", override); } @Override public Class<?> getPropertyType(boolean collection) { return !collection ? String.class : null; } /* * (non-Javadoc) * * @see com.psycho.beans.DescriptorExtension#isReadable(java.lang.Object, boolean) */ @Override public boolean isReadable(Object bean, boolean collection) { return !collection; } /* * (non-Javadoc) * * @see com.psycho.beans.DescriptorExtension#isWriteable(java.lang.Object, boolean) */ @Override public boolean isWriteable(Object bean, boolean collection) { return !collection; } abstract void configure(T target, String text, int key, int index); /* * (non-Javadoc) * * @see com.psycho.beans.DescriptorExtension#get(java.lang.Object) */ @Override public Object get(Object bean) throws NoSuchMethodException { // Implicit NullPointerExecption is expected throw new NoSuchMethodException(getClass().getName() + " is not applicable to " + bean.getClass()); } /* * (non-Javadoc) * * @see com.psycho.beans.DescriptorExtension#set(java.lang.Object, java.lang.Object) */ @Override public void set(Object bean, Object value) throws InvocationTargetException { String markedText = (String) value; String text = markedText; int mnemonicIndex = -1; int mnemonicKey = KeyEvent.VK_UNDEFINED; // TBD: mnemonic marker char should be an application resource int markerIndex = mnemonicMarkerIndex(markedText, '&'); if (markerIndex == -1) { markerIndex = mnemonicMarkerIndex(markedText, '_'); } if (markerIndex != -1) { text = text.substring(0, markerIndex) + text.substring(markerIndex + 1); mnemonicIndex = markerIndex; CharacterIterator sci = new StringCharacterIterator(markedText, markerIndex); mnemonicKey = mnemonicKey(sci.next()); } @SuppressWarnings("unchecked") T bean2 = (T) bean; configure(bean2, text, mnemonicKey, mnemonicIndex); } private static int mnemonicMarkerIndex(String s, char marker) { if ((s == null) || (s.length() < 2)) { return -1; } CharacterIterator sci = new StringCharacterIterator(s); int i = 0; while (i != -1) { i = s.indexOf(marker, i); if (i != -1) { sci.setIndex(i); char c1 = sci.previous(); sci.setIndex(i); char c2 = sci.next(); boolean isQuote = (c1 == '\'') && (c2 == '\''); boolean isSpace = Character.isWhitespace(c2); if (!isQuote && !isSpace && (c2 != CharacterIterator.DONE)) { return i; } } if (i != -1) { i += 1; } } return -1; } /* * A general purpose way to map from a char to a KeyCode is needed. An * AWT RFE has been filed: * http://bt2ws.central.sun.com/CrPrint?id=6559449 * CR 6559449 java/classes_awt Support for converting from char to KeyEvent VK_ keycode */ private static int mnemonicKey(char c) { int vk = c; if ((vk >= 'a') && (vk <= 'z')) { vk -= ('a' - 'A'); } return vk; } }