/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. * * Contributors: Maxym Mykhalchuk */ package org.openide.awt; import java.lang.reflect.Method; import java.text.MessageFormat; import java.util.MissingResourceException; import java.util.ResourceBundle; import javax.swing.AbstractButton; import javax.swing.JLabel; /** * Support class for setting button, menu, and label text strings with mnemonics. * @author Maxym Mykhalchuk * @since 3.37 * @see "#26640" */ public final class Mnemonics extends Object { /** Private constructor in order that this class is never instantiated. */ private Mnemonics() {} /** * Actual setter of the text & mnemonics for the AbstractButton/JLabel or * their subclasses. * @param item AbstractButton/JLabel * @param text new label */ private static void setLocalizedText2(Object item, String text) { // #17664. Handle null text also. if(text == null) { setText(item, null); return; } int i = findMnemonicAmpersand(text); if (i < 0) { // no '&' - don't set the mnemonic setText(item, text); setMnemonic(item, 0); } else { setText(item, text.substring(0, i) + text.substring(i + 1)); char ch = text.charAt(i + 1); if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9')) { // it's latin character or arabic digit, // setting it as mnemonics setMnemonic(item, ch); // If it's something like "Save &As", we need to set another // mnemonic index (at least under 1.4 or later) // see #29676 setMnemonicIndex(item, i, ch); } else { // it's non-latin, getting the latin correspondance int latinCode = getLatinKeycode(ch); setMnemonic(item, latinCode); setMnemonicIndex(item, i, latinCode); } } } /** * Sets the text for the menu item or other subclass of AbstractButton. * <p> * Examples: * <table cellspacing=2 cellpadding=3 border=1> * <tr><th>Input String <th>View under JDK1.3 <th>View under JDK1.4 or later * <tr><td>"Save &As" <td>S<u>a</u>ve As <td>Save <u>A</u>s * <tr><td>"Rock & Roll" <td>Rock & Roll <td>Rock & Roll * <tr><td>"Drag & &Drop" <td><u>D</u>rag & Drop <td>Drag & <u>D</u>rop * <tr><td>"&Файл" <td>Файл (<u>F</u>) <td><u>Ф</u>айл * </table> * @param item a button whose text will be changed * @param text new label */ public static void setLocalizedText(AbstractButton item, String text) { setLocalizedText2(item, text); } /** * Sets the text for the label or other subclass of JLabel. * For details see {@link #setLocalizedText(AbstractButton, String)}. * @param item a label whose text will be set * @param text new label */ public static void setLocalizedText(JLabel item, String text) { setLocalizedText2(item, text); } /** * Searches for an ampersand in a string which indicates a mnemonic. * Recognizes the following cases: * <ul> * <li>"Drag & Drop", "Ampersand ('&')" - don't have mnemonic ampersand. * "&" is not found before " " (space), or if enclosed in "'" * (single quotation marks). * <li>"&File", "Save &As..." - do have mnemonic ampersand. * <li>"Rock & Ro&ll", "Underline the '&' &character" - also do have * mnemonic ampersand, but the second one. * </ul> * @param text text to search * @return the position of mnemonic ampersand in text, or -1 if there is none */ public static int findMnemonicAmpersand(String text) { int i = -1; do { // searching for the next ampersand i = text.indexOf('&', i + 1); if (i >= 0 && (i + 1) < text.length()) { // before ' ' if (text.charAt(i + 1)==' ') { continue; // before ', and after ' } else if (text.charAt(i + 1) == '\'' && i > 0 && text.charAt(i - 1) == '\'') { continue; } // ampersand is marking mnemonics return i; } } while (i >= 0); return -1; } /** * Gets the Latin symbol which corresponds * to some non-Latin symbol on the localized keyboard. * The search is done via lookup of Resource bundle * for pairs having the form (e.g.) <code>MNEMONIC_\u0424=A</code>. * @param localeChar non-Latin character or a punctuator to be used as mnemonic * @return character on latin keyboard, corresponding to the locale character, * or the appropriate VK_*** code (if there's no latin character * "under" the non-Latin one */ private static int getLatinKeycode(char localeChar) { try { // associated should be a latin character, arabic digit // or an integer (KeyEvent.VK_***) String str=getBundle().getString("MNEMONIC_" + localeChar); // NOI18N if( str.length()==1 ) return str.charAt(0); else return Integer.parseInt(str); } catch (MissingResourceException x) { // correspondence not found, it IS an error, // but we eat it, and return the character itself x.printStackTrace(); return localeChar; } } /** storage for JDK >= 1.4 knowledge */ private static boolean isJDK14orLaterCache; /** did we already cached the knowledge about JDK specification version */ private static boolean weKnowJDK; /** * Tests whether we're running on JDK1.4 (or later). * The function caches its result. * @return true if we're running on JDK1.4 or later */ private static boolean isJDK14orLater() { if(weKnowJDK) return isJDK14orLaterCache; String spec = System.getProperty("java.specification.version"); // NOI18N if(spec == null) { // under MS JVM System.getProperty("java.specification.version") // returns null weKnowJDK = true; isJDK14orLaterCache = false; } else { int major=Integer.parseInt(spec.substring(0, spec.indexOf('.'))); int minor=Integer.parseInt(spec.substring(spec.indexOf('.') + 1)); weKnowJDK = true; isJDK14orLaterCache = major > 1 || minor >= 4; } return isJDK14orLaterCache; } /** * Wrapper for the * <code>AbstractButton.setMnemonicIndex</code> or * <code>JLabel.setDisplayedMnemonicIndex</code> method. * <li>Under JDK1.4 calls the method on item * <li>Under JDK1.3 adds " (<latin character>)" (if needed) * to label and sets the latin character as mnemonics. * @param item AbstractButton/JLabel or subclasses * @param index Index of the Character to underline under JDK1.4 * @param latinCode Latin Character Keycode to underline under JDK1.3 */ private static void setMnemonicIndex (Object item, int index, int latinCode) { if (isJDK14orLater()) { try { Method sdmi = item.getClass().getMethod("setDisplayedMnemonicIndex", new Class[] {int.class}); // NOI18N sdmi.invoke(item, new Object[] {new Integer(index)}); } catch (Exception x) { x.printStackTrace(); isJDK14orLaterCache = false; setMnemonicIndex(item, index, latinCode); } } else { // under JDK 1.3 or earlier String text = getText(item); if (text.indexOf(latinCode) == -1) { // if it's not "Save &As" setText(item, MessageFormat.format(getBundle().getString("FORMAT_MNEMONICS"), // NOI18N new Object[] {text, new Character((char)latinCode)})); } setMnemonic(item, latinCode); } } /** * Wrapper for AbstractButton/JLabel.setText * @param item AbstractButton/JLabel * @param text the text to set */ private static void setText(Object item, String text) { if (item instanceof AbstractButton) { ((AbstractButton)item).setText(text); } else { ((JLabel)item).setText(text); } } /** * Wrapper for AbstractButton/JLabel.getText * @param item AbstractButton/JLabel * @return the text of a component */ private static String getText(Object item) { if (item instanceof AbstractButton) { return ((AbstractButton)item).getText(); } else { return ((JLabel)item).getText(); } } /** * Wrapper for AbstractButton.setMnemonic and JLabel.setDisplayedMnemonic * @param item AbstractButton/JLabel * @param mnem Mnemonic char to set, latin [a-z,A-Z], digit [0-9], or any VK_ code */ private static void setMnemonic(Object item, int mnem) { if(mnem>='a' && mnem<='z') mnem=mnem+('A'-'a'); if (item instanceof AbstractButton) { ((AbstractButton)item).setMnemonic(mnem); } else { ((JLabel)item).setDisplayedMnemonic(mnem); } } /** * Getter for the used Resource bundle (org.openide.awt.Mnemonics). * Used to avoid calling </code>ResourceBundle.getBundle(...)</code> * many times in defferent places of the code. * Does no caching, it's simply an utility method. */ private static ResourceBundle getBundle() { return ResourceBundle.getBundle("org.openide.awt.Mnemonics"); // NOI18N } }