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