view java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechMenuFactory.java @ 0:78b7fc5391a2

first import, outcome of NIME 2014 hackaton
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 08 Jul 2014 16:28:59 +0100
parents
children
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.gui;

import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.text.MessageFormat;
import java.util.ResourceBundle;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ComponentInputMap;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;

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

/*
 * This class provides a version of JMenuItem, JCheckBox, and JMenu which emit an "error" sound when 
 * they're disabled and the user tries to use it by an accelerator
 */
@SuppressWarnings("serial")
class SpeechMenuFactory extends JMenu {

	/* implements the singleton pattern and keeps a static reference to the menuBar used by JMenuItems and JMenus */
	public static JMenuBar getMenuBar(){
		if(menuBar == null){
			menuBar = new SpeechMenuBar();
		}
		return menuBar;
	}
	
	public static JMenuItem getMenuItem(String text){
		return new SpeechMenuItem(text);
	}
	
	public static JMenu getMenu(String text){
		return new JMenu(text){
			@Override
			public void menuSelectionChanged(boolean isIncluded){
				  super.menuSelectionChanged(isIncluded);
				  if(isIncluded && !wasMouse){
					  String menuType = resources.getString("menufactory.menu");
					  if(getMenuBar().getComponentIndex(this) == -1){
						  menuType = resources.getString("menufactory.submenu");
					  };
					  NarratorFactory.getInstance().speak(getAccessibleContext().getAccessibleName()+ " "+ menuType);
				  }
				  wasMouse = false;
			}
			
			@Override
			public void processMouseEvent(MouseEvent e){
				wasMouse = true;
				super.processMouseEvent(e);
			}
			
			private boolean wasMouse = false;
		};
	}
	
	public static JCheckBoxMenuItem getJCheckBoxMenuItem(String text){
		return new SpeechJCheckBoxMenuItem(text);
	}
	
	/* this action is called when the user strokes a disabled menu accelerator */
	private static Action errorAction = new AbstractAction(){
		@Override
		public void actionPerformed(ActionEvent e) {
			SoundFactory.getInstance().play(SoundEvent.ERROR);
		}
	};
	
	private static class SpeechMenuBar extends JMenuBar{
		SpeechMenuBar() {
			setInputMap(SpeechMenuBar.WHEN_IN_FOCUSED_WINDOW, new ComponentInputMap(this){
				@Override
				public Object get(KeyStroke keyStroke){
					if(keyStroke == null) 
						return null;
					return super.get(keyStroke);
				}
			});
		}
		
		@Override
		public void	processKeyEvent(KeyEvent e, MenuElement[] path, MenuSelectionManager manager) {
			super.processKeyEvent(e,path,manager);
			if(e.getKeyCode() == KeyEvent.VK_ESCAPE){
				NarratorFactory.getInstance().speak(resources.getString("menufactory.leaving"));
			}
		}
	}
	
	/*
	 * this class implements a menu item which speaks out its label when 
	 * selected with the keyboard (mouse hover will have no effect)
	 * it needs a reference to the menu bar it's in to implement a respond, with the error sound, to   
	 * the accelerator even when it's disabled (normally no listeners are called otherwise) 
	 * 
	 */
	private static class SpeechMenuItem extends JMenuItem{
		@Override
		public void	processMouseEvent(MouseEvent e ){
			wasMouse = true;
			super.processMouseEvent(e);
		}
		
		public SpeechMenuItem(String text){
			super(text);
			/* bind ACCELERATOR with the error action */
			getMenuBar().getActionMap().put(ACCELERATOR, errorAction);
			wasMouse = false;
			if(text.trim().endsWith("...")){
				/* replace the ... in the accessible name  with the voice version of it */
				String accName = getAccessibleContext().getAccessibleName().replaceAll("...\\s*$", "");
				getAccessibleContext().setAccessibleName(accName +" "+ resources.getString("menufactory.3dot"));
			}
		}
		
		@Override
		public void menuSelectionChanged(boolean isIncluded){
			  super.menuSelectionChanged(isIncluded);
			  if(isIncluded && !wasMouse){
				  String disabled = isEnabled() ? "" : resources.getString("menufactory.disabled");
				  String accelerator = "";
				  if(getAccelerator() != null){
					  accelerator = resources.getString("menufactory.ctrl")+" "+ getAccelerator().toString().substring(getAccelerator().toString().lastIndexOf(' '));
				  }
				  NarratorFactory.getInstance().speak(getAccessibleContext().getAccessibleName()+disabled+accelerator);
			  }
			  wasMouse = false;
		}
		
		@Override
		public void setEnabled(boolean b){
			super.setEnabled(b);
			if(getAccelerator() == null)
				return;
			if(b == false){
				/* if the menu item gets disabled, then set up an action in the menuBar to respond to 
				 * the accelerator key stroke, so that the user gets a feedback (error sound) 
				 * even though the listeners don't get called (as the menu item is disabled) 
				 */
				getMenuBar().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(getAccelerator(), ACCELERATOR);
			}else{
				getMenuBar().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(getAccelerator(), "none");
			}
		}
		
		private boolean wasMouse;
	};
	
	private static class SpeechJCheckBoxMenuItem extends JCheckBoxMenuItem {
		public SpeechJCheckBoxMenuItem(String text){
			super(text);
			addItemListener(new ItemListener(){
				  @Override
				  public void itemStateChanged(ItemEvent evt) {
					  int stateChange = evt.getStateChange(); 
					  if(stateChange != ItemEvent.SELECTED && stateChange != ItemEvent.DESELECTED){
						  return;
					  }
					  if(!itemChangeMouseFlag){
						  NarratorFactory.getInstance().speak(
								  MessageFormat.format(
										  resources.getString(stateChange == ItemEvent.SELECTED ? "menufactory.selected" : "menufactory.unselected"), 
										  getAccessibleContext().getAccessibleName()));
					  }
					  itemChangeMouseFlag = false;
				  }
			  });
		}
		
		@Override
		public void menuSelectionChanged(boolean isIncluded){
			  super.menuSelectionChanged(isIncluded);
			  if(isIncluded && !selectionMouseFlag ){
				  NarratorFactory.getInstance().speak(
						  MessageFormat.format(
								  resources.getString(isSelected() ? "menufactory.selected" : "menufactory.unselected"), 
								  getAccessibleContext().getAccessibleName()));
			  }
			  selectionMouseFlag = false;
		}
		
		@Override
		public void processMouseEvent(MouseEvent e){
			selectionMouseFlag = true;
			itemChangeMouseFlag = true;
			super.processMouseEvent(e);
		}
		
		private boolean selectionMouseFlag = false;
		private boolean itemChangeMouseFlag = false;
	}
	
	private static JMenuBar menuBar;
	private static ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName());
	private static final String ACCELERATOR = "accelerator";
}