
com.globalmentor.swing.AbstractListModelComponentSequencePanel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of globalmentor-swing Show documentation
Show all versions of globalmentor-swing Show documentation
GlobalMentor Java Swing library.
The newest version!
/*
* Copyright © 1996-2009 GlobalMentor, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.globalmentor.swing;
import java.awt.*;
import java.util.List;
import javax.swing.*;
import javax.swing.event.*;
/**
* A component sequence panel that produces its sequence components from items in a list model. (The list items are not necessarily components.)
*
* In order to create and edit items, an editor must be set (besides setting the list to be editable using {@link #setEditable(boolean)}). Actual modification
* of the list will only be performed if the list uses a list model that implements either {@link DefaultListModel} or {@link List}.
*
*
* Bound properties:
*
*
* - {@link Editable#EDITABLE_PROPERTY} (
Boolean
)
* - Indicates the editable status has changed.
*
* @param The type of elements contained in the list model.
* @author Garret Wilson
*/
public abstract class AbstractListModelComponentSequencePanel extends AbstractComponentSequencePanel implements Editable {
/** The list model from which components are produced, or null
for no list. */
private ListModel listModel;
/** @return The list model from which components are produced, or null
for no list. */
public ListModel getListModel() {
return listModel;
}
/**
* Changes the list the sequence of components represents.
* @param newListModel The list model from which components are produced, or null
for no list.
*/
public void setListModel(final ListModel newListModel) {
final ListModel oldListModel = listModel; //get our old list model
if(oldListModel != newListModel) { //if the list model is really changing
if(oldListModel != null) { //if we had a list model before
oldListModel.removeListDataListener(listDataListener); //remove our list data listener
}
listModel = newListModel; //update the list model
if(newListModel != null) { //if we have a new list model
newListModel.addListDataListener(listDataListener); //listen for changes to the list
}
if(getEditStrategy() != null) { //if we have an edit strategy
getEditStrategy().setListModel(newListModel); //update the edit strategy's reference to the model
}
goStart(); //go to the beginning which, if we don't have a list, will switch to the default component
}
}
/** The edit strategy for editing items in the list model, or null
if there is no edit strategy. */
private ListModelEditStrategy editStrategy;
/** @return The strategy for editing items in the list model, or null
if there is no edit strategy. */
public ListModelEditStrategy getEditStrategy() {
return editStrategy;
}
/**
* Sets the edit strategy object
* @param editStrategy The edit strategy for editing items in the list, or null
if there is no edit strategy.
*/
public void setEditStrategy(final ListModelEditStrategy editStrategy) {
this.editStrategy = editStrategy;
} //TODO make sure we unregister the old list strategy first by removing all listeners to the list
/** Whether the items in the list can be edited. */
private boolean editable;
/** @return Whether the items in the list can be edited. */
public boolean isEditable() {
return editable;
}
/**
* Sets whether the items in the list can be edited. This defaults to false
. This is a bound property.
* @param newEditable true
if the list can be edited.
*/
public void setEditable(final boolean newEditable) {
final boolean oldEditable = editable; //get the old value
if(oldEditable != newEditable) { //if the value is really changing
editable = newEditable; //update the value
firePropertyChange(EDITABLE_PROPERTY, new Boolean(oldEditable), new Boolean(newEditable)); //show that the property has changed
updateStatus(); //update the status
}
}
/** Our anonymous listener that forwards events to our protected listener methods. */
protected final ListDataListener listDataListener;
/** The current index in the list. */
private int index;
/** @return The current index in the list. */
public int getIndex() {
return index;
}
/**
* Sets the current index in the list.
* @param newIndex The new list index.
*/
protected void setIndex(final int newIndex) {
index = newIndex;
}
/** Default constructor. */
public AbstractListModelComponentSequencePanel() {
this(null, true); //construct and initialize the panel without a list
}
/**
* List model constructor.
* @param listModel The list the items of which the components in this sequence panel represent, or null
for no list.
*/
public AbstractListModelComponentSequencePanel(final ListModel listModel) {
this(listModel, true); //construct and initialize the panel
}
/**
* Constructor that allows optional initialization.
* @param listModel The list the items of which the components in this sequence panel represent, or null
for no list
* @param initialize true
if the panel should initialize itself by calling the initialization methods.
*/
public AbstractListModelComponentSequencePanel(final ListModel listModel, final boolean initialize) {
this(listModel, true, true, initialize); //consruct the panel with a toolbar and statusbar
}
/**
* Constructor that allows optional initialization.
* @param listModel The list the items of which the components in this sequence panel represent.
* @param hasToolBar Whether this panel should have a toolbar.
* @param hasStatusBar Whether this panel should have a status bar.
* @param initialize true
if the panel should initialize itself by calling the initialization methods.
*/
public AbstractListModelComponentSequencePanel(final ListModel listModel, final boolean hasToolBar, final boolean hasStatusBar, final boolean initialize) {
this(listModel, null, hasToolBar, hasStatusBar, initialize); //construct the panel with no edit strategy
}
/**
* Constructor that allows optional initialization.
* @param listModel The list the items of which the components in this sequence panel represent.
* @param editStrategy The edit strategy for editing items in the list model, or null
if this panel does not allow editing the list model.
* @param hasToolBar Whether this panel should have a toolbar.
* @param hasStatusBar Whether this panel should have a status bar.
* @param initialize true
if the panel should initialize itself by calling the initialization methods.
*/
public AbstractListModelComponentSequencePanel(final ListModel listModel, final ListModelEditStrategy editStrategy, final boolean hasToolBar,
final boolean hasStatusBar, final boolean initialize) {
super(hasToolBar, hasStatusBar, false); //construct the panel, but don't initialize
this.editStrategy = editStrategy; //set the edit strategy
listDataListener = new ListDataListener() { //create an anonymous nested list data listener that forwards events to our local methods
public void intervalAdded(ListDataEvent e) {
onIntervalAdded(e.getIndex0(), e.getIndex1());
} //forward the event to our local method
public void intervalRemoved(ListDataEvent e) {
onIntervalRemoved(e.getIndex0(), e.getIndex1());
} //forward the event to our local method
public void contentsChanged(ListDataEvent e) {
onContentsChanged(e.getIndex0(), e.getIndex1());
} //forward the event to our local method
};
this.listModel = null; //start out with no list model
setListModel(listModel); //set the list model we were given
setDistinctAdvance(true); //use distinct buttons for advancing
editable = false; //default to not being editable
if(initialize) //if we should initialize the panel
initialize(); //initialize everything
}
/**
* Initializes actions in the action manager.
* @param actionManager The implementation that manages actions.
*/
protected void initializeActions(final ActionManager actionManager) {
super.initializeActions(actionManager); //do the default initialization
actionManager.removeToolAction(getStartAction()); //remove the start action
if(isEditable() && getEditStrategy() != null) { //if this sequence panel is editable and there is an edit strategy TODO allow the actions to be changed if the panel becomes uneditable
actionManager.addToolAction(new ActionManager.SeparatorAction());
actionManager.addToolAction(getEditStrategy().getAddAction());
actionManager.addToolAction(getEditStrategy().getDeleteAction());
}
}
/**
* Updates the states of the actions, including enabled/disabled status, proxied actions, etc.
*/
public void updateStatus() {
super.updateStatus(); //update the default actions
getStartAction().setEnabled(getIndex() < 0); //only allow starting if we haven't started, yet
final boolean isEditable = isEditable(); //see whether the list can be edited
/*TODO fix with edit strategy
editSeparator.setVisible(isEditable); //only show the edit separator if editing is allowed
addButton.setVisible(isEditable); //only show the add button if editing is allowed
deleteButton.setVisible(isEditable); //only show the delete button if editing is allowed
deleteButton.setEnabled(getIndex()>=0); //only enable the delete button if there is something selected to be deleted
editButton.setVisible(isEditable); //only show the edit button if editing is allowed
editButton.setEnabled(getIndex()>=0); //only enable the edit button if there is something selected to be edited
*/
}
/**
* Goes to the indicated step in the sequence.
* @see #setIndex
*/
public void go(final int index) {
if(verifyCurrentComponent()) { //if the current component verifies TODO check to see if we're actually changing, and that the given index is correct
final Component component = getIndexedComponent(index); //get the component for this index
setContentComponent(component != null ? component : getDefaultComponent()); //change to the component, if there is one
setIndex(index); //show that we're at the given index TODO make sure the index is within our range
updateStatus(); //update the status
}
}
/** Goes to the first step in the sequence. */
protected void first() {
super.first(); //go to the first component
setIndex(0); //show that we're at the first index
}
/**
* Goes to the previous component in the sequence. If there is no previous component, no action occurs.
*/
protected void previous() {
super.previous(); //go to the previous component
setIndex(getIndex() - 1); //show that we're at the previous index
}
/** Goes to the next component in the sequence. */
protected void next() {
super.next(); //go to the next component
setIndex(getIndex() + 1); //show that we're at the next index
}
/**
* Determines the component to be displayed at the given step of the sequence.
* @param index The zero-based index of the step in the sequence.
* @return The component to be displayed at the given step of the sequence.
* @throws ClassCastException if the element as the given index is not of the correct generic type E
.
*/
protected Component getIndexedComponent(final int index) {
//TODO maybe bring some of this index stuff up the hierarchy
//TODO maybe call this method from other methods to avoid redundancy
//TODO decide whether we should throw an ArrayOutOfBounds exception
return getListModel() != null && index >= 0 && index < getListModel().getSize() ? getComponent((E)getListModel().getElementAt(index)) : null; //get the component for the requested item
}
/**
* @return The first component to be displayed in the sequence.
* @throws ClassCastException if the element as the given index is not of the correct generic type E
.
*/
protected Component getFirstComponent() {
return getListModel() != null && getListModel().getSize() > 0 ? getComponent((E)getListModel().getElementAt(0)) : null; //get the component for the first item
}
/** @return true
if there is a next component after the current one. */
protected boolean hasNext() {
return getListModel() != null ? getIndex() < getListModel().getSize() - 1 : false; //we have another item if we're not at the last item in the list
}
/**
* @return The next component to be displayed in the sequence.
* @throws ClassCastException if the element as the given index is not of the correct generic type E
.
*/
protected Component getNextComponent() {
return getListModel() != null ? getComponent((E)getListModel().getElementAt(getIndex() + 1)) : null; //get the component for the next item
}
/** @return true
if there is a previous component before the current one. */
protected boolean hasPrevious() {
return getIndex() > 0; //we have another item if we're not at the first item in the list
}
/**
* @return The previous component to be displayed in the sequence.
* @throws ClassCastException if the element as the given index is not of the correct generic type E
.
*/
protected Component getPreviousComponent() {
return getListModel() != null ? getComponent((E)getListModel().getElementAt(getIndex() - 1)) : null; //get the component for the previous item
}
/**
* Returns a component appropriate for representing the given object from the list.
* @param object An object in the list.
* @return A component appropriate for representing the object.
*/
protected abstract Component getComponent(final E object);
/**
* Sent after the indices in the index0, index1 interval have been inserted in the data model. The new interval includes both index0 and index1.
* @param index0 The lower index, inclusive, of the range.
* @param index1 The upper index, inclusive, of the range.
*/
protected void onIntervalAdded(final int index0, final int index1) {
/*TODO fix
if(getIndex()<0 && index0>=0) { //if a valid index was added, and we hadn't started the sequence before TODO only move if we've already started the sequence, or wanted to---but how would we know that?
goFirst(); //
}
else //if the change doesn't affect our index
*/
{
updateStatus(); //update the status
}
}
/**
* Sent after the indices in the index0, index1 interval have been removed from the data model. The interval includes both index0 and index1.
* @param index0 The lower index, inclusive, of the range.
* @param index1 The upper index, inclusive, of the range.
*/
protected void onIntervalRemoved(final int index0, final int index1) {
if(index0 <= getIndex()) { //if the removed index affects our index
final int index; //see which index to go to, now
if(getIndex() < getListModel().getSize()) { //if our current index is still OK
index = getIndex(); //keep our current index
} else { //if our current index is now out of bounds
index = getListModel().getSize() - 1; //go to the last available index
}
go(index); //go to appropriate index TODO switch to an updateComponent method, and probably make go(int) check to make sure the index is changing
} else { //if the removed index didn't affect our index
updateStatus(); //update the status
}
}
/**
* Sent when the contents of the list has changed in a way that's too complex to characterize with the previous methods. For example, this is sent when an
* item has been replaced. Index0 and index1 bracket the change.
* @param index0 The lower index, inclusive, of the range.
* @param index1 The upper index, inclusive, of the range.
*/
protected void onContentsChanged(final int index0, final int index1) {
if(getIndex() >= index0 && getIndex() <= index1) { //if the component at our current index was affected
go(getIndex()); //update the component at our index TODO switch to an updateComponent method, and probably make go(int) check to make sure the index is changing
} else { //if nothing changed at our index
updateStatus(); //update the status
}
}
/**
* An abstract edit strategy that allows editing of the items in the list model.
*
* This edit strategy correctly uses the sequence panel as a parent component and as a modifiable object.
*
* @author Garret Wilson
*/
protected abstract class EditStrategy extends ListModelEditStrategy {
/** Default constructor. */
public EditStrategy() {
super(AbstractListModelComponentSequencePanel.this.getListModel(), AbstractListModelComponentSequencePanel.this,
AbstractListModelComponentSequencePanel.this); //construct the parent class with the list model
}
/** @return The currently selected index of the list model. */
protected int getLeadSelectionIndex() {
return getIndex(); //return the current index
}
/**
* Sets the current main seletion index of the list model.
* @param index The new index to become the main selection.
*/
public void setLeadSelectionIndex(final int index) {
go(index); //go to the indicated index
}
/** @return The component acting as the parent for windows. */
//TODO del if not needed protected Component getParentComponent() {return AbstractListModelComponentSequencePanel.this;}
/** @return The object that represents the list model modification status. */
// TODO del if not needed protected Modifiable getModifiable() {return AbstractListModelComponentSequencePanel.this;}
/**
* Edits an object from the list.
*
* This version does nothing and simply returns the item, as it is assumed that the panel itself contains the edit view of the item.
*
* @param item The item to edit in the list.
* @return The object with the modifications from the edit, or null
if the edits should not be accepted.
*/
protected Object editItem(final Object item) {
return item; //return the item so that it can be added
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy