diff --git a/rse/plugins/org.eclipse.rse.ui/META-INF/MANIFEST.MF b/rse/plugins/org.eclipse.rse.ui/META-INF/MANIFEST.MF index 0a902440875..1bc3e5456f3 100644 --- a/rse/plugins/org.eclipse.rse.ui/META-INF/MANIFEST.MF +++ b/rse/plugins/org.eclipse.rse.ui/META-INF/MANIFEST.MF @@ -15,7 +15,9 @@ Require-Bundle: org.eclipse.ui, org.eclipse.ui.ide, org.eclipse.ui.workbench.texteditor, org.eclipse.rse.core -Import-Package: com.ibm.icu.text +Import-Package: com.ibm.icu.lang, + com.ibm.icu.text, + com.ibm.icu.util Eclipse-LazyStart: true Export-Package: org.eclipse.rse.core.internal.subsystems;x-internal:=true, org.eclipse.rse.core.servicesubsystem, diff --git a/rse/plugins/org.eclipse.rse.ui/UI/org/eclipse/rse/internal/ui/view/SystemViewMenuListener.java b/rse/plugins/org.eclipse.rse.ui/UI/org/eclipse/rse/internal/ui/view/SystemViewMenuListener.java index 3aca18f15f5..d247d498648 100644 --- a/rse/plugins/org.eclipse.rse.ui/UI/org/eclipse/rse/internal/ui/view/SystemViewMenuListener.java +++ b/rse/plugins/org.eclipse.rse.ui/UI/org/eclipse/rse/internal/ui/view/SystemViewMenuListener.java @@ -109,15 +109,33 @@ public class SystemViewMenuListener implements IMenuListener, MenuListener, ArmL if (!menuMnemonicsAdded || !doOnce) { m.clear(); - if (!armListeners) - m.setMnemonics((Menu)event.getSource()); - else - m.setMnemonicsAndArmListener((Menu)event.getSource(), this); + Menu menu = (Menu)event.getSource(); + m.setMnemonics(menu); + if (armListeners) { + setArmListener(menu); + } menuMnemonicsAdded = true; if (doOnce) ((Menu)event.getSource()).removeMenuListener(this); } } + + private void setArmListener(Menu menu) { + MenuItem[] items = menu.getItems(); + for (int i = 0; i < items.length; i++) { + MenuItem menuItem = items[i]; + setArmListener(menuItem); + } + } + + private void setArmListener(MenuItem item) { + item.addArmListener(this); + Menu menu = item.getMenu(); + if (menu != null) { + setArmListener(menu); + } + } + // -------------------- // ArmListener methods // -------------------- diff --git a/rse/plugins/org.eclipse.rse.ui/UI/org/eclipse/rse/ui/Mnemonics.java b/rse/plugins/org.eclipse.rse.ui/UI/org/eclipse/rse/ui/Mnemonics.java index dbd8c56bad8..e7680566729 100644 --- a/rse/plugins/org.eclipse.rse.ui/UI/org/eclipse/rse/ui/Mnemonics.java +++ b/rse/plugins/org.eclipse.rse.ui/UI/org/eclipse/rse/ui/Mnemonics.java @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2000, 2006 IBM Corporation. All rights reserved. + * Copyright (c) 2000, 2007 IBM Corporation and others. All rights reserved. * This program and the accompanying materials are made available under the terms * of the Eclipse Public License v1.0 which accompanies this distribution, and is * available at http://www.eclipse.org/legal/epl-v10.html @@ -11,15 +11,19 @@ * Emily Bruner, Mazen Faraj, Adrian Storisteanu, Li Ding, and Kent Hawley. * * Contributors: - * {Name} (company) - description of contribution. + * Martin Oberhuber (Wind River) - [187860] review for adding foreign lang support ********************************************************************************/ package org.eclipse.rse.ui; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.rse.ui.widgets.InheritableEntryField; -import org.eclipse.swt.events.ArmListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; @@ -30,219 +34,279 @@ import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Text; +import com.ibm.icu.lang.UCharacter; +import com.ibm.icu.lang.UCharacter.UnicodeBlock; +import com.ibm.icu.util.ULocale; + /** - * A class for creating unique mnemonics per control per window. + * A class for creating unique mnemonics for each control in a given + * context - usually a composite control of some sort. */ public class Mnemonics { - private static final String[] TransparentEndings = { // endings that should appear after a mnemonic if one is added - "...", // ellipsis //$NON-NLS-1$ - ">>", // standard "more" //$NON-NLS-1$ - "<<", // standard "less" //$NON-NLS-1$ - ">", // "more" -- non-standard usage, must appear in list after >> //$NON-NLS-1$ - "<", // "less" -- non-standard usage, must appear in list after << //$NON-NLS-1$ - ":", // colon //$NON-NLS-1$ - "\uff0e\uff0e\uff0e", // wide ellipsis //$NON-NLS-1$ - "\uff1e\uff1e", // wide standard "more" //$NON-NLS-1$ - "\uff1c\uff1c", // wide standard "less" //$NON-NLS-1$ - "\uff1e", // wide non-standard "more" //$NON-NLS-1$ - "\uff1c", // wide non-standard "less" //$NON-NLS-1$ - "\uff1a" // wide colon //$NON-NLS-1$ - }; - private StringBuffer mnemonics = new StringBuffer(); // mnemonics used so far - private static final String candidateChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; //$NON-NLS-1$ - private String preferencePageMnemonics = null; // mnemonics used by Eclipse on preference pages - private String wizardPageMnemonics = null; // mnemonics used by Eclipse on wizard pages - public static final char MNEMONIC_CHAR = '&'; - private boolean onPrefPage = false; - private boolean onWizardPage = false; + private static final char MNEMONIC_CHAR = '&'; + + /* + * Interesting ISO 639-1 language codes. + */ + private static final String LC_GREEK = "el"; //$NON-NLS-1$ + private static final String LC_RUSSIAN = "ru"; //$NON-NLS-1$ + + /* + * Known valid mnemonic candidates + */ + private static final String GREEK_MNEMONICS = "\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9"; //$NON-NLS-1$ + private static final String RUSSIAN_MNEMONICS = "\u0410\u0411\u0412\u0413\u0414\u0145\u0401\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f"; //$NON-NLS-1$ + private static final String LATIN_MNEMONICS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //$NON-NLS-1$ + +// private static final String[] TRANSPARENT_ENDINGS = { ELLIPSIS, DOUBLE_GT, DOUBLE_LT, SINGLE_GT, SINGLE_LT, COLON, FW_ELLIPSIS, FW_DOUBLE_GT, FW_DOUBLE_LT, FW_SINGLE_GT, FW_SINGLE_LT, FW_COLON }; + private final static Pattern TRANSPARENT_ENDING_PATTERN = Pattern.compile("(\\s|\\.\\.\\.|>|<|:|\uff0e\uff0e\uff0e|\uff1e|\uff1c|\uff1a)+$"); //$NON-NLS-1$ private boolean applyMnemonicsToPrecedingLabels = true; - /** - * Clear the list for re-use - */ - public void clear() { - mnemonics = new StringBuffer(); - } + private Set usedSet = new HashSet(); /** - * Inserts an added mnemonic of the form (&x) into a StringBuffer at the correct point. - * Checks for transparent endings and trailing spaces. - * @param label the label to check + * Helper method to return the mnemonic from a string. + * Helpful when it is necessary to know the mnemonic assigned so it can be reassigned, + * such as is necessary for buttons which toggle their text. + * @param text the label from which to extract the mnemonic + * @return the mnemonic if assigned, else a blank character. */ - private static void insertMnemonic(StringBuffer label, String mnemonic) { - int p = label.length(); - // check for trailing spaces #1 - while (p > 0 && label.charAt(p - 1) == ' ') { - p--; - } - // check for transparent endings - for (int i = 0; i < TransparentEndings.length; i++) { - String transparentEnding = TransparentEndings[i]; - int l = transparentEnding.length(); - int n = p - l; - if (n >= 0) { - String labelEnding = label.substring(n, n + l); - if (labelEnding.equals(transparentEnding)) { - p = n; - break; - } - } - } - // check for trailing spaces #2 - while (p > 0 && label.charAt(p - 1) == ' ') { - p--; - } - // make sure there is something left to attach a mnemonic to - if (p > 0) { - label.insert(p, mnemonic); - } + public static char getMnemonic(String text) { + int idx = text.indexOf(MNEMONIC_CHAR); + if (idx >= 0 && idx < (text.length() - 1)) + return text.charAt(idx + 1); + else + return ' '; } - /** - * Given a string, this starts at the first character and iterates until - * it finds a character not previously used as a mnemonic on this page. - * Not normally called from other classes, but rather by the setMnemonic - * methods in this class. - * @param label String to which to generate and apply the mnemonic - * @return input String with '&' inserted in front of the unique character - */ - public String setUniqueMnemonic(String label) { - // Kludge for now - // If there is already a mnemonic, remove it - label = removeMnemonic(label); - //int iMnemonic = label.indexOf( MNEMONIC_CHAR ); - //if( iMnemonic >= 0 && iMnemonic < label.length() - 1 ){ - //mnemonics.append( label.charAt( iMnemonic + 1 ) ); - //return label; - //} - int labelLen = label.length(); - if (labelLen == 0) - return label; - else if ((labelLen == 1) && label.equals("?")) //$NON-NLS-1$ - return label; - StringBuffer newLabel = new StringBuffer(label); - int mcharPos = findUniqueMnemonic(label); - if (mcharPos != -1) - newLabel.insert(mcharPos, MNEMONIC_CHAR); - // if no unique character found, then - // find a new arbitrary one from the alphabet... - else { - mcharPos = findUniqueMnemonic(candidateChars); - if (mcharPos != -1) { - String addedMnemonic = "(" + MNEMONIC_CHAR + candidateChars.charAt(mcharPos) + ")"; //$NON-NLS-1$ //$NON-NLS-2$ - insertMnemonic(newLabel, addedMnemonic); - } - } - return newLabel.toString(); - } // end getUniqueMnemonic - /** * Given a label and mnemonic, this applies that mnemonic to the label. * Not normally called from other classes, but rather by the setUniqueMnemonic * methods in this class. * @param label String to which to apply the mnemonic * @param mnemonicChar the character that is to be the mnemonic character - * @return input String with '&' inserted in front of the given character + * @return input String with '&' inserted in front of the given character, + * or with "(c)" appended to the label at a proper position in case the + * character c is not part of the label. */ public static String applyMnemonic(String label, char mnemonicChar) { int labelLen = label.length(); if (labelLen == 0) return label; StringBuffer newLabel = new StringBuffer(label); - int mcharPos = findCharPos(label, mnemonicChar); + int mcharPos = label.indexOf(mnemonicChar); if (mcharPos != -1) newLabel.insert(mcharPos, MNEMONIC_CHAR); else { String addedMnemonic = new String("(" + MNEMONIC_CHAR + mnemonicChar + ")"); //$NON-NLS-1$ //$NON-NLS-2$ - insertMnemonic(newLabel, addedMnemonic); + int p = getEndingPosition(label); + newLabel.insert(p, addedMnemonic); } return newLabel.toString(); - } // end getUniqueMnemonic - - /** - * Given a char, find its position in the given string - */ - private static int findCharPos(String label, char charToFind) { - int pos = -1; - for (int idx = 0; (pos == -1) && (idx < label.length()); idx++) - if (label.charAt(idx) == charToFind) pos = idx; - return pos; } /** - * Determine if given char is a unique mnemonic + * Helper method to strip the mnemonic from a string. + * Useful if using Eclipse supplied labels. + * @param text the label from which to strip the mnemonic + * @return the label with the mnemonic stripped */ - public boolean isUniqueMnemonic(char currchar) { - boolean isUnique = true; - for (int idx = 0; isUnique && (idx < mnemonics.length()); idx++) - if (mnemonics.charAt(idx) == currchar) isUnique = false; - return isUnique; + public static String removeMnemonic(String text) { + String[] parts = text.split("\\(\\&.\\)", 2); //$NON-NLS-1$ + if (parts.length == 1) { + parts = text.split("\\&", 2); //$NON-NLS-1$ + } + if (parts.length == 2) { + text = parts[0] + parts[1]; + } + return text; + } + + /** + * Finds the point at which to insert a mnemonic of the form (&x). + * Checks for transparent endings and trailing spaces. + * @param label the label to check + * @return the position at which a mnemonic can be inserted. + */ + private static int getEndingPosition(String label) { + Matcher m = TRANSPARENT_ENDING_PATTERN.matcher(label); + int result = m.find() ? m.start() : label.length(); + return result; } /** - * Find a uniqe mnemonic char in given string. - * Note if one is found, it is added to the list of currently used mnemonics! - * - * @return index position of unique character in input string, or -1 if none found. + * Clear the list of used mnemonic characters */ - public int findUniqueMnemonic(String label) { - int labelLen = label.length(); - if (labelLen == 0) return -1; - int retcharPos = -1; - label = label.toUpperCase(); - char currchar = label.charAt(0); - boolean isUnique = false; + public void clear() { + usedSet.clear(); + } - // if we're on a preference page, get the preference page mnemonics - if (onPrefPage) { - - if (preferencePageMnemonics == null) { - preferencePageMnemonics = getPreferencePageMnemonics(); - } + /** + * Given a string, this starts at the first character and iterates until + * it finds a character not already used as a mnemonic. + * Not normally called from other classes, but rather by the setMnemonic + * methods in this class. + * Sets the mnemonic according to the org.eclipse.rse.ui/MNEMONIC_POLICY preference. + * (Note: this preference and the values below are NOT guaranteed API as yet and may change + * without notice). + * In all policies, if the label has a mnemonic it is not touched. + * Duplicate mnemonics can occur between labels that have hard coded mnemonics. + * + * @param label String to which to generate and apply the mnemonic + * @return input String with '&' inserted in front of the mnemonic character + */ + public String setUniqueMnemonic(String label) { + int policy = 3; + // determine the cases where the label does not need a mnemonic + if (policy == 0 || label == null || label.trim().length() == 0 || label.equals("?")) { //$NON-NLS-1$ + return label; } - - // if we're on a wizard page, get the wizard page mnemonics - if (onWizardPage) { - - if (wizardPageMnemonics == null) { - wizardPageMnemonics = getWizardPageMnemonics(); - } - } - - // attempt to find the first character in the given - // string that has not already been used as a mnemonic - for (int idx = 0; (idx < labelLen) && (retcharPos == -1); idx++) { - currchar = label.charAt(idx); - - if (!(onPrefPage && preferencePageMnemonics.indexOf(currchar) != -1) && !(onWizardPage && wizardPageMnemonics.indexOf(currchar) != -1) && candidateChars.indexOf(currchar) != -1) { - isUnique = isUniqueMnemonic(currchar); - if (isUnique) { - mnemonics.append(currchar); - retcharPos = idx; + // if a mnemonic exists in the label mark it as used + char mn = getMnemonic(label); + makeUsed(mn); + // if a mnemonic exists in the label use it, if not add one + if (mn == ' ') { // no mnemonic exists + int p = findUniqueMnemonic(label); + String mnemonicString = ""; //$NON-NLS-1$ + if (p >= 0) { // a character in the label can be used as the mnemonic + makeUsed(label.charAt(p)); + mnemonicString = Character.toString(MNEMONIC_CHAR); + } else { + // a unique character in the label cannot be found, add one according to the policy + if (policy == 1) { // policy 1, do not add one if one cannot be found + } + else if (policy == 2) { // policy 2, use a letter from the label anyway, favor upper case + int endingPosition=getEndingPosition(label); + for (p = 0; p < endingPosition; p++) { + mn = label.charAt(p); + if (UCharacter.isUpperCase(mn)) break; + } + if (p == endingPosition) { + for (p = 0; p < endingPosition; p++) { + mn = label.charAt(p); + if (UCharacter.isLetter(mn)) break; + } + } + if (p < label.length()) { + mnemonicString = Character.toString(MNEMONIC_CHAR); + } + } + else if (policy == 3) { // policy 3, add a mnemonic at the end + String candidates = getCandidates(); + p = findUniqueMnemonic(candidates); + if (p >= 0) { + mn = candidates.charAt(p); + mnemonicString = "(" + MNEMONIC_CHAR + mn + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + p = getEndingPosition(label); + makeUsed(mn); + } } } + StringBuffer newLabel = new StringBuffer(label); + if (p >= 0) { + newLabel.insert(p, mnemonicString); + } + label = newLabel.toString(); + } else { // a valid mnemonic already exists in the label + makeUsed(mn); } - return retcharPos; + return label; } /** - * Returns a string containing the mnemonics for a preference page. - * @return the mnemonics. + * Determine if given char is a unique mnemonic. + * @param ch the character to test. + * @return true if the character has not yet been used. */ - private String getPreferencePageMnemonics() { - String[] labels = JFaceResources.getStrings(new String[] { "defaults", "apply" }); //$NON-NLS-1$ //$NON-NLS-2$ - return getMnemonicsFromStrings(labels).toUpperCase(); + public boolean isUniqueMnemonic(char ch) { + return !isUsed(ch); + } + + /** + * @return a string of acceptable mnemonic candidates for the language + * of the current locale. + */ + private String getCandidates() { + /* + * This is a coarse-grained approach and uses the 2-letter language codes from ISO 639-1. + * This should be quite sufficient for mnemonic generation. + */ + String language = ULocale.getDefault().getLanguage(); + if (language.equals(LC_GREEK)) return GREEK_MNEMONICS; + if (language.equals(LC_RUSSIAN)) return RUSSIAN_MNEMONICS; + return LATIN_MNEMONICS; } /** - * Returns a string containing the mnemonics for a wizard page. - * @return the mnemonics. + * Find a unique mnemonic char in given string. + * @param label the string in which to search for the best mnemonic character + * @return index position of unique character in input string, or -1 if none found. */ - private String getWizardPageMnemonics() { - String[] labels = new String[] { IDialogConstants.BACK_LABEL, IDialogConstants.NEXT_LABEL, IDialogConstants.FINISH_LABEL }; - return getMnemonicsFromStrings(labels).toUpperCase(); + private int findUniqueMnemonic(String label) { + int uniqueIndex = -1; + char ch = label.charAt(0); + for (int i = 0; (i < label.length()) && (uniqueIndex == -1); i++) { + ch = label.charAt(i); + if (!isUsed(ch) && isAcceptable(ch)) { + uniqueIndex = i; + } + } + return uniqueIndex; } - + + private boolean isAcceptable(char ch) { + UnicodeBlock block = UnicodeBlock.of(ch); + boolean result = (isAcceptable(block) && UCharacter.isLetter(ch)); // the character is a letter + return result; + } + + private boolean isAcceptable(UnicodeBlock block) { + if (block == UnicodeBlock.BASIC_LATIN) return true; + if (block == UnicodeBlock.LATIN_1_SUPPLEMENT) return true; + if (block == UnicodeBlock.LATIN_EXTENDED_A) return true; + if (block == UnicodeBlock.LATIN_EXTENDED_B) return true; + if (block == UnicodeBlock.LATIN_EXTENDED_C) return true; + if (block == UnicodeBlock.LATIN_EXTENDED_D) return true; + if (block == UnicodeBlock.GREEK) return true; + if (block == UnicodeBlock.CYRILLIC) return true; + if (block == UnicodeBlock.HEBREW) return true; + if (block == UnicodeBlock.ARABIC) return true; + return false; + } + + private boolean isUsed(char ch) { + // TODO if we are guaranteed java 1.5 we can use Character.valueOf(ch) + boolean result = usedSet.contains(new Character(ch)); + return result; + } + + private void makeUsed(char ch) { + if (ch != ' ') { + char lower = Character.toLowerCase(ch); + char upper = Character.toUpperCase(ch); + usedSet.add(new Character(lower)); + usedSet.add(new Character(upper)); + } + } + + private void makeUsed(String s) { + for (int i = 0; i < s.length(); i++) { + makeUsed(s.charAt(i)); + } + } + /** * Returns a string with the mnemonics for a given array of strings. * @param strings the array of strings. @@ -259,243 +323,6 @@ public class Mnemonics { return result.toString(); } - /** - * Adds a mnemonic to an SWT Button such that the user can select it via Ctrl/Alt+mnemonic. - * Note a mnemonic unique to this window is chosen - */ - public void setMnemonic(Button button) { - removeMnemonic(button); // just in case it already has a mnemonic - String text = button.getText(); - if ((text != null) && (text.trim().length() > 0)) button.setText(setUniqueMnemonic(text)); - } - - /** - * If a button is removed from a dialog window, call this method to remove its mnemonic from the list for this dialog. - * This frees it up for another button to use. - */ - public void removeMnemonic(Button button) { - String text = button.getText(); - if (text == null) return; - int idx = text.indexOf(MNEMONIC_CHAR); - if (idx >= 0) { - StringBuffer buffer = new StringBuffer(text); - char mchar = buffer.charAt(idx + 1); // the char after the & - buffer.deleteCharAt(idx); // delete the & - boolean found = false; - for (int mdx = 0; !found && (mdx < mnemonics.length()); mdx++) - if (mnemonics.charAt(mdx) == mchar) { - found = true; - mnemonics.deleteCharAt(mdx); - } - - button.setText(buffer.toString()); - } - } - - /** - * Helper method to strip the mnemonic from a string. - * Useful if using Eclipse supplied labels - */ - public static String removeMnemonic(String text) { - int idx = text.indexOf(MNEMONIC_CHAR); - if (idx >= 0) { - StringBuffer buffer = new StringBuffer(text); - buffer.deleteCharAt(idx); // delete the & - - // in case of already appended (&X), remove the remaining (X) - if (buffer.length() > (1 + idx) && idx > 1 && buffer.charAt(idx + 1) == ')' && buffer.charAt(idx - 1) == '(') buffer.delete(idx - 1, idx + 2); - return buffer.toString(); - } else - return text; - } - - /** - * Remove and free up mnemonic - */ - public String removeAndFreeMnemonic(String text) { - int idx = text.indexOf(MNEMONIC_CHAR); - if (idx >= 0) { - StringBuffer buffer = new StringBuffer(text); - char mchar = buffer.charAt(idx + 1); // the char after the & - buffer.deleteCharAt(idx); // delete the & - boolean found = false; - for (int mdx = 0; !found && (mdx < mnemonics.length()); mdx++) - if (mnemonics.charAt(mdx) == mchar) { - found = true; - mnemonics.deleteCharAt(mdx); - } - return buffer.toString(); - } else - return text; - } - - /** - * Helper method to return the mnemonic from a string. - * Helpful when it is necessary to know the mnemonic assigned so it can be reassigned, - * such as is necessary for buttons which toggle their text. - * @return the mnemonic if assigned, else a blank character. - */ - public static char getMnemonic(String text) { - int idx = text.indexOf(MNEMONIC_CHAR); - if (idx >= 0) - return text.charAt(idx + 1); - else - return ' '; - } - - /** - * Given a Composite, this method walks all the children recursively and - * and sets the mnemonics uniquely for each child control where a - * mnemonic makes sense (eg, buttons). - * The letter/digit chosen for the mnemonic is unique for this Composite, - * so you should call this on as high a level of a composite as possible - * per Window. - * Call this after populating your controls. - */ - public void setMnemonics(Composite parent) { - Control children[] = parent.getChildren(); - if (children != null) { - Control currChild = null; - boolean bSetText = false; - for (int idx = 0; idx < children.length; idx++) { - currChild = children[idx]; - // composite? Recurse - // d54732: but do not recurse if it is a combo! For a combo, we want to check - // if there is a preceding label. It's meaningless for a combo to - // have children anyway (KM) - if ((currChild instanceof Composite) && - (!applyMnemonicsToPrecedingLabels || (applyMnemonicsToPrecedingLabels && !(currChild instanceof Combo) && !(currChild instanceof InheritableEntryField)))) - setMnemonics((Composite) currChild); - // button? select and apply unique mnemonic... - else if (currChild instanceof Button) { - Button currButton = (Button) currChild; - String text = currButton.getText(); - if ((text != null) && (text.trim().length() > 0)) { - currButton.setText(setUniqueMnemonic(text)); - bSetText = true; - } - } - // entry field or combo box? select and apply unique mnemonic to preceding label... - else if (applyMnemonicsToPrecedingLabels && (idx > 0) && ((currChild instanceof Text) || (currChild instanceof Combo) || (currChild instanceof InheritableEntryField)) && - (children[idx - 1] instanceof Label)) { - Label currLabel = (Label) children[idx - 1]; - String text = currLabel.getText(); - if ((text != null) && (text.trim().length() > 0)) { - currLabel.setText(setUniqueMnemonic(text)); - bSetText = true; - } - } - } - if (bSetText == true) parent.layout(true); // in case a (x) was appended, we need to layout the controls again - } - } - - /** - * Given a menu, this method walks all the items and assigns each a unique - * memnonic. Also handles casdading submenus. - * Call this after populating the menu. - */ - public void setMnemonics(Menu menu) { - MenuItem[] children = menu.getItems(); - if ((children != null) && (children.length > 0)) { - MenuItem currChild = null; - for (int idx = 0; idx < children.length; idx++) { - currChild = children[idx]; - String text = currChild.getText(); - if ((text != null) && (text.length() > 0)) { - if (text.indexOf(MNEMONIC_CHAR) < 0) // bad things happen when setting mnemonics twice! - { - Image image = currChild.getImage(); - currChild.setText(setUniqueMnemonic(text)); - if (image != null) currChild.setImage(image); - } - } - } - } - } - - /** - * Given a menu, this method walks all the items and assigns each a unique - * memnonic. Also handles casdading submenus. - *

