pátek 28. března 2008

A všichni si rozumíme 2 - klávesové zkratky

Psali jste někdy lokalizované aplikace ve Swingu? A jak jste řešili klávesové zkratky (Mnemonics) pro různé jazyky? Já jsem se při programování JSignPdf rozhodl použít přístup, který znám z Delphi. V resource řetězcích vložím ampersand (&) před písmeno, které má být použito jako klávesová zkratka. Když chci zobrazit znak ampersand, tak napíšu dva za sebe.

A jak to funguje v kódu? Mám wrapper pro ResourceBundle, který nabízí mimo metod pro přístup k lokalizovaným stringům i metodu, která vrací MnemonicIndex (pozici znaku, který je použit jako klávesová zkratka).

/**
* Returns message for given key from active ResourceBundle
* @param aKey name of key in resource bundle
* @return message for given key
*/
public String get(final String aKey) {
  String tmpMessage = bundle.getString(aKey);
  if (tmpMessage == null) {
    tmpMessage = aKey;
  } else {
    tmpMessage = tmpMessage.replaceAll("&([^&])", "$1");
  }
  return tmpMessage;
}

/**
* Returns index of character which should be used as a mnemonic.
* It returns -1 if such an character doesn't exist.
* @param aKey resource key
* @return index (position) of character in translated message
*/
public int getMnemonicIndex(final String aKey) {
  String tmpMessage = bundle.getString(aKey);
  int tmpResult = -1;
  if (tmpMessage != null) {
    int searchFrom = 0;
    int tmpDoubles = 0;
    int tmpPos;
    final int tmpLen = tmpMessage.length();
    do {
      tmpPos = tmpMessage.indexOf('&', searchFrom);
      if (tmpPos == tmpLen-1) tmpPos = -1;
      if (tmpPos>-1) {
        if (tmpMessage.charAt(tmpPos+1) != '&') {
          tmpResult = tmpPos - tmpDoubles;
        } else {
          searchFrom = tmpPos + 2;
          tmpDoubles++;
        }
      }
    } while (tmpPos!=-1 && tmpResult==-1 && searchFrom<tmpLen);
  }
  return tmpResult;
}

/**
* Returns message for given key from active ResourceBundle and replaces
* parameters with values given in array.
* @param aKey key in resource bundle
* @param anArgs array of parameters to replace in message
* @return message for given key with given arguments
*/
public String get(String aKey, String anArgs[]) {
  String tmpMessage = get(aKey);
  if (aKey==tmpMessage || anArgs == null || anArgs.length == 0) {
    return tmpMessage;
  }
  final MessageFormat tmpFormat = new MessageFormat(tmpMessage);
  return tmpFormat.format(anArgs);
}

použití uvedeného wrapperu potom vypadá následovně (res je jméno instance wrapperu):

/**
* Application translations.
*/
private void translateLabels() {
  setTitle(res.get("gui.title", new String[] {Constants.VERSION}));
  setLabelAndMnemonic(lblKeystoreType, "gui.keystoreType.label");
  setLabelAndMnemonic(chkbAdvanced, "gui.advancedView.checkbox");
  setLabelAndMnemonic(btnSignIt,"gui.signIt.button");
}

/**
* Sets translations and mnemonics for labels and different kind of buttons
* @param aComponent component in which should be label set
* @param aKey message key
*/
private void setLabelAndMnemonic(final JComponent aComponent, final String aKey) {
  final String tmpLabelText = res.get(aKey);
  final int tmpMnemIndex = res.getMnemonicIndex(aKey);
  if (aComponent instanceof JLabel) {
    final JLabel tmpLabel = (JLabel) aComponent;
    tmpLabel.setText(tmpLabelText);
    if (tmpMnemIndex>-1) {
      tmpLabel.setDisplayedMnemonic(tmpLabelText.toLowerCase().charAt(tmpMnemIndex));
      tmpLabel.setDisplayedMnemonicIndex(tmpMnemIndex);
    }
  } else if (aComponent instanceof AbstractButton) {
    //handles Buttons, Checkboxes and Radiobuttons
    final AbstractButton tmpBtn = (AbstractButton) aComponent;
    tmpBtn.setText(tmpLabelText);
    if (tmpMnemIndex>-1) {
      tmpBtn.setMnemonic(tmpLabelText.toLowerCase().charAt(tmpMnemIndex));
    }
  } else {
    throw new IllegalArgumentException();
  }
}

Resource fajl potom obsahuje takovéto záznamy:

gui.advancedView.checkbox=A&dvanced view
gui.alias.label=Key &alias

Žádné komentáře: