blob: 42e952f6b09f4ea21427b5a34fce8c572082c0fd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0, which accompanies this distribution
* and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.utility.internal.model.value.swing;
import javax.swing.AbstractListModel;
import javax.swing.event.ListDataListener;
import org.eclipse.jpt.utility.internal.StringTools;
import org.eclipse.jpt.utility.internal.model.listener.awt.AWTListChangeListenerWrapper;
import org.eclipse.jpt.utility.internal.model.value.CollectionListValueModelAdapter;
import org.eclipse.jpt.utility.model.event.ListChangeEvent;
import org.eclipse.jpt.utility.model.listener.ListChangeListener;
import org.eclipse.jpt.utility.model.value.CollectionValueModel;
import org.eclipse.jpt.utility.model.value.ListValueModel;
/**
* This javax.swing.ListModel can be used to keep a ListDataListener
* (e.g. a JList) in synch with a ListValueModel (or a CollectionValueModel).
*
* An instance of this ListModel *must* be supplied with a value model,
* which is a ListValueModel on the bound list or a CollectionValueModel
* on the bound collection. This is required - the list (or collection)
* itself can be null, but the value model that holds it cannot.
*/
public class ListModelAdapter
extends AbstractListModel
{
/** A value model on the underlying model list. */
protected ListValueModel<?> listHolder;
/**
* Cache the size of the list for "dramatic" changes.
* @see #listChanged(ListChangeEvent)
*/
protected int listSize;
/** A listener that allows us to forward changes made to the underlying model list. */
protected final ListChangeListener listChangeListener;
// ********** constructors **********
/**
* Default constructor - initialize stuff.
*/
private ListModelAdapter() {
super();
this.listSize = 0;
this.listChangeListener = this.buildListChangeListener();
}
/**
* Constructor - the list holder is required.
*/
public ListModelAdapter(ListValueModel<?> listHolder) {
this();
this.setModel(listHolder);
}
/**
* Constructor - the collection holder is required.
*/
public ListModelAdapter(CollectionValueModel<?> collectionHolder) {
this();
this.setModel(collectionHolder);
}
// ********** initialization **********
protected ListChangeListener buildListChangeListener() {
return new AWTListChangeListenerWrapper(this.buildListChangeListener_());
}
protected ListChangeListener buildListChangeListener_() {
return new ListChangeListener() {
public void itemsAdded(ListChangeEvent event) {
ListModelAdapter.this.itemsAdded(event);
}
public void itemsRemoved(ListChangeEvent event) {
ListModelAdapter.this.itemsRemoved(event);
}
public void itemsReplaced(ListChangeEvent event) {
ListModelAdapter.this.itemsReplaced(event);
}
public void itemsMoved(ListChangeEvent event) {
ListModelAdapter.this.itemsMoved(event);
}
public void listCleared(ListChangeEvent event) {
ListModelAdapter.this.listCleared();
}
public void listChanged(ListChangeEvent event) {
ListModelAdapter.this.listChanged();
}
@Override
public String toString() {
return "list listener";
}
};
}
// ********** ListModel implementation **********
public int getSize() {
return this.listHolder.size();
}
public Object getElementAt(int index) {
return this.listHolder.get(index);
}
/**
* Extend to start listening to the underlying model list if necessary.
*/
@Override
public void addListDataListener(ListDataListener l) {
if (this.hasNoListDataListeners()) {
this.engageModel();
this.listSize = this.listHolder.size();
}
super.addListDataListener(l);
}
/**
* Extend to stop listening to the underlying model list if appropriate.
*/
@Override
public void removeListDataListener(ListDataListener l) {
super.removeListDataListener(l);
if (this.hasNoListDataListeners()) {
this.disengageModel();
this.listSize = 0;
}
}
// ********** public API **********
/**
* Return the underlying list model.
*/
public ListValueModel<?> model() {
return this.listHolder;
}
/**
* Set the underlying list model.
*/
public void setModel(ListValueModel<?> listHolder) {
if (listHolder == null) {
throw new NullPointerException();
}
boolean hasListeners = this.hasListDataListeners();
if (hasListeners) {
this.disengageModel();
}
this.listHolder = listHolder;
if (hasListeners) {
this.engageModel();
this.listChanged();
}
}
/**
* Set the underlying collection model.
*/
@SuppressWarnings("unchecked")
public void setModel(CollectionValueModel<?> collectionHolder) {
this.setModel(new CollectionListValueModelAdapter(collectionHolder));
}
// ********** queries **********
/**
* Return whether this model has no listeners.
*/
protected boolean hasNoListDataListeners() {
return this.getListDataListeners().length == 0;
}
/**
* Return whether this model has any listeners.
*/
protected boolean hasListDataListeners() {
return ! this.hasNoListDataListeners();
}
// ********** behavior **********
protected void engageModel() {
this.listHolder.addListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener);
}
protected void disengageModel() {
this.listHolder.removeListChangeListener(ListValueModel.LIST_VALUES, this.listChangeListener);
}
// ********** list change support **********
/**
* Items were added to the underlying model list.
* Notify listeners of the changes.
*/
protected void itemsAdded(ListChangeEvent event) {
int start = event.getIndex();
int end = start + event.itemsSize() - 1;
this.fireIntervalAdded(this, start, end);
this.listSize += event.itemsSize();
}
/**
* Items were removed from the underlying model list.
* Notify listeners of the changes.
*/
protected void itemsRemoved(ListChangeEvent event) {
int start = event.getIndex();
int end = start + event.itemsSize() - 1;
this.fireIntervalRemoved(this, start, end);
this.listSize -= event.itemsSize();
}
/**
* Items were replaced in the underlying model list.
* Notify listeners of the changes.
*/
protected void itemsReplaced(ListChangeEvent event) {
int start = event.getIndex();
int end = start + event.itemsSize() - 1;
this.fireContentsChanged(this, start, end);
}
/**
* Items were moved in the underlying model list.
* Notify listeners of the changes.
*/
protected void itemsMoved(ListChangeEvent event) {
int start = Math.min(event.getSourceIndex(), event.getTargetIndex());
int end = Math.max(event.getSourceIndex(), event.getTargetIndex()) + event.getMoveLength() - 1;
this.fireContentsChanged(this, start, end);
}
/**
* The underlying model list was cleared.
* Notify listeners of the changes.
*/
protected void listCleared() {
if (this.listSize != 0) {
this.fireIntervalRemoved(this, 0, this.listSize - 1);
this.listSize = 0;
}
}
/**
* The underlying model list has changed "dramatically".
* Notify listeners of the changes.
*/
protected void listChanged() {
if (this.listSize != 0) {
this.fireIntervalRemoved(this, 0, this.listSize - 1);
}
this.listSize = this.listHolder.size();
if (this.listSize != 0) {
this.fireIntervalAdded(this, 0, this.listSize - 1);
}
}
// ********** Object overrides **********
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.listHolder);
}
}