/**
* Copyright (c) 2003, Spellcast development team
* http://spellcast.dev.java.net/
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* [1] Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* [2] Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* [3] Neither the name "Spellcast development team" nor the names of
* its contributors may be used to endorse or promote products
* derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package net.java.dev.spellcast.utilities;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.LayoutManager;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
import javax.swing.event.ListDataEvent;
/**
* One of the consequences of using a <code>JList</code> is that, in order to allow selectability, many of the
* components within a rendered cell have no functionality; buttons cannot be clicked, and sublists cannot really exist.
* However, two concepts of a JList are very useful: having ordered components which respond to changes in a
* <code>ListModel</code>, and having these components be displayed in a list-like fashion. The point of this class
* is to implement those two features of a <code>JList</code> and ignore the selectable-index feature available to
* <code>JList</code>s. <br>
* <br>
* This panel creates a list of which contains data mirroring a given <code>LockableListModel</code>. Note, however,
* that this does not in any way keep track of what is added or removed in of itself, and relies on the
* <code>Container</code> component to implement that part of its functionality, which has the consequence of allowing
* all add() and remove() functions to be public. Thus, if any components are added or removed from this list through
* other classes, the contents are no longer reliable and unanticipated exceptions may be thrown.
*/
public abstract class PanelList
extends JPanel
{
private JPanel listPanel;
/**
* Constructs a new <code>PanelList</code> which will respond to changes in the given
* <code>LockableListModel</code> by adding or removing components as needed.
*
* @param visibleRows if this component is inside a <code>JScrollPane</code>, this reflects the number of rows
* that should be visible in the viewport; otherwise, this value indicates the minimum size of the panel
* @param cellHeight the height of each individual cell in the <code>PanelList</code>
* @param cellWidth the width of each individual cell in the <code>PanelList</code>
* @param associatedListModel the list model associated with this <code>PanelList</code>
*/
public PanelList( final int visibleRows, final int cellWidth, final int cellHeight,
final LockableListModel associatedListModel )
{
this( visibleRows, cellWidth, cellHeight, associatedListModel, true );
}
/**
* Constructs a new <code>PanelList</code> which will respond to changes in the given
* <code>LockableListModel</code> by adding or removing components as needed.
*
* @param visibleRows if this component is inside a <code>JScrollPane</code>, this reflects the number of rows
* that should be visible in the viewport; otherwise, this value indicates the minimum size of the panel
* @param cellHeight the height of each individual cell in the <code>PanelList</code>
* @param cellWidth the width of each individual cell in the <code>PanelList</code>
* @param associatedListModel the list model associated with this <code>PanelList</code>
*/
public PanelList( final int visibleRows, final int cellWidth, final int cellHeight,
final LockableListModel associatedListModel, final boolean useBoxLayout )
{
super( new BorderLayout() );
JPanel listContainer = new JPanel( new BorderLayout() );
listContainer.add( this.listPanel = new JPanel(), BorderLayout.NORTH );
this.add( listContainer, this.isResizeableList() ? BorderLayout.CENTER : BorderLayout.WEST );
this.listPanel.setLayout( useBoxLayout ? (LayoutManager) new BoxLayout( this.listPanel, BoxLayout.Y_AXIS ) : (LayoutManager) new FlowLayout() );
if ( associatedListModel != null )
{
Object[] contents = associatedListModel.toArray();
for ( int i = 0; i < contents.length; ++i )
{
this.listPanel.add( (Component) this.constructPanelListCell( contents[ i ], i ), i );
}
this.validatePanelList();
associatedListModel.addListDataListener( new PanelListListener() );
}
}
protected boolean isResizeableList()
{
return false;
}
/**
* Overridden so that individual components may be enabled and disabled. This is different from the behavior of a
* normal JPanel, because the children of a <code>PanelList</code> should be enabled and disabled with the parent
* list.
*
* @param isEnabled <code>true</code> if the list should be enabled
*/
@Override
public void setEnabled( final boolean isEnabled )
{
int componentCount = this.listPanel.getComponentCount();
for ( int i = 0; i < componentCount; ++i )
{
this.listPanel.getComponent( i ).setEnabled( isEnabled );
}
}
/**
* Returns a <code>PanelListCell</code> constructed from the given Object, to be positioned at the given index in
* the <code>PanelList</code>.
*
* @param value the object which contains the data needed to construct the display
* @param index this cell's intended index within the <code>PanelList</code>
* @return the constructed panel list cell
*/
protected abstract PanelListCell constructPanelListCell( Object value, int index );
/**
* A private function used to validate the panel list. The function serves to resize the display, as appropriate.
* This allows any function which is controlling the <code>Scrollable</code> elements of the list to properly
* adjust themselves to accomodate the updated panel.
*/
private void validatePanelList()
{
this.validate();
this.repaint();
}
public Component[] getPanelListCells()
{
return this.listPanel.getComponents();
}
/**
* Rather than having the PanelList implement <code>ListDataListener</code>, it instead defers all listening to
* this listener class, which then listens on the <code>LockableListModel</code> for changes in its underlying
* structure.
*/
private class PanelListListener
implements javax.swing.event.ListDataListener
{
/**
* Called whenever contents have been added to the original list; a function required by every
* <code>ListDataListener</code>.
*
* @param e the <code>ListDataEvent</code> that triggered this function call
*/
public void intervalAdded( final ListDataEvent e )
{
LockableListModel source = (LockableListModel) e.getSource();
int index0 = e.getIndex0();
int index1 = e.getIndex1();
if ( index1 >= source.size() || source.size() == PanelList.this.listPanel.getComponentCount() )
{
return;
}
for ( int i = index0; i <= index1; ++i )
{
PanelList.this.listPanel.add(
(Component) PanelList.this.constructPanelListCell( source.get( i ), i ), i );
}
PanelList.this.validatePanelList();
}
/**
* Called whenever contents have been removed from the original list; a function required by every
* <code>ListDataListener</code>.
*
* @param e the <code>ListDataEvent</code> that triggered this function call
*/
public void intervalRemoved( final ListDataEvent e )
{
LockableListModel source = (LockableListModel) e.getSource();
int index0 = e.getIndex0();
int index1 = e.getIndex1();
if ( index1 >= PanelList.this.listPanel.getComponentCount() || source.size() == PanelList.this.listPanel.getComponentCount() )
{
return;
}
for ( int i = index1; i >= index0; --i )
{
PanelList.this.listPanel.remove( i );
}
PanelList.this.validatePanelList();
}
/**
* Called whenever contents in the original list have changed; a function required by every
* <code>ListDataListener</code>.
*
* @param e the <code>ListDataEvent</code> that triggered this function call
*/
public void contentsChanged( final ListDataEvent e )
{
LockableListModel source = (LockableListModel) e.getSource();
int index0 = e.getIndex0();
int index1 = e.getIndex1();
int originalCount = PanelList.this.listPanel.getComponentCount();
for ( int i = index1; i >= index0; --i )
{
if ( i >= originalCount )
{
PanelList.this.listPanel.add(
(Component) PanelList.this.constructPanelListCell( source.get( i ), i ), originalCount );
}
else if ( i < source.size() )
{
PanelList.this.listPanel.remove( i );
}
else
{
( (PanelListCell) PanelList.this.listPanel.getComponent( i ) ).updateDisplay(
PanelList.this, source.get( i ), i );
}
}
PanelList.this.validatePanelList();
}
}
}