Mercurial > hg > ccmieditor
view java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechOptionPane.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 | d66dd5880081 |
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.Color; import java.awt.Component; import java.awt.Frame; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.text.MessageFormat; import java.util.LinkedHashSet; import java.util.List; import java.util.ResourceBundle; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFormattedTextField; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SwingWorker; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.text.JTextComponent; import uk.ac.qmul.eecs.ccmi.sound.SoundEvent; import uk.ac.qmul.eecs.ccmi.sound.SoundFactory; import uk.ac.qmul.eecs.ccmi.speech.Narrator; import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory; import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities; /** * * An option panel made out of an {@code Object} being displayed and to buttons: one for accepting and another one for * cancelling the option. * Furthermore, this class provides one-line calls to display accessible dialog boxes. Input by the user as well * as focused components are spoken out through text to speech synthesis performed by a {@link Narrator} instance. */ public class SpeechOptionPane { /** * Construct a new {@code SpeechOptionPane} with no title. The title is displayed at the top of the dialog * that is displayed after a call to {@code showDialog} */ public SpeechOptionPane(){ this(""); } /** * Construct a new {@code SpeechOptionPane} with no title. The title is displayed at the top of the dialog * that is displayed after a call to {@code showDialog} * * @param title the String to be displayed */ public SpeechOptionPane(String title){ this.title = title; okButton = new JButton("OK"); cancelButton = new JButton("Cancel"); } /** * Pops the a dialog holding this SpeechOptionPane * * @param parent the parent component of the dialog * @param message the {@code Object} to display * @return an integer indicating the option selected by the user */ @SuppressWarnings("serial") public int showDialog(Component parent,final Object message){ optPane = new JOptionPane(); optPane.setMessage(message); /* Enter will entail a unique action, regardless the component that's focused */ optPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "closeDialog"); optPane.getActionMap().put("closeDialog", new AbstractAction(){ @Override public void actionPerformed(ActionEvent evt) { okButton.doClick(); } }); optPane.setMessageType(JOptionPane.PLAIN_MESSAGE); Object[] options = { okButton, cancelButton }; optPane.setOptions(options); /* ctrl key will hush the TTS */ optPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"shut_up"); optPane.getActionMap().put("shut_up", SpeechUtilities.getShutUpAction()); final JDialog dialog = optPane.createDialog(parent, title); dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); SpeechUtilities.changeTabListener(optPane,dialog); /* when either button is pressed, dialog is disposed and the button itself becomes the optPane.value */ ActionListener buttonListener = new ActionListener(){ @Override public void actionPerformed(ActionEvent evt) { onClose(dialog,(JButton)evt.getSource()); } }; okButton.addActionListener(buttonListener); cancelButton.addActionListener(buttonListener); SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); if(okButton.equals(optPane.getValue())){ return OK_OPTION; }else{ return CANCEL_OPTION; } } /** * Sets the string appearing at the top of the dialog where this option pane is displayed when {@code showDialog} * is called. * @param title the title of this option pane */ public void setDialogTitle(String title){ this.title = title; } /** * Returns the {@code JButton} that the user has to press (when the option pane is displayed after * {@code showDialog} is called) in order to accept the option. * * @return a reference to the internal {@code JButton} */ public JButton getOkButton(){ return okButton; } /** * Returns the {@code JButton} that the user has to press (when the option pane is displayed after * {@code showDialog} is called) in order to reject the option. * * @return a reference to the internal {@code JButton} */ public JButton getCancelButton(){ return cancelButton; } /** * This method is called just after the user presses either button of the dialog displayed * after {@code showDialog} is called. * It assign a value to the return value and it frees the dialog resources. * It can be overwritten by subclasses but care should be taken of calling this class method via * {@code super} in order to properly close the dialog. * * @param dialog the dialog displayed after {@code showDialog} is called. * @param source the button that triggered the closing of {@code dialog} */ protected void onClose(JDialog dialog, JButton source){ optPane.setValue(source); dialog.dispose(); } private String title; private JOptionPane optPane; private JButton okButton; private JButton cancelButton; /* -------- STATIC METHODS ----------- */ /** * Shows a dialog with a text area requesting input for the user. * * @param parentComponent the parent {@code Component} for the dialog * @param message a displayed in the dialog, such text is also uttered by the {@code Narrator} * @param text the initial text the text area contains when the dialog is displayed * * @return the new text entered by the user */ public static String showTextAreaDialog(Component parentComponent, String message, String text){ JTextArea textArea = new JTextArea(NOTES_TEXT_AREA_ROW_SIZE,NOTES_TEXT_AREA_COL_SIZE); textArea.setText(text); NarratorFactory.getInstance().speak(message); return textComponentDialog(parentComponent, message, textArea); } /** * Shows a dialog with a text field requesting input from the user * * @param parentComponent the parent {@code Component} for the dialog * @param message a message displayed in the dialog, such text is also uttered by the {@code Narrator} * @param initialSelectionValue the initial text the text field contains when the dialog is displayed * * @return the text entered by the user */ public static String showInputDialog(Component parentComponent, String message, String initialSelectionValue){ final JTextField textField = new JTextField(initialSelectionValue); textField.selectAll(); NarratorFactory.getInstance().speak(message); return textComponentDialog(parentComponent, message, textField); } private static String textComponentDialog(Component parentComponent, String message, final JTextComponent textComponent){ Object componentToDisplay = textComponent; if(textComponent instanceof JTextArea) componentToDisplay = new JScrollPane(textComponent); Object[] displayObjects = { new JLabel(message), componentToDisplay }; final JOptionPane optPane = new JOptionPane(); optPane.setMessage(displayObjects); optPane.setMessageType(QUESTION_MESSAGE); optPane.setOptionType(OK_CANCEL_OPTION); /* ctrl key will hush the TTS */ optPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"shut_up"); optPane.getActionMap().put("shut_up", SpeechUtilities.getShutUpAction()); final JDialog dialog = optPane.createDialog(parentComponent, resources.getString("dialog.speech_option_pane.input")); if(textComponent instanceof JTextArea) dialog.setResizable(true); dialog.addWindowFocusListener(new WindowAdapter(){ @Override public void windowGainedFocus(WindowEvent e) { textComponent.requestFocusInWindow(); } }); SpeechUtilities.changeTabListener(optPane,dialog); textComponent.addKeyListener(SpeechUtilities.getSpeechKeyListener(true)); textComponent.setEditable(true); // start the editing sound SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); dialog.dispose(); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); if(optPane.getValue() == null)//window closed return null; else if(((Integer)optPane.getValue()).intValue() == CANCEL_OPTION || ((Integer)optPane.getValue()).intValue() == CLOSED_OPTION)//pressed on cancel return null; else{ // pressed on OK return textComponent.getText().trim(); } } /** * Shows a dialog with a {@code JComboBox} requesting selection from the user * * @param parentComponent the parent {@code Component} for the dialog * @param message a message displayed in the dialog, such text is also uttered by the {@code Narrator} * @param options options for the {@code JComboBox} * @param initialValue the options value selected when the dialog is shown * @return the option selected by the user */ public static Object showSelectionDialog(Component parentComponent, String message, Object[] options, Object initialValue){ final LoopComboBox comboBox = new LoopComboBox(options); comboBox.setSelectedItem(initialValue); Object[] displayObjects = { new JLabel(message), comboBox }; JOptionPane optPane = new JOptionPane(); optPane.setMessage(displayObjects); optPane.setMessageType(QUESTION_MESSAGE); optPane.setOptionType(OK_CANCEL_OPTION); /* ctrl key will hush the TTS */ optPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"shut_up"); optPane.getActionMap().put("shut_up", SpeechUtilities.getShutUpAction()); NarratorFactory.getInstance().speak(message+", "+SpeechUtilities.getComponentSpeech(comboBox)); final JDialog dialog = optPane.createDialog(parentComponent, resources.getString("dialog.speech_option_pane.select")); dialog.addWindowFocusListener(new WindowAdapter(){ @Override public void windowGainedFocus(WindowEvent e) { comboBox.requestFocusInWindow(); } }); comboBox.addItemListener(SpeechUtilities.getSpeechComboBoxItemListener()); SpeechUtilities.changeTabListener(optPane,dialog); // start the editing sound SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); dialog.dispose(); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); if(optPane.getValue() == null)//window closed return null; else if(((Integer)optPane.getValue()).intValue() == CANCEL_OPTION || ((Integer)optPane.getValue()).intValue() == CLOSED_OPTION)//pressed on cancel )//pressed on cancel return null; else{ // pressed on OK return comboBox.getSelectedItem(); } } /** * Shows the dialog with a {@code JSpinner} requesting the selection of the speech rate * of the main voice of the {@code Narrator} * * @param parentComponent the parent {@code Component} for the dialog * @param message a message displayed in the dialog, such text is also uttered by the {@code Narrator} * @param value the initial value * @param min the minimum value of the spinner * @param max the maximum value of the spinner * @return the selected integer value or {@code null} if the user cancels the dialog */ public static Integer showNarratorRateDialog(Component parentComponent, String message, int value, int min, int max){ NarratorFactory.getInstance().speak(message); final JSpinner spinner = new JSpinner(new LoopSpinnerNumberModel(value,min,max)); JFormattedTextField tf = ((JSpinner.DefaultEditor)spinner.getEditor()).getTextField(); tf.setEditable(false); tf.setFocusable(false); tf.setBackground(Color.white); Object[] displayObjects = { new JLabel(message), spinner}; final JOptionPane optPane = new JOptionPane(); optPane.setMessage(displayObjects); optPane.setMessageType(QUESTION_MESSAGE); optPane.setOptionType(OK_CANCEL_OPTION); /* ctrl key will hush the TTS */ optPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"shut_up"); optPane.getActionMap().put("shut_up", SpeechUtilities.getShutUpAction()); final JDialog dialog = optPane.createDialog(parentComponent, resources.getString("dialog.speech_option_pane.input")); SpeechUtilities.changeTabListener(optPane,dialog); dialog.addWindowFocusListener(new WindowAdapter(){ @Override public void windowGainedFocus(WindowEvent e) { spinner.requestFocusInWindow(); } }); spinner.addChangeListener(new ChangeListener(){ @Override public void stateChanged(ChangeEvent evt) { JSpinner s = (JSpinner)(evt.getSource()); NarratorFactory.getInstance().setRate((Integer)s.getValue()); NarratorFactory.getInstance().speak(s.getValue().toString()); } }); // start the editing sound SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); dialog.dispose(); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); /* set the speech rate back to the value passed as argument */ NarratorFactory.getInstance().setRate(value); if(optPane.getValue() == null)//window closed return null; else if(((Integer)optPane.getValue()).intValue() == CANCEL_OPTION || ((Integer)optPane.getValue()).intValue() == CLOSED_OPTION)//pressed on cancel return null; else{ // pressed on OK return (Integer)spinner.getValue(); } } /** * Brings up a dialog with selected options requesting user to confirmation. * * @param parentComponent the parent {@code Component} for the dialog * @param message a message displayed in the dialog, such text is also uttered by the {@code Narrator} * @param optionType an integer designating the options available on the dialog * @return an integer indicating the option selected by the user */ public static int showConfirmDialog(Component parentComponent, String message, int optionType){ NarratorFactory.getInstance().speak(message); JOptionPane optPane = new JOptionPane(); optPane.setMessage(message); optPane.setMessageType(QUESTION_MESSAGE); optPane.setOptionType(optionType); /* ctrl key will hush the TTS */ optPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"shut_up"); optPane.getActionMap().put("shut_up", SpeechUtilities.getShutUpAction()); JDialog dialog = optPane.createDialog(parentComponent, resources.getString("dialog.speech_option_pane.confirm")); SpeechUtilities.changeTabListener(optPane,dialog); SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); dialog.dispose(); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); if(optPane.getValue() == null)//window closed return CANCEL_OPTION; else return ((Integer)optPane.getValue()).intValue(); } /** * Displays a message to the user. * * @param parentComponent the parent {@code Component} for the dialog * @param message the message displayed in the dialog, such text is also uttered by the {@code Narrator} * @param messageType the type of message to be displayed */ public static void showMessageDialog(Component parentComponent, String message, int messageType){ NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("dialog.speech_option_pane.message"), message)); JOptionPane optPane = new JOptionPane(); optPane.setMessage(message); optPane.setMessageType(messageType); /* ctrl key will hush the TTS */ optPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"shut_up"); optPane.getActionMap().put("shut_up", SpeechUtilities.getShutUpAction()); JDialog dialog = optPane.createDialog(parentComponent, resources.getString("dialog.speech_option_pane.confirm")); SpeechUtilities.changeTabListener(optPane,dialog); SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); dialog.dispose(); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); } /** * Displays an error message to the user. * * @param parentComponent the parent {@code Component} for the dialog * @param message the message displayed in the dialog, such text is also uttered by the {@code Narrator} */ public static void showMessageDialog(Component parentComponent, String message){ showMessageDialog(parentComponent,message,ERROR_MESSAGE); } /** * Execute a ProgressDialogWorker task and * shows an indeterminate progress bar dialog if the task is not completed after * {@code millisToDecideToPopup}. The user can use the dialog <i>cancel</i> button * to cancel the task. * * @param <T> the result type returned by the worker * @param <V> the intermediate result type of the worker * @param parentComponent the parent {@code Component} for the dialog * @param message message a message displayed in the dialog, such text is also uttered by the {@code Narrator} * @param worker a {@code ProgressDialogWorker} that is executed when this method is called * @param millisToDecideToPopup the millisecond to let to the worker before popping the dialog up * @return an integer indicating whether the task was completed or it was interrupted by the user */ public static <T,V> int showProgressDialog(Component parentComponent, String message,final ProgressDialogWorker<T,V> worker, int millisToDecideToPopup){ JProgressBar progressBar = worker.bar; Object displayObjects[] = {message, progressBar}; final JOptionPane optPane = new JOptionPane(displayObjects); optPane.setOptionType(DEFAULT_OPTION); optPane.setOptions(new Object[] {PROGRESS_DIALOG_CANCEL_OPTION}); /* ctrl key will hush the TTS */ optPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"shut_up"); optPane.getActionMap().put("shut_up", SpeechUtilities.getShutUpAction()); final JDialog dialog = optPane.createDialog(parentComponent, resources.getString("dialog.speech_option_pane.download")); SpeechUtilities.changeTabListener(optPane,dialog); worker.setDialog(dialog); worker.execute(); try { Thread.sleep(millisToDecideToPopup); } catch (InterruptedException ie) { throw new RuntimeException(ie); //should never happen } if(worker.isDone()) return OK_OPTION; NarratorFactory.getInstance().speak(message); SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); if( optPane.getValue() == null || optPane.getValue() == PROGRESS_DIALOG_CANCEL_OPTION || Integer.valueOf(CLOSED_OPTION).equals(optPane.getValue())){ worker.cancel(true); return CANCEL_OPTION; } return OK_OPTION; } /** * Shows a check box dialog to select the modifiers of a given property * * @param parentComponent the parent {@code Component} for the dialog * @param message message a message displayed in the dialog, such text is also uttered by the {@code Narrator} * @param modifierTypes the different types of modifiers that are available for a given property * @param modifierIndexes the initial selection of modifiers as the dialog is shown * @return a new set with the modifier indexes selected by the user */ public static Set<Integer> showModifiersDialog(Component parentComponent, String message, List<String> modifierTypes, Set<Integer> modifierIndexes){ JOptionPane optPane = new JOptionPane(); JPanel checkBoxPanel = new JPanel(new GridLayout(0, 1)); final JCheckBox[] checkBoxes = new JCheckBox[modifierTypes.size()]; for(int i=0;i<checkBoxes.length;i++){ checkBoxes[i] = new JCheckBox(modifierTypes.get(i)); if(modifierIndexes.contains(i)) checkBoxes[i].setSelected(true); checkBoxPanel.add(checkBoxes[i]); checkBoxes[i].addItemListener(SpeechUtilities.getCheckBoxSpeechItemListener()); } NarratorFactory.getInstance().speak(message+" "+SpeechUtilities.getComponentSpeech(checkBoxes[0])); Object[] displayObjects = {new JLabel(message),checkBoxPanel}; optPane.setMessage(displayObjects); optPane.setMessageType(QUESTION_MESSAGE); optPane.setOptionType(OK_CANCEL_OPTION); /* ctrl key will hush the TTS */ optPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"shut_up"); optPane.getActionMap().put("shut_up", SpeechUtilities.getShutUpAction()); JDialog dialog = optPane.createDialog(parentComponent, resources.getString("dialog.speech_option_pane.modifiers")); SpeechUtilities.changeTabListener(optPane,dialog); dialog.addWindowFocusListener(new WindowAdapter(){ @Override public void windowGainedFocus(WindowEvent e) { checkBoxes[0].requestFocusInWindow(); } }); SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); dialog.dispose(); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); if(optPane.getValue() == null)//window closed return null; else if(((Integer)optPane.getValue()).intValue() == CANCEL_OPTION || ((Integer)optPane.getValue()).intValue() == CLOSED_OPTION)//pressed on cancel return null; else{ // pressed on OK Set<Integer> returnSet = new LinkedHashSet<Integer>(); for(int i=0;i<checkBoxes.length;i++) if(checkBoxes[i].isSelected()) returnSet.add(i); return returnSet; } } /** * Returns the specified component's {code Frame}. * * @param parentComponent the component for this dialog * @return the {@code Frame} that contains the component */ public static Frame getFrameForComponent(Component parentComponent){ return JOptionPane.getFrameForComponent(parentComponent); } private static ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName()); private static final int NOTES_TEXT_AREA_COL_SIZE = 10; private static final int NOTES_TEXT_AREA_ROW_SIZE = 10; private static final String PROGRESS_DIALOG_CANCEL_OPTION = resources.getString("dialog.speech_option_pane.cancel"); public static final int QUESTION_MESSAGE = JOptionPane.QUESTION_MESSAGE; public static final int ERROR_MESSAGE = JOptionPane.ERROR_MESSAGE; public static final int INFORMATION_MESSAGE = JOptionPane.INFORMATION_MESSAGE; public static final int WARNING_MESSAGE = JOptionPane.WARNING_MESSAGE; public static final int OK_CANCEL_OPTION = JOptionPane.OK_CANCEL_OPTION; public static final int CANCEL_OPTION = JOptionPane.CANCEL_OPTION; public static final int OK_OPTION = JOptionPane.OK_OPTION; public static final int CLOSED_OPTION = JOptionPane.CLOSED_OPTION; public static final int DEFAULT_OPTION = JOptionPane.DEFAULT_OPTION; public static final int YES_NO_OPTION = JOptionPane.YES_NO_OPTION; public static final int YES_OPTION = JOptionPane.YES_OPTION; public static final int NO_OPTION = JOptionPane.NO_OPTION; /** * A swing worker to be passed as argument to {@code showProgressDialog}. The {@code execute} * method can be interrupted by the user by clicking on the {@code cancel} button of the dialog. * * @param <T> the result type returned by this {@code SwingWorker}'s {@code doInBackground} and {@code get} methods * @param <V> the type used for carrying out intermediate results by this {@code SwingWorker}'s {@code publish} and {@code process} methods */ public static abstract class ProgressDialogWorker<T,V> extends SwingWorker<T,V> { /** * Creates a new ProgressDialogWorker */ public ProgressDialogWorker(){ bar = new JProgressBar(); bar.setIndeterminate(true); } private void setDialog(JDialog dialog){ this.dialog = dialog; } @Override protected void done() { //executed in EDT when the work is done if(dialog != null) dialog.dispose(); } private JDialog dialog; private JProgressBar bar; } }