annotate java/src/uk/ac/qmul/eecs/ccmi/speech/SpeechUtilities.java @ 8:ea7885bd9bff tip

fixed bug : render solid line as dotted/dashed when moving the stylus from dotted/dashed to solid
author ccmi-guest
date Thu, 03 Jul 2014 16:12:20 +0100
parents 075ae9eb2a40
children
rev   line source
fiore@0 1 /*
fiore@0 2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
fiore@0 3
fiore@0 4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
fiore@0 5
fiore@0 6 This program is free software: you can redistribute it and/or modify
fiore@0 7 it under the terms of the GNU General Public License as published by
fiore@0 8 the Free Software Foundation, either version 3 of the License, or
fiore@0 9 (at your option) any later version.
fiore@0 10
fiore@0 11 This program is distributed in the hope that it will be useful,
fiore@0 12 but WITHOUT ANY WARRANTY; without even the implied warranty of
fiore@0 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
fiore@0 14 GNU General Public License for more details.
fiore@0 15
fiore@0 16 You should have received a copy of the GNU General Public License
fiore@0 17 along with this program. If not, see <http://www.gnu.org/licenses/>.
fiore@0 18 */
fiore@0 19
fiore@0 20 package uk.ac.qmul.eecs.ccmi.speech;
fiore@0 21
fiore@0 22 import java.awt.AWTKeyStroke;
fiore@0 23 import java.awt.Component;
fiore@0 24 import java.awt.Container;
fiore@0 25 import java.awt.FocusTraversalPolicy;
fiore@0 26 import java.awt.KeyboardFocusManager;
fiore@0 27 import java.awt.event.ActionEvent;
fiore@0 28 import java.awt.event.ItemEvent;
fiore@0 29 import java.awt.event.ItemListener;
fiore@0 30 import java.awt.event.KeyAdapter;
fiore@0 31 import java.awt.event.KeyEvent;
fiore@0 32 import java.awt.event.KeyListener;
fiore@0 33 import java.util.ResourceBundle;
fiore@0 34
fiore@0 35 import javax.swing.AbstractAction;
fiore@0 36 import javax.swing.Action;
fiore@0 37 import javax.swing.JButton;
fiore@0 38 import javax.swing.JCheckBox;
fiore@0 39 import javax.swing.JComboBox;
fiore@0 40 import javax.swing.JComponent;
fiore@0 41 import javax.swing.JSpinner;
fiore@3 42 import javax.swing.JTabbedPane;
fiore@0 43 import javax.swing.JTextArea;
fiore@0 44 import javax.swing.JTextField;
fiore@3 45 import javax.swing.JTree;
fiore@0 46 import javax.swing.KeyStroke;
fiore@0 47 import javax.swing.text.BadLocationException;
fiore@0 48 import javax.swing.text.JTextComponent;
fiore@0 49
fiore@0 50 import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
fiore@0 51 import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
fiore@3 52 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
fiore@0 53
fiore@0 54 /**
fiore@0 55 * A class providing static utilities methods concerning the text to speech synthesis.
fiore@0 56 *
fiore@0 57 */
fiore@0 58 public abstract class SpeechUtilities {
fiore@0 59 /* this class is of static use only */
fiore@0 60 private SpeechUtilities(){}
fiore@0 61
fiore@0 62 public static String getComponentSpeech(Component c){
fiore@0 63 StringBuilder b = new StringBuilder();
fiore@0 64 if(c.getAccessibleContext().getAccessibleName() != null)
fiore@0 65 b.append(c.getAccessibleContext().getAccessibleName());
fiore@0 66 if(c instanceof JButton)
fiore@0 67 b.append(' ').append(resources.getString("component.button"));
fiore@0 68 else if(c instanceof JTextField){
fiore@0 69 b.append(' ').append(resources.getString("component.text_field"));
fiore@0 70 b.append(((JTextField)c).getText());
fiore@0 71 }else if(c instanceof JTextArea){
fiore@0 72 b.append(' ').append(resources.getString("component.text_area"));
fiore@0 73 b.append(((JTextArea)c).getText());
fiore@0 74 }else if(c instanceof JComboBox){
fiore@0 75 b.append(((JComboBox)c).getSelectedItem().toString());
fiore@0 76 b.append(' ').append(resources.getString("component.combo_box"));
fiore@0 77 }else if(c instanceof JCheckBox){
fiore@0 78 b.append(' ').append(((JCheckBox)c).isSelected() ? resources.getString("component.chech") : resources.getString("component.uncheck"));
fiore@0 79 }else if(c instanceof JSpinner){
fiore@0 80 b.append(' ').append(resources.getString("component.spinner"));
fiore@0 81 b.append(((JSpinner)c).getValue());
fiore@3 82 }else if(c instanceof JTabbedPane){
fiore@3 83 Component comp = ((JTabbedPane)c).getSelectedComponent();
fiore@3 84 if(comp == null)
fiore@3 85 return "";
fiore@3 86 b.append(' ').append( comp.getName());
fiore@3 87 }else if(!(c instanceof JTree)){
fiore@0 88 b.append(' ').append(c.getAccessibleContext().getAccessibleRole());
fiore@0 89 }
fiore@0 90 return b.toString();
fiore@0 91 }
fiore@0 92
fiore@0 93 @SuppressWarnings("serial")
fiore@0 94 public static void changeTabListener(JComponent component, final Container container){
fiore@0 95 /* remove the default tab traversal key from all the containers */
fiore@0 96 disableTraversalKey(component);
fiore@0 97 /* get the look and feel default keys for moving the focus on (usually = TAB) */
fiore@0 98 for(AWTKeyStroke keyStroke : KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS))
fiore@0 99 component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(keyStroke.getKeyCode(),keyStroke.getModifiers()),"tab");
fiore@0 100
fiore@0 101 /* add action to the moving focus keys: reproduce focus system and add speech to it */
fiore@0 102 component.getActionMap().put("tab", new AbstractAction(){
fiore@0 103 @Override
fiore@0 104 public void actionPerformed(ActionEvent evt) {
fiore@0 105 FocusTraversalPolicy policy = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalPolicy();
fiore@0 106 Component next = policy.getComponentAfter(container, KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner());
fiore@7 107 if(next == null)
fiore@7 108 return;
fiore@0 109 next.requestFocusInWindow();
fiore@0 110 NarratorFactory.getInstance().speak(SpeechUtilities.getComponentSpeech(next));
fiore@3 111 InteractionLog.log("TABBED PANE","change focus ",next.getAccessibleContext().getAccessibleName());
fiore@0 112 }
fiore@0 113 });
fiore@0 114
fiore@0 115 for(AWTKeyStroke keyStroke : KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS))
fiore@0 116 component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(keyStroke.getKeyCode(),keyStroke.getModifiers()),"back_tab");
fiore@0 117
fiore@0 118 component.getActionMap().put("back_tab", new AbstractAction(){
fiore@0 119 @Override
fiore@0 120 public void actionPerformed(ActionEvent evt) {
fiore@0 121 FocusTraversalPolicy policy = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalPolicy();
fiore@0 122 Component previous = policy.getComponentBefore(container, KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner());
fiore@7 123 if(previous == null)
fiore@7 124 return;
fiore@0 125 previous.requestFocusInWindow();
fiore@0 126 NarratorFactory.getInstance().speak(SpeechUtilities.getComponentSpeech(previous));
fiore@3 127 InteractionLog.log("TABBED PANE","change focus ",previous.getAccessibleContext().getAccessibleName());
fiore@0 128 }
fiore@0 129 });
fiore@3 130
fiore@3 131 /* shut up the narrator upon pressing ctrl */
fiore@3 132 // component.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"ctrldown");
fiore@3 133 // component.getActionMap().put("ctrldown",new AbstractAction(){
fiore@3 134 // public void actionPerformed(ActionEvent evt){
fiore@3 135 // NarratorFactory.getInstance().shutUp();
fiore@3 136 // }
fiore@3 137 // });
fiore@0 138 }
fiore@0 139
fiore@0 140 private static void disableTraversalKey(Container container){
fiore@0 141 for(final Component c : container.getComponents()){
fiore@0 142 if(c instanceof Container){
fiore@0 143 c.setFocusTraversalKeysEnabled(false);
fiore@0 144 disableTraversalKey((Container)c);
fiore@0 145 }
fiore@0 146 }
fiore@0 147 }
fiore@0 148
fiore@3 149 public static KeyListener getSpeechKeyListener(boolean editableComponent, boolean secondVoice){
fiore@3 150 if(!editableComponent)
fiore@3 151 return new SpeechKeyListener(false,secondVoice);
fiore@3 152 return speechKeyListener;
fiore@3 153 }
fiore@3 154
fiore@3 155 /**
fiore@3 156 * Returns a {@code speechKeyListener} using first voice (default)
fiore@5 157 * @param editableComponent whether this key listener is for a component
fiore@5 158 * that will be editable
fiore@5 159 * @return a key listener that utters the letters when typed
fiore@3 160 */
fiore@0 161 public static KeyListener getSpeechKeyListener(boolean editableComponent){
fiore@3 162 return getSpeechKeyListener(editableComponent,false);
fiore@0 163 }
fiore@0 164
fiore@0 165 public static ItemListener getSpeechComboBoxItemListener(){
fiore@0 166 return comboBoxItemListener;
fiore@0 167 }
fiore@0 168
fiore@0 169 public static ItemListener getCheckBoxSpeechItemListener(){
fiore@0 170 return checkBoxItemListener;
fiore@0 171 }
fiore@0 172
fiore@0 173 public static Action getShutUpAction(){
fiore@0 174 return shutUpAction;
fiore@0 175 }
fiore@0 176
fiore@0 177 /*
fiore@0 178 * this class manages the speech feedback when moving around a text component
fiore@0 179 * with the up, down, left and right arrows
fiore@0 180 */
fiore@0 181 private static class SpeechKeyListener extends KeyAdapter{
fiore@0 182 boolean isTab;
fiore@0 183 boolean isBeginning;
fiore@0 184 boolean isFirstLine;
fiore@0 185 boolean isLastLine;
fiore@0 186 boolean editableComponent;
fiore@3 187 int voice;
fiore@0 188
fiore@3 189 SpeechKeyListener(boolean editablecomponent, boolean useSecondVoice){
fiore@0 190 this.editableComponent = editablecomponent;
fiore@3 191 voice = useSecondVoice ? Narrator.SECOND_VOICE : Narrator.FIRST_VOICE;
fiore@0 192 }
fiore@0 193
fiore@0 194 @Override
fiore@0 195 public void keyTyped(KeyEvent evt){
fiore@0 196 /* this will manage digit or letter characters */
fiore@0 197 if(!isTab && !evt.isControlDown() && editableComponent){
fiore@0 198 if(Character.isLetterOrDigit(evt.getKeyChar())){
fiore@3 199 NarratorFactory.getInstance().speak(String.valueOf(evt.getKeyChar()),voice);
fiore@0 200 }else{
fiore@0 201 /* this will manage special characters with a letter representation */
fiore@0 202 switch(evt.getKeyChar()){
fiore@0 203 case '\n' :
fiore@0 204 if(!(evt.getSource() instanceof JTextField))
fiore@3 205 NarratorFactory.getInstance().speak(resources.getString("char.new_line"),voice);
fiore@0 206 break;
fiore@0 207 case ' ' :
fiore@3 208 NarratorFactory.getInstance().speak(resources.getString("char.space"),voice);
fiore@0 209 break;
fiore@0 210 case '@' :
fiore@3 211 NarratorFactory.getInstance().speak(resources.getString("char.at"),voice);
fiore@0 212 break;
fiore@3 213 case '*' :
fiore@3 214 NarratorFactory.getInstance().speak(resources.getString("char.asterisk"),voice);
fiore@3 215 break;
fiore@3 216 case '$' :
fiore@3 217 NarratorFactory.getInstance().speak(resources.getString("char.dollar"),voice);
fiore@3 218 break;
fiore@0 219 case '.' :
fiore@3 220 NarratorFactory.getInstance().speak(resources.getString("char.dot"),voice);
fiore@0 221 break;
fiore@0 222 case ',' :
fiore@3 223 NarratorFactory.getInstance().speak(resources.getString("char.comma"),voice);
fiore@0 224 break;
fiore@0 225 case ';' :
fiore@3 226 NarratorFactory.getInstance().speak(resources.getString("char.semi_colon"),voice);
fiore@0 227 break;
fiore@0 228 case ':' :
fiore@3 229 NarratorFactory.getInstance().speak(resources.getString("char.colon"),voice);
fiore@0 230 break;
fiore@0 231 case '<' :
fiore@3 232 NarratorFactory.getInstance().speak(resources.getString("char.lower_than"),voice);
fiore@0 233 break;
fiore@0 234 case '>' :
fiore@3 235 NarratorFactory.getInstance().speak(resources.getString("char.greater_than"),voice);
fiore@0 236 break;
fiore@0 237 case '#' :
fiore@3 238 NarratorFactory.getInstance().speak(resources.getString("char.sharp"),voice);
fiore@0 239 break;
fiore@0 240 case '~' :
fiore@3 241 NarratorFactory.getInstance().speak(resources.getString("char.tilde"),voice);
fiore@0 242 break;
fiore@0 243 case '+' :
fiore@3 244 NarratorFactory.getInstance().speak(resources.getString("char.plus"),voice);
fiore@0 245 break;
fiore@0 246 case '-' :
fiore@3 247 NarratorFactory.getInstance().speak(resources.getString("char.dash"),voice);
fiore@0 248 break;
fiore@0 249 case '_' :
fiore@3 250 NarratorFactory.getInstance().speak(resources.getString("char.underscore"),voice);
fiore@0 251 break;
fiore@0 252 case '/' :
fiore@3 253 NarratorFactory.getInstance().speak(resources.getString("char.slash"),voice);
fiore@0 254 break;
fiore@0 255 }
fiore@0 256 }
fiore@0 257 }
fiore@0 258 isTab = false;
fiore@0 259 }
fiore@0 260
fiore@0 261 /* manages all the non digit or letter characters */
fiore@0 262 @Override
fiore@0 263 public void keyPressed(KeyEvent e){
fiore@0 264 int caretPos = ((JTextComponent)e.getSource()).getCaretPosition();
fiore@0 265 String text = ((JTextComponent)e.getSource()).getText();
fiore@0 266
fiore@0 267 if (e.getKeyCode() == KeyEvent.VK_TAB){
fiore@0 268 isTab = true;
fiore@0 269 }
fiore@0 270 if(caretPos == 0)
fiore@0 271 isBeginning = true;
fiore@0 272 else
fiore@0 273 isBeginning = false;
fiore@0 274
fiore@0 275 isFirstLine = true;
fiore@0 276 for(int i=0; i<caretPos;i++){
fiore@0 277 if(text.charAt(i) == '\n'){
fiore@0 278 isFirstLine = false;
fiore@0 279 break;
fiore@0 280 }
fiore@0 281 }
fiore@0 282
fiore@0 283 if(text.indexOf('\n', caretPos) == -1)
fiore@0 284 isLastLine = true;
fiore@0 285 else
fiore@0 286 isLastLine = false;
fiore@0 287 }
fiore@0 288
fiore@0 289 @Override
fiore@0 290 public void keyReleased(KeyEvent evt){
fiore@0 291 JTextComponent textComponent = (JTextComponent)evt.getSource();
fiore@0 292 String text;
fiore@0 293 int begin,end,caretPos;
fiore@0 294
fiore@0 295 switch(evt.getKeyCode()){
fiore@0 296 case KeyEvent.VK_BACK_SPACE:
fiore@3 297 NarratorFactory.getInstance().speak(resources.getString("char.back_space"),voice);
fiore@0 298 break;
fiore@0 299 case KeyEvent.VK_DELETE :
fiore@3 300 NarratorFactory.getInstance().speak(resources.getString("char.delete"),voice);
fiore@0 301 break;
fiore@0 302 case KeyEvent.VK_LEFT :
fiore@0 303 case KeyEvent.VK_RIGHT :
fiore@0 304 try {
fiore@0 305 if(evt.getKeyCode() == KeyEvent.VK_LEFT){ //left
fiore@0 306 if(textComponent.getCaretPosition() == 0 && isBeginning){
fiore@0 307 SoundFactory.getInstance().play(SoundEvent.ERROR);
fiore@0 308 return;
fiore@0 309 }
fiore@0 310 }else{ // right
fiore@0 311 if(textComponent.getCaretPosition() == textComponent.getText().length()){
fiore@0 312 SoundFactory.getInstance().play(SoundEvent.ERROR);
fiore@0 313 return;
fiore@0 314 }
fiore@0 315 }
fiore@3 316 NarratorFactory.getInstance().speak(textComponent.getText(textComponent.getCaretPosition(),1),voice);
fiore@0 317 } catch (BadLocationException e1) {
fiore@0 318 e1.printStackTrace();
fiore@0 319 }
fiore@0 320 break;
fiore@0 321 case KeyEvent.VK_UP :
fiore@0 322 /* when moving up and down, the line we land on is spoken out (the whole line). If the border
fiore@0 323 * (top/bottom most line) is reached then the error sound is played. on a JTextField this
fiore@0 324 * is the default behaviour with up and down keys as we only have one line
fiore@0 325 */
fiore@0 326
fiore@0 327 if(isFirstLine){//we're on the first line and cannot go any upper
fiore@0 328 SoundFactory.getInstance().play(SoundEvent.ERROR);
fiore@0 329 return;
fiore@0 330 }
fiore@0 331
fiore@0 332 text = textComponent.getText();
fiore@0 333 caretPos = textComponent.getCaretPosition();
fiore@0 334
fiore@0 335 /* look for the beginning of the row the cursor is */
fiore@0 336 begin = 0;
fiore@0 337 for(int i=0; i<caretPos;i++){
fiore@0 338 if(text.charAt(i) == '\n')
fiore@0 339 begin = i+1;
fiore@0 340 }
fiore@0 341
fiore@0 342 /* now the end */
fiore@0 343 end = text.indexOf('\n', caretPos);
fiore@0 344
fiore@0 345 if(end == begin)//in case it's an empty line
fiore@0 346 end++;
fiore@3 347 NarratorFactory.getInstance().speak(text.substring(begin, end),voice);
fiore@0 348 break;
fiore@0 349 case KeyEvent.VK_DOWN :
fiore@0 350 if(isLastLine){ //no new line we either have one line only or sit on the last one
fiore@0 351 SoundFactory.getInstance().play(SoundEvent.ERROR);
fiore@0 352 return;
fiore@0 353 }
fiore@0 354
fiore@0 355 text = textComponent.getText();
fiore@0 356 caretPos = textComponent.getCaretPosition();
fiore@0 357
fiore@0 358 begin = 0;
fiore@0 359 for(int i=0;i<caretPos;i++){
fiore@0 360 if(text.charAt(i) == '\n')
fiore@0 361 begin = i+1;
fiore@0 362 }
fiore@0 363 begin = Math.min(begin, text.length()-1);
fiore@0 364
fiore@0 365 end = text.indexOf('\n', begin);
fiore@0 366 if(end == -1) // the line we're looking for is the last one
fiore@0 367 end = text.length()-1;
fiore@0 368
fiore@0 369 if(end == begin) // in case it's an empty line
fiore@0 370 end++;
fiore@3 371 NarratorFactory.getInstance().speak(text.substring(begin, end),voice);
fiore@0 372 break;
fiore@0 373 }
fiore@0 374 }
fiore@0 375 }
fiore@0 376
fiore@0 377 private static final ResourceBundle resources = ResourceBundle.getBundle(Narrator.class.getName());
fiore@3 378 private static final SpeechKeyListener speechKeyListener = new SpeechKeyListener(true,false);
fiore@0 379 private static final ItemListener comboBoxItemListener = new ItemListener(){
fiore@0 380 @Override
fiore@0 381 public void itemStateChanged(ItemEvent evt) {
fiore@0 382 if(evt.getStateChange() == ItemEvent.SELECTED)
fiore@0 383 NarratorFactory.getInstance().speak(evt.getItem().toString());
fiore@0 384 }
fiore@0 385 };
fiore@0 386
fiore@0 387 private static final ItemListener checkBoxItemListener = new ItemListener(){
fiore@0 388 @Override
fiore@0 389 public void itemStateChanged(ItemEvent evt) {
fiore@0 390 NarratorFactory.getInstance().speak(getComponentSpeech(((JCheckBox)evt.getItemSelectable())));
fiore@0 391 }
fiore@0 392 };
fiore@0 393
fiore@0 394 @SuppressWarnings("serial")
fiore@0 395 private static final Action shutUpAction = new AbstractAction(){
fiore@0 396 @Override
fiore@0 397 public void actionPerformed(ActionEvent e) {
fiore@0 398 NarratorFactory.getInstance().shutUp();
fiore@0 399 }
fiore@0 400 };
fiore@0 401 }