- * Also, since while we are at it, this overloaded method also sets a given ArmListener - * to each menu item, perhaps for the purpose of displaying tooltip text. - * It makes sense to do this when doing mnemonics because both must be done for every menu item - * with text and must be done exactly once for each. - *

- * Call this after populating the menu. - */ - public void setMnemonicsAndArmListener(Menu menu, ArmListener listener) { - MenuItem[] children = menu.getItems(); - if ((children != null) && (children.length > 0)) { - MenuItem currChild = null; - for (int idx = 0; idx < children.length; idx++) { - currChild = children[idx]; - String text = currChild.getText(); - if ((text != null) && (text.length() > 0)) { - int mnemonicIndex = text.indexOf(MNEMONIC_CHAR); - if (mnemonicIndex < 0) // bad things happen when setting mnemonics twice! - { - Image image = currChild.getImage(); - currChild.setText(setUniqueMnemonic(text)); - if (image != null) currChild.setImage(image); - currChild.addArmListener(listener); - } else - // hmm, already has a mnemonic char. Either it is an Eclipse/BP-supplied action, or we have been here before. - // The distinction is important as want to add an Arm listener, but only once! - { - // for now we do the brute force ugly thing... - Image image = currChild.getImage(); - - // need to adjust any action that already has this mnemonic - char c = text.charAt(mnemonicIndex + 1); - - // anything already have this? - if (!isUniqueMnemonic(c)) { - // if so, we need to adjust existing action - for (int n = 0; n < idx; n++) { - MenuItem oChild = children[n]; - String oText = oChild.getText(); - char oldN = getMnemonic(oText); - if (oldN == c) { - // this one now needs to change - String cleanText = removeMnemonic(oText); - oChild.setText(setUniqueMnemonic(cleanText)); - } - } - } - - text = removeAndFreeMnemonic(text); - currChild.setText(setUniqueMnemonic(text)); - if (image != null) currChild.setImage(image); - currChild.removeArmListener(listener); // just in case - currChild.addArmListener(listener); - } - } - } - } - } - - /** - * Set if the mnemonics are for a preference page - * Preference pages already have a few buttons with mnemonics set by Eclipse - * We have to make sure we do not use the ones they use - */ - public Mnemonics setOnPreferencePage(boolean page) { - this.onPrefPage = page; - return this; - } - - /** - * Set if the mnemonics are for a wizard page - * Wizard pages already have a few buttons with mnemonics set by Eclipse - * We have to make sure we do not use the ones they use - */ - public Mnemonics setOnWizardPage(boolean page) { - this.onWizardPage = page; - return this; - } - /** * Set whether to apply mnemonics to labels preceding text fields, combos and inheritable entry fields. * This is for consistency with Eclipse. Only set to false if it does not work @@ -509,4 +336,186 @@ public class Mnemonics { return this; } + /** + * Adds a mnemonic to an SWT Button such that the user can select it via Ctrl/Alt+mnemonic. + * Note a mnemonic unique to this window is chosen. + * @param button the button to equip with a mnemonic + * @return true if the button was actually changed + */ + public boolean setMnemonic(Button button) { + boolean changed = false; + String text = button.getText(); + if ((text != null) && (text.trim().length() > 0)) { + String newText = setUniqueMnemonic(text); + if (!text.equals(newText)) { + button.setText(newText); + changed = true; + } + } + return changed; + } + + /** + * Given a menu, this method walks all the items and assigns each a unique + * mnemonic. Also handles cascading menus. + * The mnemonics + * used on cascades are independent of those of the parent. + * Call this after populating the menu. + * @param menu the menu to examine + */ + public void setMnemonics(Menu menu) { + gatherMenuMnemonics(menu); + MenuItem[] items = menu.getItems(); + for (int i = 0; i < items.length; i++) { + MenuItem menuItem = items[i]; + String text = menuItem.getText(); + if (text.indexOf(MNEMONIC_CHAR) < 0) { // if there is no mnemonic + String newText = setUniqueMnemonic(text); + if (!text.equals(newText)) { + Image image = menuItem.getImage(); + menuItem.setText(newText); + if (image != null) { + menuItem.setImage(image); + } + } + } + Menu cascade = menuItem.getMenu(); + if (cascade != null) { + Mnemonics context = new Mnemonics(); + context.setMnemonics(cascade); + } + } + } + + private void gatherMenuMnemonics(Menu menu) { + MenuItem[] items = menu.getItems(); + for (int i = 0; i < items.length; i++) { + MenuItem menuItem = items[i]; + String text = menuItem.getText(); + char ch = getMnemonic(text); + makeUsed(ch); + } + } + + /** + * Given a Composite, this method walks all the children recursively and + * and sets the mnemonics uniquely for each child control where a + * mnemonic makes sense (eg, buttons). + * The letter/digit chosen for the mnemonic is unique for this Composite, + * so you should call this on as high a level of a composite as possible + * per window. + * Call this after populating your controls. + * @param parent the parent control to examine. + */ + public void setMnemonics(Composite parent) { + setMnemonics(parent, new HashSet()); + } + + /** + * Given a Composite, this method walks all the children recursively and + * and sets the mnemonics uniquely for each child control where a + * mnemonic makes sense (for example, buttons). + * The letter/digit chosen for the mnemonic is unique for this Composite, + * so you should call this on as high a level of a composite as possible + * per window. + * Call this after populating your controls. + * @param parent the parent control to examine. + * @param ignoredControls the set of controls in which to not set mnemonics. + * If the controls are composites, their children are also not examined. + */ + public void setMnemonics(Composite parent, Set ignoredControls) { + gatherCompositeMnemonics(parent); + boolean mustLayout = setCompositeMnemonics(parent, ignoredControls); + if (mustLayout) { + parent.layout(true); + } + } + + private boolean setCompositeMnemonics(Composite parent, Set ignoredControls) { + Control children[] = parent.getChildren(); + Control currentChild = null; + boolean mustLayout = false; + for (int i = 0; i < children.length; i++) { + Control previousChild = currentChild; + currentChild = children[i]; + if (!ignoredControls.contains(currentChild)) { + if (currentChild instanceof Combo || currentChild instanceof InheritableEntryField || currentChild instanceof Text) { + if (applyMnemonicsToPrecedingLabels && previousChild instanceof Label) { + Label label = (Label) previousChild; + String text = label.getText(); + if ((text != null) && (text.trim().length() > 0)) { + String newText = setUniqueMnemonic(text); + if (!text.equals(newText)) { + label.setText(setUniqueMnemonic(text)); + mustLayout = true; + } + } + } + } else if (currentChild instanceof Button) { + mustLayout |= setMnemonic((Button)currentChild); + } else if (currentChild instanceof Composite) { + /* + * d54732: (KM) we test Composites last since we don't want to recurse if it is a Combo. + * For a combo, we want to check if there is a preceding label. + * It's meaningless for a combo to have children. + */ + mustLayout |= setCompositeMnemonics((Composite) currentChild, ignoredControls); + } // ignore other controls + } + } + return mustLayout; + } + + private void gatherCompositeMnemonics(Composite parent) { + Control children[] = parent.getChildren(); + Control currentChild = null; + for (int i = 0; i < children.length; i++) { + Control previousChild = currentChild; + currentChild = children[i]; + String childText = null; + if (currentChild instanceof Combo || currentChild instanceof InheritableEntryField || currentChild instanceof Text) { + if (applyMnemonicsToPrecedingLabels && previousChild instanceof Label) { + Label label = (Label) previousChild; + childText = label.getText(); + } + } else if (currentChild instanceof Button) { + childText = ((Button)currentChild).getText(); + } else if (currentChild instanceof Composite) { + gatherCompositeMnemonics((Composite) currentChild); + } // ignore other controls + if (childText != null) { + char ch = getMnemonic(childText); + makeUsed(ch); + } + } + } + + /** + * Set if the mnemonics are for a preference page + * Preference pages already have a few buttons with mnemonics set by Eclipse + * We have to make sure we do not use the ones they use + */ + public Mnemonics setOnPreferencePage(boolean page) { + if (page) { + String[] labels = JFaceResources.getStrings(new String[] { "defaults", "apply" }); //$NON-NLS-1$ //$NON-NLS-2$ + String used = getMnemonicsFromStrings(labels).toUpperCase(); + makeUsed(used); + } + return this; + } + + /** + * Set if the mnemonics are for a wizard page + * Wizard pages already have a few buttons with mnemonics set by Eclipse + * We have to make sure we do not use the ones they use + */ + public Mnemonics setOnWizardPage(boolean page) { + if (page) { + String[] labels = new String[] { IDialogConstants.BACK_LABEL, IDialogConstants.NEXT_LABEL, IDialogConstants.FINISH_LABEL }; + String used = getMnemonicsFromStrings(labels).toUpperCase(); + makeUsed(used); + } + return this; + } + } \ No newline at end of file