view java/src/uk/ac/qmul/eecs/ccmi/speech/SpeechUtilities.java @ 0:9418ab7b7f3f

Initial import
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Fri, 16 Dec 2011 17:35:51 +0000
parents
children 9e67171477bc
line wrap: on
line source
/*  
 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
  
 Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/  

package uk.ac.qmul.eecs.ccmi.speech;

import java.awt.AWTKeyStroke;
import java.awt.Component;
import java.awt.Container;
import java.awt.FocusTraversalPolicy;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ResourceBundle;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;

import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;

/**
 * A class providing static utilities methods concerning the text to speech synthesis. 
 *
 */
public abstract class SpeechUtilities {
	/* this class is of static use only */
	private SpeechUtilities(){}
	
	public static String getComponentSpeech(Component c){
		StringBuilder b = new StringBuilder();
		if(c.getAccessibleContext().getAccessibleName() != null)
			b.append(c.getAccessibleContext().getAccessibleName());
		if(c instanceof JButton)
			b.append(' ').append(resources.getString("component.button"));
		else if(c instanceof JTextField){
			b.append(' ').append(resources.getString("component.text_field"));
			b.append(((JTextField)c).getText());
		}else if(c instanceof JTextArea){
			b.append(' ').append(resources.getString("component.text_area"));
			b.append(((JTextArea)c).getText());
		}else if(c instanceof JComboBox){
			b.append(((JComboBox)c).getSelectedItem().toString());
			b.append(' ').append(resources.getString("component.combo_box"));
		}else if(c instanceof JCheckBox){
			b.append(' ').append(((JCheckBox)c).isSelected() ? resources.getString("component.chech") : resources.getString("component.uncheck"));
		}else if(c instanceof JSpinner){
			b.append(' ').append(resources.getString("component.spinner"));
			b.append(((JSpinner)c).getValue());
		}else{
			b.append(' ').append(c.getAccessibleContext().getAccessibleRole());
		}
		return b.toString();
	}
	
