Java tutorial
/* * Copyright (c) 2009-2014 JGoodies Software GmbH. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of JGoodies Software GmbH nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jgoodies.common.swing; import static com.jgoodies.common.base.Preconditions.checkNotNull; import static com.jgoodies.common.internal.Messages.MUST_NOT_BE_NULL; import java.awt.event.KeyEvent; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import javax.swing.AbstractButton; import javax.swing.Action; import javax.swing.JLabel; import com.jgoodies.common.base.Strings; /** * Configures the text, mnemonic and mnemonic index in Actions, JLabels, * and AbstractButtons. The term <em>marked text</em> is used for Strings * that contain a marker character that marks both the mnemonic and * its index in the resulting <em>plain text</em>.<p> * * To set a mnemonic, add an ampersand ('&') before the character * that should be the mnemonic, for example "&Save". * If your text has multiple ampersands, only the first one is used to mark * the mnemonic; the other ampersands appear as normal text. * For example "&Look&Feel" has the first 'L' has mnemonic. * If you want to use the ampersand itself in the text, use two consecutive * ampersands for any ampersand that precede the one that is used to mark * the mnemonic. For example the marked text "R&&D D&epartment" * has "R&D Department" as text and the mnemonic is 'E'.<p> * * Examples: * <table border="1"> * <tr> * <td><b>Marked Text</b></td> * <td><b>Plain Text</b></td> * <td><b>Mnemonic</b></td> * <td><b>Mnemonic Index</b></td> * <td><b>Comment</b></td> * </tr> * <tr> * <td>Save</td> * <td>Save</td> * <td>0</td> * <td>-1</td> * <td>No mnemonic</td> * </tr> * <tr> * <td>&Save</td> * <td>Save</td> * <td>'S'</td> * <td>0</td> * <td> </td> * </tr> * <tr> * <td>Save &as</td> * <td>Save as</td> * <td>'A'</td> * <td>5</td> * <td>Second 'a' marked</td> * </tr> * <tr> * <td>Look&Feel</td> * <td>LookFeel</td> * <td>'F'</td> * <td>4</td> * <td>& should be quoted</td> * </tr> * <tr> * <td>Look&&Feel</td> * <td>Look&Feel</td> * <td>0</td> * <td>-1</td> * <td>& is quoted</td> * </tr> * <tr> * <td>&Look&Feel</td> * <td>Look&Feel</td> * <td>L</td> * <td>0</td> * <td>Second & needs no quote</td> * </tr> * <tr> * <td>Look & Feel</td> * <td>Look & Feel</td> * <td>0</td> * <td>-1</td> * <td>Whitespace cannot be marked</td> * </tr> * <tr> * <td>R&&D D&epartment</td> * <td>R&D Department</td> * <td>'E'</td> * <td>5</td> * <td>First & is quoted</td> * </tr> * <tr> * <td><html>a&b</html></td> * <td><html>a<u>b</u></html></td> * <td>'B'</td> * <td>-1</td> * <td>'b' is underlined</td> * </tr> * <tr> * <td><html>R&amp;D D&ep.</html></td> * <td><html>R&amp;D D<u>e</u>p.</html></td> * <td>'B'</td> * <td>-1</td> * <td>HTML &amp; doesn't mark</td> * </tr> * </table> * * @author Karsten Lentzsch */ public final class MnemonicUtils { /** * The single mnemonic marker. Future versions of this class shall * support other markers, for example the underline char ('_'). */ static final char MNEMONIC_MARKER = '&'; // Instance Creation ****************************************************** private MnemonicUtils() { // Suppresses default constructor, prevents instantiation. } // Setting Text, Mnemonic and Index *************************************** /** * Configures the text, mnemonic and mnemonic index for {@code target} * using the given text that can be marked with the mnemonic marker '&'. * For example if {@code markedText} is "Save &as", the text * will be set to "Save as", the mnemonic is 'a', and the * mnemonic index is 5. * * @param target the button to be configured * @param markedText the text with optional mnemonic marker * @throws NullPointerException if {@code target} is {@code null} */ public static void configure(AbstractButton target, String markedText) { checkNotNull(target, MUST_NOT_BE_NULL, "target"); configure0(target, new MnemonicText(markedText, MNEMONIC_MARKER)); } /** * Configures the text, mnemonic and mnemonic index for {@code target} * using the given text that can be marked with the mnemonic marker '&'. * For example if {@code markedText} is "Save &as", the text * will be set to "Save as", the mnemonic is 'a', and the * mnemonic index is 5. * * @param target the Action to be configured * @param markedText the text with optional mnemonic marker * @throws NullPointerException if {@code target} is {@code null} */ public static void configure(Action target, String markedText) { checkNotNull(target, MUST_NOT_BE_NULL, "target"); configure0(target, new MnemonicText(markedText, MNEMONIC_MARKER)); } /** * Configures the text, mnemonic and mnemonic index for {@code target} * using the given text that can be marked with the mnemonic marker '&'. * For example if {@code markedText} is "Save &as", the text * will be set to "Save as", the mnemonic is 'a', and the * mnemonic index is 5. * * @param target the label to be configured * @param markedText the text with optional mnemonic marker * @throws NullPointerException if {@code target} is {@code null} */ public static void configure(JLabel target, String markedText) { checkNotNull(target, MUST_NOT_BE_NULL, "target"); configure0(target, new MnemonicText(markedText, MNEMONIC_MARKER)); } // Misc ******************************************************************* /** * Returns the plain text for the given marked text by removing * the mnemonic marker and marker quotes - if any. If the marked text * is HTML, the plain text has the mnemonic character underlined. * * See the {@link MnemonicUtils} class comment for information * about how to mark a mnemonic and how to quote a marker. * * <pre> * MnemonicUtils.plainText("Save") == "Save" * MnemonicUtils.plainText("&Save") == "Save" * MnemonicUtils.plainText("&Look&Feel") == "Look&Feel" * MnemonicUtils.plainText("Look & Feel") == "Look & Feel" * MnemonicUtils.plainText("R&&D D&epartment") == "R&D Department" * MnemonicUtils.plainText("<html>a&b</html>") == "<html>a<u>b</u></html>" * </pre> * * @param markedText the text that may contain a mnemonic marker * @return the text without mnemonic marker and marker quotes */ public static String plainText(String markedText) { return new MnemonicText(markedText, MNEMONIC_MARKER).text; } // Testing API ************************************************************ /** * Extracts and returns the mnemonic from the given marked text. * * @param markedText the text with optional mnemonic marker * @return the mnemonic or {@code 0} if no mnemonic is marked */ static int mnemonic(String markedText) { return new MnemonicText(markedText, MNEMONIC_MARKER).key; } /** * Finds and returns the index of the mnemonic in the marked text. * * @param markedText the text with optional mnemonic marker * @return the mnemonic index or {@code -1} if there's no mnemonic * or the text is HTML */ static int mnemonicIndex(String markedText) { return new MnemonicText(markedText, MNEMONIC_MARKER).index; } // Implementation ********************************************************* private static void configure0(AbstractButton button, MnemonicText mnemonicText) { button.setText(mnemonicText.text); button.setMnemonic(mnemonicText.key); button.setDisplayedMnemonicIndex(mnemonicText.index); } /* * We set the mnemonic only if non-null. This works around a bug in * AbstractButton that can configure itself from an Action with mnemonic * set to null but throws an NPE if the Action's mnemonic becomes * null afterwards. * * @see AbstractButton.ButtonActionPropertyChangeListener */ private static void configure0(Action action, MnemonicText mnemonicText) { Integer keyValue = Integer.valueOf(mnemonicText.key); Integer indexValue = mnemonicText.index == -1 ? null : Integer.valueOf(mnemonicText.index); action.putValue(Action.NAME, mnemonicText.text); action.putValue(Action.MNEMONIC_KEY, keyValue); action.putValue(Action.DISPLAYED_MNEMONIC_INDEX_KEY, indexValue); } private static void configure0(JLabel label, MnemonicText mnemonicText) { label.setText(mnemonicText.text); label.setDisplayedMnemonic(mnemonicText.key); label.setDisplayedMnemonicIndex(mnemonicText.index); } // Helper Code ************************************************************ private static final class MnemonicText { String text; int key; int index; private MnemonicText(String markedText, char marker) { int i; if (markedText == null || markedText.length() <= 1 || (i = markedText.indexOf(marker)) == -1) { text = markedText; key = KeyEvent.VK_UNDEFINED; index = -1; return; } boolean html = Strings.startsWithIgnoreCase(markedText, "<html>"); StringBuilder builder = new StringBuilder(); int begin = 0; int quotedMarkers = 0; int markerIndex = -1; boolean marked = false; char markedChar = 0; CharacterIterator sci = new StringCharacterIterator(markedText); do { builder.append(markedText.substring(begin, i)); char current = sci.setIndex(i); char next = sci.next(); if (html) { int entityEnd = indexOfEntityEnd(markedText, i); if (entityEnd == -1) { marked = true; builder.append("<u>").append(next).append("</u>"); begin = i + 2; markedChar = next; } else { builder.append(markedText.substring(i, entityEnd)); begin = entityEnd; } } else { if (next == marker) { // "Look&&Feel" builder.append(next); begin = i + 2; quotedMarkers++; } else if (Character.isWhitespace(next)) { // "Look & Feel" builder.append(current).append(next); begin = i + 2; } else if (next == CharacterIterator.DONE) { builder.append(current); begin = i + 2; } else { builder.append(next); begin = i + 2; markerIndex = i - quotedMarkers; marked = true; markedChar = next; } } i = markedText.indexOf(marker, begin); } while (i != -1 && !marked); if (begin < markedText.length()) { builder.append(markedText.substring(begin)); } text = builder.toString(); index = markerIndex; if (marked) { key = mnemonicKey(markedChar); } else { key = KeyEvent.VK_UNDEFINED; } } private static int indexOfEntityEnd(String htmlText, int start) { CharacterIterator sci = new StringCharacterIterator(htmlText, start); char c; do { c = sci.next(); if (c == ';') { return sci.getIndex(); } if (!Character.isLetterOrDigit(c)) { return -1; } } while (c != CharacterIterator.DONE); 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; } } }