/* class JList
*
* Copyright (C) 2003 R M Pitman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Modified Jul 15, 2003 by Tadpole Computer, Inc.
* Modifications Copyright 2003 by Tadpole Computer, Inc.
*
* Modifications are hereby licensed to all parties at no charge under
* the same terms as the original.
*
* Fixed case where visible row count was larger that the total number
* of rows in the model.
*/
package charvax.swing;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Vector;
import charva.awt.Container;
import charva.awt.Dimension;
import charva.awt.EventQueue;
import charva.awt.Insets;
import charva.awt.Point;
import charva.awt.Scrollable;
import charva.awt.Toolkit;
import charva.awt.event.KeyEvent;
import charva.awt.event.MouseEvent;
import charva.awt.event.ScrollEvent;
import charva.awt.event.ScrollListener;
import charvax.swing.event.ListSelectionListener;
/**
* A component that allows the user to select one or more objects from a
* list.<p>
*
* The JList does not provide scrolling capability. The
* JList is normally inserted into a JScrollPane to provide scrolling.<p>
*
*/
public class JList
extends JComponent
implements Scrollable
{
/** Constructs a JList with 5 rows, 10 columns wide.
*/
public JList() {
setModel(new DefaultListModel());
}
/**
* Construct a JList that displays the elements in the specified
* non-null model.
*/
public JList(ListModel model_) {
setModel(model_);
}
/**
* Construct a JList containing the items in the specified array.
*/
public JList(Object[] items_)
{
setListData(items_);
}
/**
* Construct a JList containing the items in the specified Vector.
*/
public JList(Vector<?> items_)
{
setListData(items_);
}
/** Constructs a ListModel from an array of Objects and then applies
* setModel to it.
*/
public void setListData(Object[] listData_) {
DefaultListModel model = new DefaultListModel();
for (int i=0; i<listData_.length; i++) {
model.addElement(listData_[i]);
}
setModel(model);
}
/** Constructs a ListModel from a Vector and then applies setModel to it.
*/
public void setListData(Vector<?> listData_) {
Enumeration<?> e = listData_.elements();
DefaultListModel model = new DefaultListModel();
while (e.hasMoreElements()) {
Object item = e.nextElement();
model.addElement(item);
}
setModel(model);
}
/** Sets the model that represents the "contents" of the list, and
* clears the selection.
*/
public void setModel(ListModel model_) {
_listModel = model_;
_selectionModel.clearSelection();
}
/** Returns the data model that holds the list of items displayed by this
* JList.
*/
public ListModel getModel() {
return _listModel;
}
/** Set the maximum number of rows that can be displayed at a time
* by the JScrollPane which contains this JList.
*/
public void setVisibleRowCount(int rows_) {
_visibleRows = rows_;
}
public int getVisibleRowCount() {
return _visibleRows;
}
/** Set the number of columns INSIDE the list.
*/
public void setColumns(int cols_) {
_columns = cols_;
}
public Dimension getSize() {
return new Dimension(getWidth(), getHeight());
}
public int getWidth() {
Insets insets = super.getInsets();
return _columns + insets.left + insets.right;
}
public int getHeight() {
Insets insets = super.getInsets();
return _listModel.getSize() + insets.top + insets.bottom;
}
/** Returns the size of the viewport needed to display visibleRows rows.
*/
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(getWidth(), getVisibleRowCount());
}
/** Sets the selection model of the JList to an implementation
* of the ListSelectionModel interface.
*/
public void setSelectionModel(ListSelectionModel model_) {
_selectionModel = model_;
}
/** Returns the list's implementation of ListSelectionModel.
*/
public ListSelectionModel getSelectionModel() {
return _selectionModel;
}
/**
* Register an ListSelectionListener object for this component.
* The listener is notified each time a change to the selection occurs.
*/
public void addListSelectionListener(ListSelectionListener il_) {
_selectionModel.addListSelectionListener(il_);
}
/** Remove the specified ListSelectionListener from the list of listeners
* that will be notified when the selection changes.
*/
public void removeListSelectionListener(ListSelectionListener listener_) {
_selectionModel.removeListSelectionListener(listener_);
}
/**
* Get the first selected index, or -1 if there is no selected index.
*/
public int getSelectedIndex() {
return _selectionModel.getMinSelectionIndex();
}
/** Returns an array of the selected indices. The indices are
* sorted in increasing index order.
*/
public int[] getSelectedIndices() {
ArrayList<Integer> objects = new ArrayList<Integer>();
if ( ! _selectionModel.isSelectionEmpty()) {
int first = _selectionModel.getMinSelectionIndex();
int last = _selectionModel.getMaxSelectionIndex();
for (int i=first; i<=last; i++) {
if (_selectionModel.isSelectedIndex(i))
objects.add(new Integer(i));
}
}
int[] values = new int[objects.size()];
for (int i=0; i<values.length; i++) {
values[i] = objects.get(i).intValue();
}
return values;
}
/**
* Get the first selected item on this list, or <code>null</code>
* if the selection is empty.
*/
public Object getSelectedValue() {
/* Return null if there are no selected items.
*/
int index = _selectionModel.getMinSelectionIndex();
if (index == -1)
return null;
return _listModel.getElementAt(index);
}
/** Returns an array of the selected values. The objects are
* sorted in increasing index order.
*/
public Object[] getSelectedValues() {
ArrayList<Object> objects = new ArrayList<Object>();
if ( ! _selectionModel.isSelectionEmpty()) {
int first = _selectionModel.getMinSelectionIndex();
int last = _selectionModel.getMaxSelectionIndex();
for (int i=first; i<=last; i++) {
if (_selectionModel.isSelectedIndex(i))
objects.add(_listModel.getElementAt(i));
}
}
return objects.toArray();
}
/**
* Make the specified item visible (by scrolling the list up or down).
* This method does not do anything unless the JList is in a JViewport.
* Note that the list is not redrawn by this method; the redrawing is
* done by the JScrollPane (that is registered as a ScrollListener).
*/
public synchronized void ensureIndexIsVisible(int index_) {
if ( ! (getParent() instanceof JViewport) )
return;
if (index_ < 0)
index_ = 0;
else if (index_ > _listModel.getSize() - 1) {
index_ = _listModel.getSize() - 1;
}
// It seems reasonable to assume that the "current row" should
// be set to the index that is being made visible.
_currentRow = index_;
Toolkit term = Toolkit.getDefaultToolkit();
EventQueue evtqueue = term.getSystemEventQueue();
// First scroll the list DOWN so that index 0 is visible.
evtqueue.postEvent(
new ScrollEvent(this, ScrollEvent.DOWN,
new Point(0, 0)));
// Then (if necessary) scroll it UP so that the specified index
// is not below the bottom of the viewport.
evtqueue.postEvent(
new ScrollEvent(this, ScrollEvent.UP,
new Point(0, index_)));
}
/**
* Select the item at the specified index. Note that this method
* does not redraw the JList.
*/
public void setSelectedIndex(int index_) {
_selectionModel.setSelectionInterval(index_, index_);
}
/**
* Sets the selection to be the set union between the current
* selection and the specified interval between index0_ and index1_
* (inclusive).
*/
public void addSelectionInterval(int index0_, int index1_) {
_selectionModel.addSelectionInterval(index0_, index1_);
}
/**
* Sets the selection to be the set difference between the current
* selection and the specified interval between index0_ and index1_
* (inclusive).
*/
public void removeSelectionInterval(int index0_, int index1_) {
_selectionModel.removeSelectionInterval(index0_, index1_);
}
/** Clears the selection. After this <code>isSelectionEmpty()</code>
* will return true.
*/
public void clearSelection() {
_selectionModel.clearSelection();
}
/** Returns the lowest selected item index.
*/
public int getMinSelectionIndex() {
return _selectionModel.getMinSelectionIndex();
}
/** Returns the highest selected item index.
*/
public int getMaxSelectionIndex() {
return _selectionModel.getMaxSelectionIndex();
}
/**
* Sets the flag that determines whether this list allows multiple
* selections.
* @param mode_ the selection mode. Allowed values are:<p>
* <ul>
* <li> ListSelectionModel.SINGLE_SELECTION. Only one list index
* can be selected at a time.
* <li> ListSelectionModel.SINGLE_INTERVAL_SELECTION.
* <li> ListSelectionModel.MULTIPLE_INTERVAL_SELECTION. Any number
* of list items can be selected simultaneously.
* </ul>
*/
public void setSelectionMode(int mode_) {
_selectionModel.setSelectionMode(mode_);
}
/**
* Determines whether this list allows multiple selections.
*/
public int getSelectionMode() {
return _selectionModel.getSelectionMode();
}
/** Determines if the specified item in this scrolling list is selected.
*/
public boolean isIndexSelected(int index_) {
return _selectionModel.isSelectedIndex(index_);
}
/**
* Called by LayoutManager.
*/
public Dimension minimumSize() {
/* Calculate the minimum number of columns that will contain all the
* items in the list.
*/
_columns = 1;
for (int i=0; i< _listModel.getSize(); i++) {
String c = _listModel.getElementAt(i).toString();
if (c.length() > _columns)
_columns = c.length();
}
/* Take into account the border inherited from the JComponent
* superclass.
*/
Insets insets = super.getInsets();
return new Dimension(_columns + insets.left + insets.right,
_visibleRows + insets.top + insets.bottom);
}
public void requestFocus() {
/* Generate the FOCUS_GAINED event.
*/
super.requestFocus();
/* Get the absolute origin of this component
*/
Point origin = getLocationOnScreen();
Insets insets = super.getInsets();
origin.translate(insets.left, insets.top);
Toolkit.getDefaultToolkit().setCursor(
origin.addOffset(0, _currentRow));
}
/** Draws this component. Overrides draw() in Component.
* @param toolkit
*/
public void draw(Toolkit toolkit) {
/* This situation can occur if the ListModel has been changed.
*/
if (_currentRow >= _listModel.getSize()) {
if (_listModel.getSize() == 0)
_currentRow = 0;
else
_currentRow = _listModel.getSize() - 1;
}
/* Draw the border if it exists
*/
super.draw(toolkit);
/* Get the absolute origin of this component.
*/
Point origin = getLocationOnScreen();
Insets insets = super.getInsets();
origin.translate(insets.left, insets.top);
int colorpair = getCursesColor();
int attribute;
StringBuffer blanks = new StringBuffer();
for (int j=0; j<_columns; j++)
blanks.append(' ');
for (int i = 0; i < _listModel.getSize(); i++) {
toolkit.setCursor(origin.x, origin.y + i);
if (isIndexSelected(i))
attribute = Toolkit.A_REVERSE;
else
attribute = 0;
if (i == _currentRow)
attribute += Toolkit.A_BOLD;
String item = _listModel.getElementAt(i).toString();
StringBuffer buffer = new StringBuffer(item);
for (int k=item.length(); k<_columns; k++)
buffer.append(' ');
toolkit.addString(buffer.toString(), attribute, colorpair);
} // end FOR loop
for (int i = _listModel.getSize(); i < _visibleRows; i++) {
toolkit.setCursor(origin.x, origin.y + i);
toolkit.addString(blanks.toString(), 0, colorpair);
}
}
public void processKeyEvent(KeyEvent ke_) {
/* First call all KeyListener objects that may have been registered
* for this component.
*/
super.processKeyEvent(ke_);
/* Check if any of the KeyListeners consumed the KeyEvent.
*/
if (ke_.isConsumed())
return;
Toolkit term = Toolkit.getDefaultToolkit();
EventQueue evtqueue = term.getSystemEventQueue();
int key = ke_.getKeyCode();
switch (key) {
case '\t':
getParent().nextFocus();
return;
case KeyEvent.VK_BACK_TAB:
getParent().previousFocus();
return;
case KeyEvent.VK_DOWN:
/* If we are already at the bottom of the list, ignore
* this keystroke.
*/
if (_currentRow >= _listModel.getSize() -1)
return;
evtqueue.postEvent(
new ScrollEvent(this, ScrollEvent.UP,
new Point(0, ++_currentRow)));
break;
case KeyEvent.VK_PAGE_DOWN:
_currentRow += _visibleRows;
if (_currentRow >= _listModel.getSize())
_currentRow = _listModel.getSize() -1;
evtqueue.postEvent(
new ScrollEvent(this, ScrollEvent.UP,
new Point(0, _currentRow)));
break;
case KeyEvent.VK_END:
_currentRow = _listModel.getSize() - 1;
evtqueue.postEvent(new ScrollEvent(
this, ScrollEvent.UP, new Point(0, _currentRow)));
break;
case KeyEvent.VK_UP:
/* If we are already at the top of the list, ignore
* this keystroke.
*/
if (_currentRow < 1)
return;
evtqueue.postEvent(new ScrollEvent(
this, ScrollEvent.DOWN, new Point(0, --_currentRow)));
break;
case KeyEvent.VK_PAGE_UP:
_currentRow -= _visibleRows;
if (_currentRow < 0)
_currentRow = 0;
evtqueue.postEvent(new ScrollEvent(this, ScrollEvent.DOWN,
new Point(0, _currentRow)));
break;
case KeyEvent.VK_ENTER:
_doSelect();
break;
case KeyEvent.VK_HOME:
_currentRow = 0;
break;
}
if ((getParent() instanceof JViewport) == false) {
draw(Toolkit.getDefaultToolkit());
requestFocus();
super.requestSync();
}
}
private void _doSelect() {
/* Pressing ENTER or double-clicking on a row selects/deselects
* the current row. If the list is empty, ignore the keystroke.
*/
if (_listModel.getSize() == 0)
return;
if (isIndexSelected(_currentRow))
removeSelectionInterval(_currentRow, _currentRow);
else {
int mode = getSelectionMode();
if (mode == ListSelectionModel.SINGLE_SELECTION)
setSelectedIndex(_currentRow);
else
addSelectionInterval(_currentRow, _currentRow);
}
repaint();
}
public void processMouseEvent(MouseEvent e_) {
super.processMouseEvent(e_);
if (e_.getButton() == MouseEvent.BUTTON1 &&
e_.getModifiers() == MouseEvent.MOUSE_CLICKED &&
this.isFocusTraversable()) {
if (e_.getClickCount() == 1) {
int y = e_.getY();
Point origin = getLocationOnScreen();
Container parent = getParent();
if (parent instanceof JViewport) {
_currentRow = y - origin.y;
repaint();
}
}
else
_doSelect();
}
}
/**
* Register a ScrollListener object for this JList.
*/
public void addScrollListener(ScrollListener sl_) {
if (_scrollListeners == null)
_scrollListeners = new Vector<ScrollListener>();
_scrollListeners.add(sl_);
}
/**
* Remove a ScrollListener object that is registered for this JList.
*/
public void removeScrollListener(ScrollListener sl_) {
if (_scrollListeners == null)
return;
_scrollListeners.remove(sl_);
}
/** Process scroll events generated by this JList.
*/
public void processScrollEvent(ScrollEvent e_) {
if (_scrollListeners != null) {
for (Enumeration<ScrollListener> e = _scrollListeners.elements();
e.hasMoreElements(); ) {
ScrollListener sl = (ScrollListener) e.nextElement();
sl.scroll(e_);
}
}
}
/** Outputs a textual description of this component to stderr.
*/
public void debug(int level_) {
for (int i=0; i<level_; i++)
System.err.print(" ");
System.err.println("JList origin=" + _origin +
" size=" + minimumSize());
}
//================================================================
// INSTANCE VARIABLES
private int _visibleRows = 5;
private int _columns = 10;
/** Offset (from start of list) of the item under the cursor (i.e. the
* item that will be selected/deselected if the user presses ENTER)
*/
protected int _currentRow = 0;
/** The ListSelectionModel used by this JList.
*/
protected ListSelectionModel _selectionModel =
new DefaultListSelectionModel();
/** The ListModel that holds the items that are displayed by
* this JList.
*/
protected ListModel _listModel;
/** A list of ScrollListeners registered for this JList.
*/
private Vector<ScrollListener> _scrollListeners = null;
}