	@SuppressWarnings("serial")
	public static void changeTabListener(JComponent component, final Container container){
		/* remove the default tab traversal key from all the containers */
		disableTraversalKey(component);
		/* get the look and feel default keys for moving the focus on (usually = TAB) */
		for(AWTKeyStroke keyStroke : KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS))
			component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(keyStroke.getKeyCode(),keyStroke.getModifiers()),"tab");
		
		/* add action to the moving focus keys: reproduce focus system and add speech to it */
		component.getActionMap().put("tab", new AbstractAction(){
			@Override
			public void actionPerformed(ActionEvent evt) {
				FocusTraversalPolicy policy = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalPolicy();
				Component next = policy.getComponentAfter(container, KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner());
				next.requestFocusInWindow();
				NarratorFactory.getInstance().speak(SpeechUtilities.getComponentSpeech(next));
			}
		});
		
		for(AWTKeyStroke keyStroke : KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS))
			component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(keyStroke.getKeyCode(),keyStroke.getModifiers()),"back_tab");
		
		component.getActionMap().put("back_tab", new AbstractAction(){
			@Override
			public void actionPerformed(ActionEvent evt) {
				FocusTraversalPolicy policy = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalPolicy();
				Component previous = policy.getComponentBefore(container, KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner());
				previous.requestFocusInWindow();
				NarratorFactory.getInstance().speak(SpeechUtilities.getComponentSpeech(previous));
			}
		});
	}
	
	private static void disableTraversalKey(Container container){
		for(final Component c : container.getComponents()){
			if(c instanceof Container){
				c.setFocusTraversalKeysEnabled(false);
				disableTraversalKey((Container)c);
			}
		}
	}
	
	public static KeyListener getSpeechKeyListener(boolean editableComponent){
		if(!editableComponent)
			return new SpeechKeyListener(false);
		return speechKeyListener;
	}
	
	public static ItemListener getSpeechComboBoxItemListener(){
		return comboBoxItemListener;
	}
	
	public static ItemListener getCheckBoxSpeechItemListener(){
		return checkBoxItemListener;
	}
	
	public static FocusListener getFocusSpeechListener(){
		return focusListener;
	}
	
	public static Action getShutUpAction(){
		return shutUpAction;
	}
	
	/*
	 * this class manages the speech feedback when moving around a text component
	 * with the up, down, left and right arrows
	 */
	private static class SpeechKeyListener extends KeyAdapter{
		boolean isTab;
		boolean isBeginning;
		boolean isFirstLine;
		boolean isLastLine;
		boolean editableComponent;
		
		SpeechKeyListener(boolean editablecomponent){
			this.editableComponent = editablecomponent;
		}
		
		@Override
		public void keyTyped(KeyEvent evt){
			/* this will manage digit or letter characters */
			if(!isTab && !evt.isControlDown() && editableComponent){
				if(Character.isLetterOrDigit(evt.getKeyChar())){
					NarratorFactory.getInstance().speak(String.valueOf(evt.getKeyChar()));
				}else{
					/* this will manage special characters with a letter representation */
					switch(evt.getKeyChar()){
					case '\n' :
						if(!(evt.getSource() instanceof JTextField))
							NarratorFactory.getInstance().speak(resources.getString("char.new_line"));
						break;
					case ' ' :
						NarratorFactory.getInstance().speak(resources.getString("char.space"));
						break;	
					case '@' : 
						NarratorFactory.getInstance().speak(resources.getString("char.at"));
						break;
					case '.' :
						NarratorFactory.getInstance().speak(resources.getString("char.dot"));
						break;
					case ',' :
						NarratorFactory.getInstance().speak(resources.getString("char.comma"));
						break;
					case ';' :
						NarratorFactory.getInstance().speak(resources.getString("char.semi_colon"));
						break;
					case ':' :
						NarratorFactory.getInstance().speak(resources.getString("char.colon"));
						break;	
					case '<' :
						NarratorFactory.getInstance().speak(resources.getString("char.lower_than"));
						break;
					case '>' :
						NarratorFactory.getInstance().speak(resources.getString("char.greater_than"));
						break;
					case '#' :
						NarratorFactory.getInstance().speak(resources.getString("char.sharp"));
						break;
					case '~' : 
						NarratorFactory.getInstance().speak(resources.getString("char.tilde"));
						break;
					case '+' : 
						NarratorFactory.getInstance().speak(resources.getString("char.plus"));
						break;
					case '-' : 
						NarratorFactory.getInstance().speak(resources.getString("char.dash"));
						break;
					case '_' : 
						NarratorFactory.getInstance().speak(resources.getString("char.underscore"));
						break;	
					case '/' :
						NarratorFactory.getInstance().speak(resources.getString("char.slash"));
						break;
					}
				}
		 	}
			isTab = false;
		}
		
		/* manages all the non digit or letter characters */
		@Override
		public void keyPressed(KeyEvent e){
			int caretPos = ((JTextComponent)e.getSource()).getCaretPosition();
			String text = ((JTextComponent)e.getSource()).getText();
			
			if (e.getKeyCode() == KeyEvent.VK_TAB){
				isTab = true;
			}
			if(caretPos == 0)
				isBeginning = true;
			else
				isBeginning = false;
			
			isFirstLine = true;
			for(int i=0; i<caretPos;i++){
				if(text.charAt(i) == '\n'){
					isFirstLine = false;
					break;
				}
			}
			
			if(text.indexOf('\n', caretPos) == -1)
				isLastLine = true;
			else
				isLastLine = false;
		}
		
		@Override
		public void keyReleased(KeyEvent evt){
			JTextComponent textComponent = (JTextComponent)evt.getSource();
			String text;
			int begin,end,caretPos;
			
			switch(evt.getKeyCode()){
			case KeyEvent.VK_BACK_SPACE:
				NarratorFactory.getInstance().speak(resources.getString("char.back_space"));
				break;
			case KeyEvent.VK_DELETE :
				NarratorFactory.getInstance().speak(resources.getString("char.delete"));
				break;
			case KeyEvent.VK_LEFT :
			case KeyEvent.VK_RIGHT :
				try {
					if(evt.getKeyCode() == KeyEvent.VK_LEFT){ //left
						if(textComponent.getCaretPosition() == 0 && isBeginning){
							SoundFactory.getInstance().play(SoundEvent.ERROR);
							return;
						}
					}else{ // right
						if(textComponent.getCaretPosition() == textComponent.getText().length()){
							SoundFactory.getInstance().play(SoundEvent.ERROR);
							return;
						}
					}
					NarratorFactory.getInstance().speak(textComponent.getText(textComponent.getCaretPosition(),1));
				} catch (BadLocationException e1) {
					e1.printStackTrace();
				}
				break;
			case KeyEvent.VK_UP :
				/* when moving up and down, the line we land on is spoken out (the whole line). If the border
				 * (top/bottom most line) is reached then the error sound is played. on a JTextField this
				 * is the default behaviour with up and down keys as we only have one line
				 */	
				
				if(isFirstLine){//we're on the first line and cannot go any upper
					SoundFactory.getInstance().play(SoundEvent.ERROR);
					return;
				}
				
				text = textComponent.getText();
				caretPos = textComponent.getCaretPosition();
				
				/* look for the beginning of the row the cursor is */
				begin = 0;
				for(int i=0; i<caretPos;i++){
					if(text.charAt(i) == '\n')
						begin = i+1;
				}
				
				/* now the end */
				end = text.indexOf('\n', caretPos);
				
				if(end == begin)//in case it's an empty line
					end++;
				NarratorFactory.getInstance().speak(text.substring(begin, end));
				break;
			case KeyEvent.VK_DOWN :
				if(isLastLine){ //no new line we either have one line only or sit on the last one
					SoundFactory.getInstance().play(SoundEvent.ERROR);
					return;
				}
				
				text = textComponent.getText();
				caretPos = textComponent.getCaretPosition();
				
				begin = 0;
				for(int i=0;i<caretPos;i++){
					if(text.charAt(i) == '\n')
						begin = i+1;
				}
				begin = Math.min(begin, text.length()-1);
				
				end = text.indexOf('\n', begin);
				if(end == -1) // the line we're looking for is the last one 
					end = text.length()-1;
				
				if(end == begin) // in case it's an empty line
					end++;
				NarratorFactory.getInstance().speak(text.substring(begin, end));
				break;
			}
		}
	}
	
	private static final ResourceBundle resources = ResourceBundle.getBundle(Narrator.class.getName());
	private static final SpeechKeyListener speechKeyListener = new SpeechKeyListener(true);
	private static final ItemListener comboBoxItemListener = new ItemListener(){
		@Override
		public void itemStateChanged(ItemEvent evt) {
			if(evt.getStateChange() == ItemEvent.SELECTED)
				NarratorFactory.getInstance().speak(evt.getItem().toString());
		}
	};
	
	private static final ItemListener checkBoxItemListener = new ItemListener(){
		@Override
		public void itemStateChanged(ItemEvent evt) {
			NarratorFactory.getInstance().speak(getComponentSpeech(((JCheckBox)evt.getItemSelectable())));
		}
	};
	
	private static final FocusListener focusListener = new FocusAdapter(){
		public void focusGained(FocusEvent evt){
			NarratorFactory.getInstance().speak(getComponentSpeech(evt.getComponent()));
		}
	};
	
	@SuppressWarnings("serial")
	private static final Action shutUpAction = new AbstractAction(){
		@Override
		public void actionPerformed(ActionEvent e) {
			NarratorFactory.getInstance().shutUp();
		}
	};
}