com.jgoodies.common.swing.MnemonicUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jgoodies-common Show documentation
Show all versions of jgoodies-common Show documentation
The JGoodies Common library provides convenience code
for other JGoodies libraries and applications.
/*
* Copyright (c) 2009-2012 JGoodies Karsten Lentzsch. 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 Karsten Lentzsch 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 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 marked text is used for Strings
* that contain a marker character that marks both the mnemonic and
* its index in the resulting plain text.
*
* 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'.
*
* Examples:
*
*
* Marked Text
* Plain Text
* Mnemonic
* Mnemonic Index
* Comment
*
*
* Save
* Save
* 0
* -1
* No mnemonic
*
*
* &Save
* Save
* 'S'
* 0
*
*
*
* Save &as
* Save as
* 'A'
* 5
* Second 'a' marked
*
*
* Look&Feel
* LookFeel
* 'F'
* 4
* & should be quoted
*
*
* Look&&Feel
* Look&Feel
* 0
* -1
* & is quoted
*
*
* &Look&Feel
* Look&Feel
* L
* 0
* Second & needs no quote
*
*
* Look & Feel
* Look & Feel
* 0
* -1
* Whitespace cannot be marked
*
*
* R&&D D&epartment
* R&D Department
* 'E'
* 5
* First & is quoted
*
*
* <html>a&b</html>
* <html>a<u>b</u></html>
* 'B'
* -1
* 'b' is underlined
*
*
* <html>R&D D&ep.</html>
* <html>R&D D<u>e</u>p.</html>
* 'B'
* -1
* HTML & doesn't mark
*
*
*
* @author Karsten Lentzsch
* @version $Revision: 1.1 $
*/
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, "target must not be null.");
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, "target must not be null.");
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, "target must not be null.");
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.
*
*
* 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>"
*
*
* @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, "");
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("").append(next).append("");
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;
}
}
}