001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: *
041: * Contributors: Maxym Mykhalchuk
042: */
043: package org.openide.awt;
044:
045: import org.openide.util.Utilities;
046: import java.util.MissingResourceException;
047: import java.util.ResourceBundle;
048: import java.util.logging.Logger;
049: import javax.swing.AbstractButton;
050: import javax.swing.JLabel;
051:
052: /**
053: * Support class for setting button, menu, and label text strings with mnemonics.
054: * @author Maxym Mykhalchuk
055: * @since 3.37
056: * @see <a href="http://www.netbeans.org/issues/show_bug.cgi?id=26640">Issue #26640</a>
057: */
058: public final class Mnemonics extends Object {
059: /** Private constructor in order that this class is never instantiated. */
060: private Mnemonics() {
061: }
062:
063: /**
064: * Actual setter of the text & mnemonics for the AbstractButton/JLabel or
065: * their subclasses.
066: * @param item AbstractButton/JLabel
067: * @param text new label
068: */
069: private static void setLocalizedText2(Object item, String text) {
070: // #17664. Handle null text also.
071: // & in HTML should be ignored
072: if (text == null) { // NOI18N
073: setText(item, null);
074:
075: return;
076: }
077:
078: int i = findMnemonicAmpersand(text);
079:
080: if (i < 0) {
081: // no '&' - don't set the mnemonic
082: setText(item, text);
083: setMnemonic(item, 0);
084: } else {
085: setText(item, text.substring(0, i) + text.substring(i + 1));
086: //#67807 no mnemonics on macosx
087: if (Utilities.isMac()) {
088: setMnemonic(item, 0);
089: } else {
090: char ch = text.charAt(i + 1);
091: if (text.startsWith("<html>")) { // NOI18N
092: // Workaround for JDK bug #6510775
093: setText(item, text.substring(0, i) + "<u>" + ch
094: + "</u>" + text.substring(i + 2)); // NOI18N
095: i += 3; // just in case it gets fixed
096: }
097: if (((ch >= 'A') && (ch <= 'Z'))
098: || ((ch >= 'a') && (ch <= 'z'))
099: || ((ch >= '0') && (ch <= '9'))) {
100: // it's latin character or arabic digit,
101: // setting it as mnemonics
102: setMnemonic(item, ch);
103:
104: // If it's something like "Save &As", we need to set another
105: // mnemonic index (at least under 1.4 or later)
106: // see #29676
107: setMnemonicIndex(item, i);
108: } else {
109: // it's non-latin, getting the latin correspondance
110: try {
111: int latinCode = getLatinKeycode(ch);
112: setMnemonic(item, latinCode);
113: setMnemonicIndex(item, i);
114: } catch (MissingResourceException e) {
115: Logger
116: .getLogger(Mnemonics.class.getName())
117: .info(
118: "Mapping from a non-Latin character '"
119: + ch
120: + "' not found in a localized (branded) version of "
121: + "openide/awt/src/org/openide/awt/Mnemonics.properties - "
122: + "mnemonic cannot be assigned in "
123: + text);
124: }
125: }
126: }
127: }
128: }
129:
130: /**
131: * Sets the text for a menu item or other subclass of AbstractButton.
132: * <p>Examples:</p>
133: * <table cellspacing="2" cellpadding="3" border="1">
134: * <tr><th>Input String</th> <th>View under JDK 1.4 or later</th></tr>
135: * <tr><td><code>Save &As<code></td> <td>Save <u>A</u>s</td></tr>
136: * <tr><td><code>Rock & Roll<code></td> <td>Rock & Roll</td></tr>
137: * <tr><td><code>Drag & &Drop<code></td> <td>Drag & <u>D</u>rop</td></tr>
138: * <tr><td><code>&Файл</code></td> <td><u>Ф</u>айл</td></tr>
139: * </table>
140: * @param item a button whose text will be changed
141: * @param text new label
142: */
143: public static void setLocalizedText(AbstractButton item, String text) {
144: setLocalizedText2(item, text);
145: }
146:
147: /**
148: * Sets the text for the label or other subclass of JLabel.
149: * For details see {@link #setLocalizedText(AbstractButton, String)}.
150: * @param item a label whose text will be set
151: * @param text new label
152: */
153: public static void setLocalizedText(JLabel item, String text) {
154: setLocalizedText2(item, text);
155: }
156:
157: /**
158: * Searches for an ampersand in a string which indicates a mnemonic.
159: * Recognizes the following cases:
160: * <ul>
161: * <li>"Drag & Drop", "Ampersand ('&')" - don't have mnemonic ampersand.
162: * "&" is not found before " " (space), or if enclosed in "'"
163: * (single quotation marks).
164: * <li>"&File", "Save &As..." - do have mnemonic ampersand.
165: * <li>"Rock & Ro&ll", "Underline the '&' &character" - also do have
166: * mnemonic ampersand, but the second one.
167: * <li>"<html><b>R&amp;D</b> departmen&t" - has mnemonic
168: * ampersand before "t".
169: * Ampersands in HTML texts that are part of entity are ignored.
170: * </ul>
171: * @param text text to search
172: * @return the position of mnemonic ampersand in text, or -1 if there is none
173: */
174: public static int findMnemonicAmpersand(String text) {
175: int i = -1;
176: boolean isHTML = text.startsWith("<html>");
177:
178: do {
179: // searching for the next ampersand
180: i = text.indexOf('&', i + 1);
181:
182: if ((i >= 0) && ((i + 1) < text.length())) {
183: if (isHTML) {
184: boolean startsEntity = false;
185: for (int j = i + 1; j < text.length(); j++) {
186: char c = text.charAt(j);
187: if (c == ';') {
188: startsEntity = true;
189: break;
190: }
191: if (!Character.isLetterOrDigit(c)) {
192: break;
193: }
194: }
195: if (!startsEntity) {
196: return i;
197: }
198: } else {
199: // before ' '
200: if (text.charAt(i + 1) == ' ') {
201: continue;
202:
203: // before ', and after '
204: } else if ((text.charAt(i + 1) == '\'') && (i > 0)
205: && (text.charAt(i - 1) == '\'')) {
206: continue;
207: }
208:
209: // ampersand is marking mnemonics
210: return i;
211: }
212: }
213: } while (i >= 0);
214:
215: return -1;
216: }
217:
218: /**
219: * Gets the Latin symbol which corresponds
220: * to some non-Latin symbol on the localized keyboard.
221: * The search is done via lookup of Resource bundle
222: * for pairs having the form (e.g.) <code>MNEMONIC_\u0424=A</code>.
223: * @param localeChar non-Latin character or a punctuator to be used as mnemonic
224: * @return character on latin keyboard, corresponding to the locale character,
225: * or the appropriate VK_*** code (if there's no latin character
226: * "under" the non-Latin one
227: */
228: private static int getLatinKeycode(char localeChar)
229: throws MissingResourceException {
230: // associated should be a latin character, arabic digit
231: // or an integer (KeyEvent.VK_***)
232: String str = getBundle().getString("MNEMONIC_" + localeChar); // NOI18N
233:
234: if (str.length() == 1) {
235: return str.charAt(0);
236: } else {
237: return Integer.parseInt(str);
238: }
239: }
240:
241: /**
242: * Wrapper for the
243: * <code>AbstractButton.setMnemonicIndex</code> or
244: * <code>JLabel.setDisplayedMnemonicIndex</code> method.
245: * @param item AbstractButton/JLabel or subclasses
246: * @param index Index of the Character to underline under JDK1.4
247: * @param latinCode Latin Character Keycode to underline under JDK1.3
248: */
249: private static void setMnemonicIndex(Object item, int index) {
250: if (item instanceof AbstractButton) {
251: ((AbstractButton) item).setDisplayedMnemonicIndex(index);
252: } else if (item instanceof JLabel) {
253: ((JLabel) item).setDisplayedMnemonicIndex(index);
254: }
255: }
256:
257: /**
258: * Wrapper for AbstractButton/JLabel.setText
259: * @param item AbstractButton/JLabel
260: * @param text the text to set
261: */
262: private static void setText(Object item, String text) {
263: if (item instanceof AbstractButton) {
264: ((AbstractButton) item).setText(text);
265: } else {
266: ((JLabel) item).setText(text);
267: }
268: }
269:
270: /**
271: * Wrapper for AbstractButton.setMnemonic and JLabel.setDisplayedMnemonic
272: * @param item AbstractButton/JLabel
273: * @param mnem Mnemonic char to set, latin [a-z,A-Z], digit [0-9], or any VK_ code
274: */
275: private static void setMnemonic(Object item, int mnem) {
276: if (Utilities.isMac()) {
277: // there shall be no mnemonics on macosx.
278: //#55864
279: return;
280: }
281:
282: if ((mnem >= 'a') && (mnem <= 'z')) {
283: mnem = mnem + ('A' - 'a');
284: }
285:
286: if (item instanceof AbstractButton) {
287: ((AbstractButton) item).setMnemonic(mnem);
288: } else {
289: ((JLabel) item).setDisplayedMnemonic(mnem);
290: }
291: }
292:
293: /**
294: * Getter for the used Resource bundle (org.openide.awt.Mnemonics).
295: * Used to avoid calling </code>ResourceBundle.getBundle(...)</code>
296: * many times in defferent places of the code.
297: * Does no caching, it's simply an utility method.
298: */
299: private static ResourceBundle getBundle() {
300: return ResourceBundle.getBundle("org.openide.awt.Mnemonics"); // NOI18N
301: }
302: }
|