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. + *
- * 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