// This file is part of Penn TotalRecall <http://memory.psych.upenn.edu/TotalRecall>.
//
// TotalRecall 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, version 3 only.
//
// TotalRecall 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 TotalRecall. If not, see <http://www.gnu.org/licenses/>.
package components.wordpool;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
/**
* <code>JList</code> that stores available wordpool word for the annotating open audio file.
*
* @author Yuvi Masory
*/
public class WordpoolList extends JList implements FocusListener, MouseListener, KeyListener {
private static WordpoolListModel model;
private static WordpoolList instance;
WordpoolListCellRenderer render;
private WordpoolList() {
model = new WordpoolListModel();
setModel(model);
//set the cell renderer that will display lst words differently from regular words
render = new WordpoolListCellRenderer();
setCellRenderer(render);
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
setLayoutOrientation(JList.VERTICAL);
//focus listener makes the the containing WordpoolDisplay look focused at the appropriate times
addFocusListener(this);
//normally JLists take focus on their own when clicked. however in this case we have made
//the WordpoolList not focusable when it's empty
//so in that case we give focus to the WordpoolTextField when the WordpoolList is clicked
addMouseListener(new MouseAdapter(){
@Override
public void mousePressed(MouseEvent e) {
if(isFocusable()) {
//automatically takes focus in this case
}
else {
WordpoolTextField.getInstance().requestFocusInWindow();
}
}
});
addKeyListener(this);
//clicking on wordpool words
addMouseListener(this);
//hitting enter can be used to enter a wordpool word to the text field
//this code is duplicated in the mouse listener where double click has the same effect
getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "insert_word");
getActionMap().put("insert_word", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
Object[] objs = getSelectedValues();
if(objs.length == 1) { //in case multiple selection mode is used in the future
WordpoolWord selectedWord = (WordpoolWord)objs[0];
WordpoolDisplay.switchToFocusAndClobber(selectedWord.getText());
}
}
});
//overrides JScrollPane key bindings for the benefit of SeekAction's key bindings
getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "none");
getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "none");
getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_DOWN_MASK, false), "none");
getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_DOWN_MASK, false), "none");
}
/**
* Type-refined implementation that guarantees a <code>WordpoolListModel</code> instead of a <code>ListModel</code>.
*
* @return The <code>WordpoolListModel</code> associated with the <code>WordpoolList</code>
*/
@Override
public WordpoolListModel getModel() {
return model;
}
/**
* Gets a reference to this object for use by a custom <code>FocusTraversalPolicy</code>.
*
* <p>Unfortunately this requires a break from the encapsulation strategy of <code>WordpoolDisplay</code> containing all the <code>public</code> access.
* Please do NOT abuse this method to access the <code>WordpoolDisplay</code> for purposes other than those intended.
* Add new public features to <code>WordpoolDisplay</code> which can then use {@linkplain #getInstance()} as needed.
*
* @return {@link #getInstance()}
*/
public static WordpoolList getFocusTraversalReference() {
return getInstance();
}
/**
* Custom focusability condition that behaves in the default manner aside from rejecting focus when this <code>WordpoolList</code> has no elements.
*
* @return Whether or nut this component should accept focus
*/
@Override
public boolean isFocusable() {
return(super.isFocusable() && model.getSize() > 0);
}
/**
* Handler for the event that this <code>WordpoolList</code> gains focus.
*/
public void focusGained(FocusEvent e) {
int anchor = getAnchorSelectionIndex();
if(anchor >= 0) {
setSelectedIndex(anchor);
}
else {
setSelectedIndex(0);
}
}
/**
* Handler for the event this <code>AudioFileList</code> loses focus.
*
* Asks the containing <code>AudioFileDisplay</code> to stop looking focused.
*/
public void focusLost(FocusEvent e) {
clearSelection();
}
/**
*
* @return
*/
protected static WordpoolWord getFirstWord() {
if(model.getSize() > 0) {
return (WordpoolWord)model.getElementAt(0);
}
else {
return null;
}
}
/**
* Singleton accessor.
*
* Many classes in this package require access to this object, so a singleton accessor strategy is used to avoid the need
* to pass every class a reference to this object.
*
* @return The singleton <code>WordpoolList</code>
*/
protected static WordpoolList getInstance() {
if(instance == null) {
instance = new WordpoolList();
}
return instance;
}
/**
* On double click adds enters the clicked-on word to the text field.
*
* @param e The MouseEvent provided by the trigger
*/
public void mouseClicked(MouseEvent e) {
if(e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
int index = locationToIndex(e.getPoint());
if(index >= 0) {
WordpoolWord clickedWord = (WordpoolWord) model.getElementAt(index);
WordpoolDisplay.switchToFocusAndClobber(clickedWord.getText());
}
}
}
/** Empty implementation. */
public void mouseEntered(MouseEvent e) {}
/** Empty implementation. */
public void mouseExited(MouseEvent e) {}
/** Empty implementation. */
public void mousePressed(MouseEvent e) {}
/** Empty implementation. */
public void mouseReleased(MouseEvent e) {}
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_UP) {
if(getSelectedIndex() == 0) {
WordpoolTextField.getInstance().requestFocusInWindow();
}
}
}
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}
}