f@0: /*
f@0: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
f@0:
f@0: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
f@0:
f@0: This program is free software: you can redistribute it and/or modify
f@0: it under the terms of the GNU General Public License as published by
f@0: the Free Software Foundation, either version 3 of the License, or
f@0: (at your option) any later version.
f@0:
f@0: This program is distributed in the hope that it will be useful,
f@0: but WITHOUT ANY WARRANTY; without even the implied warranty of
f@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
f@0: GNU General Public License for more details.
f@0:
f@0: You should have received a copy of the GNU General Public License
f@0: along with this program. If not, see .
f@0: */
f@0:
f@0: package uk.ac.qmul.eecs.ccmi.speech;
f@0:
f@0: import java.awt.AWTKeyStroke;
f@0: import java.awt.Component;
f@0: import java.awt.Container;
f@0: import java.awt.FocusTraversalPolicy;
f@0: import java.awt.KeyboardFocusManager;
f@0: import java.awt.event.ActionEvent;
f@0: import java.awt.event.ItemEvent;
f@0: import java.awt.event.ItemListener;
f@0: import java.awt.event.KeyAdapter;
f@0: import java.awt.event.KeyEvent;
f@0: import java.awt.event.KeyListener;
f@0: import java.util.ResourceBundle;
f@0:
f@0: import javax.swing.AbstractAction;
f@0: import javax.swing.Action;
f@0: import javax.swing.JButton;
f@0: import javax.swing.JCheckBox;
f@0: import javax.swing.JComboBox;
f@0: import javax.swing.JComponent;
f@0: import javax.swing.JSpinner;
f@0: import javax.swing.JTabbedPane;
f@0: import javax.swing.JTextArea;
f@0: import javax.swing.JTextField;
f@0: import javax.swing.JTree;
f@0: import javax.swing.KeyStroke;
f@0: import javax.swing.text.BadLocationException;
f@0: import javax.swing.text.JTextComponent;
f@0:
f@0: import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
f@0: import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
f@0: import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
f@0:
f@0: /**
f@0: * A class providing static utilities methods concerning the text to speech synthesis.
f@0: *
f@0: */
f@0: public abstract class SpeechUtilities {
f@0: /* this class is of static use only */
f@0: private SpeechUtilities(){}
f@0:
f@0: public static String getComponentSpeech(Component c){
f@0: StringBuilder b = new StringBuilder();
f@0: if(c.getAccessibleContext().getAccessibleName() != null)
f@0: b.append(c.getAccessibleContext().getAccessibleName());
f@0: if(c instanceof JButton)
f@0: b.append(' ').append(resources.getString("component.button"));
f@0: else if(c instanceof JTextField){
f@0: b.append(' ').append(resources.getString("component.text_field"));
f@0: b.append(((JTextField)c).getText());
f@0: }else if(c instanceof JTextArea){
f@0: b.append(' ').append(resources.getString("component.text_area"));
f@0: b.append(((JTextArea)c).getText());
f@0: }else if(c instanceof JComboBox){
f@0: b.append(((JComboBox)c).getSelectedItem().toString());
f@0: b.append(' ').append(resources.getString("component.combo_box"));
f@0: }else if(c instanceof JCheckBox){
f@0: b.append(' ').append(((JCheckBox)c).isSelected() ? resources.getString("component.chech") : resources.getString("component.uncheck"));
f@0: }else if(c instanceof JSpinner){
f@0: b.append(' ').append(resources.getString("component.spinner"));
f@0: b.append(((JSpinner)c).getValue());
f@0: }else if(c instanceof JTabbedPane){
f@0: Component comp = ((JTabbedPane)c).getSelectedComponent();
f@0: if(comp == null)
f@0: return "";
f@0: b.append(' ').append( comp.getName());
f@0: }else if(!(c instanceof JTree)){
f@0: b.append(' ').append(c.getAccessibleContext().getAccessibleRole());
f@0: }
f@0: return b.toString();
f@0: }
f@0:
f@0: @SuppressWarnings("serial")
f@0: public static void changeTabListener(JComponent component, final Container container){
f@0: /* remove the default tab traversal key from all the containers */
f@0: disableTraversalKey(component);
f@0: /* get the look and feel default keys for moving the focus on (usually = TAB) */
f@0: for(AWTKeyStroke keyStroke : KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS))
f@0: component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(keyStroke.getKeyCode(),keyStroke.getModifiers()),"tab");
f@0:
f@0: /* add action to the moving focus keys: reproduce focus system and add speech to it */
f@0: component.getActionMap().put("tab", new AbstractAction(){
f@0: @Override
f@0: public void actionPerformed(ActionEvent evt) {
f@0: FocusTraversalPolicy policy = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalPolicy();
f@0: Component next = policy.getComponentAfter(container, KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner());
f@0: if(next == null)
f@0: return;
f@0: next.requestFocusInWindow();
f@0: NarratorFactory.getInstance().speak(SpeechUtilities.getComponentSpeech(next));
f@0: InteractionLog.log("TABBED PANE","change focus ",next.getAccessibleContext().getAccessibleName());
f@0: }
f@0: });
f@0:
f@0: for(AWTKeyStroke keyStroke : KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS))
f@0: component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(keyStroke.getKeyCode(),keyStroke.getModifiers()),"back_tab");
f@0:
f@0: component.getActionMap().put("back_tab", new AbstractAction(){
f@0: @Override
f@0: public void actionPerformed(ActionEvent evt) {
f@0: FocusTraversalPolicy policy = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalPolicy();
f@0: Component previous = policy.getComponentBefore(container, KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner());
f@0: if(previous == null)
f@0: return;
f@0: previous.requestFocusInWindow();
f@0: NarratorFactory.getInstance().speak(SpeechUtilities.getComponentSpeech(previous));
f@0: InteractionLog.log("TABBED PANE","change focus ",previous.getAccessibleContext().getAccessibleName());
f@0: }
f@0: });
f@0:
f@0: /* shut up the narrator upon pressing ctrl */
f@0: // component.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"ctrldown");
f@0: // component.getActionMap().put("ctrldown",new AbstractAction(){
f@0: // public void actionPerformed(ActionEvent evt){
f@0: // NarratorFactory.getInstance().shutUp();
f@0: // }
f@0: // });
f@0: }
f@0:
f@0: private static void disableTraversalKey(Container container){
f@0: for(final Component c : container.getComponents()){
f@0: if(c instanceof Container){
f@0: c.setFocusTraversalKeysEnabled(false);
f@0: disableTraversalKey((Container)c);
f@0: }
f@0: }
f@0: }
f@0:
f@0: public static KeyListener getSpeechKeyListener(boolean editableComponent, boolean secondVoice){
f@0: if(!editableComponent)
f@0: return new SpeechKeyListener(false,secondVoice);
f@0: return speechKeyListener;
f@0: }
f@0:
f@0: /**
f@0: * Returns a {@code speechKeyListener} using first voice (default)
f@0: * @param editableComponent whether this key listener is for a component
f@0: * that will be editable
f@0: * @return a key listener that utters the letters when typed
f@0: */
f@0: public static KeyListener getSpeechKeyListener(boolean editableComponent){
f@0: return getSpeechKeyListener(editableComponent,false);
f@0: }
f@0:
f@0: public static ItemListener getSpeechComboBoxItemListener(){
f@0: return comboBoxItemListener;
f@0: }
f@0:
f@0: public static ItemListener getCheckBoxSpeechItemListener(){
f@0: return checkBoxItemListener;
f@0: }
f@0:
f@0: public static Action getShutUpAction(){
f@0: return shutUpAction;
f@0: }
f@0:
f@0: /*
f@0: * this class manages the speech feedback when moving around a text component
f@0: * with the up, down, left and right arrows
f@0: */
f@0: private static class SpeechKeyListener extends KeyAdapter{
f@0: boolean isTab;
f@0: boolean isBeginning;
f@0: boolean isFirstLine;
f@0: boolean isLastLine;
f@0: boolean editableComponent;
f@0: int voice;
f@0:
f@0: SpeechKeyListener(boolean editablecomponent, boolean useSecondVoice){
f@0: this.editableComponent = editablecomponent;
f@0: voice = useSecondVoice ? Narrator.SECOND_VOICE : Narrator.FIRST_VOICE;
f@0: }
f@0:
f@0: @Override
f@0: public void keyTyped(KeyEvent evt){
f@0: /* this will manage digit or letter characters */
f@0: if(!isTab && !evt.isControlDown() && editableComponent){
f@0: if(Character.isLetterOrDigit(evt.getKeyChar())){
f@0: NarratorFactory.getInstance().speak(String.valueOf(evt.getKeyChar()),voice);
f@0: }else{
f@0: /* this will manage special characters with a letter representation */
f@0: switch(evt.getKeyChar()){
f@0: case '\n' :
f@0: if(!(evt.getSource() instanceof JTextField))
f@0: NarratorFactory.getInstance().speak(resources.getString("char.new_line"),voice);
f@0: break;
f@0: case ' ' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.space"),voice);
f@0: break;
f@0: case '@' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.at"),voice);
f@0: break;
f@0: case '*' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.asterisk"),voice);
f@0: break;
f@0: case '$' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.dollar"),voice);
f@0: break;
f@0: case '.' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.dot"),voice);
f@0: break;
f@0: case ',' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.comma"),voice);
f@0: break;
f@0: case ';' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.semi_colon"),voice);
f@0: break;
f@0: case ':' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.colon"),voice);
f@0: break;
f@0: case '<' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.lower_than"),voice);
f@0: break;
f@0: case '>' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.greater_than"),voice);
f@0: break;
f@0: case '#' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.sharp"),voice);
f@0: break;
f@0: case '~' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.tilde"),voice);
f@0: break;
f@0: case '+' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.plus"),voice);
f@0: break;
f@0: case '-' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.dash"),voice);
f@0: break;
f@0: case '_' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.underscore"),voice);
f@0: break;
f@0: case '/' :
f@0: NarratorFactory.getInstance().speak(resources.getString("char.slash"),voice);
f@0: break;
f@0: }
f@0: }
f@0: }
f@0: isTab = false;
f@0: }
f@0:
f@0: /* manages all the non digit or letter characters */
f@0: @Override
f@0: public void keyPressed(KeyEvent e){
f@0: int caretPos = ((JTextComponent)e.getSource()).getCaretPosition();
f@0: String text = ((JTextComponent)e.getSource()).getText();
f@0:
f@0: if (e.getKeyCode() == KeyEvent.VK_TAB){
f@0: isTab = true;
f@0: }
f@0: if(caretPos == 0)
f@0: isBeginning = true;
f@0: else
f@0: isBeginning = false;
f@0:
f@0: isFirstLine = true;
f@0: for(int i=0; i