package com.explodingpixels.macwidgets; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * The backing model to be used with a {@link com.explodingpixels.macwidgets.SourceList}. */ public final class SourceListModel { private List<SourceListCategory> fCategories = new ArrayList<SourceListCategory>(); private List<SourceListModelListener> fListeners = new ArrayList<SourceListModelListener>(); private PropertyChangeListener fPropertyChangeListener = createSourceListItemListener(); // SourceListCategory methods ///////////////////////////////////////////////////////////////// /** * Gets the {@link SourceListCategory}s associated with this model. * * @return the {@link SourceListCategory}s associated with this model. */ public List<SourceListCategory> getCategories() { return Collections.unmodifiableList(fCategories); } /** * Adds the given category to the model and fires an event such that * {@link SourceListModelListener}s will be notified. * * @param category the {@link SourceListCategory} to add. */ public void addCategory(SourceListCategory category) { addCategory(category, fCategories.size()); } /** * Adds the given category to the model at the given index and fires an event such that * {@link SourceListModelListener}s will be notified. * * @param category the {@link com.explodingpixels.macwidgets.SourceListCategory} to add. * @param index the index to add the category at. */ public void addCategory(SourceListCategory category, int index) { fCategories.add(index, category); fireCategoryAdded(category, index); } /** * Removes the given category from the model and fires an event such that * {@link SourceListModelListener}s will be notified. * * @param category the {@link SourceListCategory} to remove. * @throws IllegalArgumentException if the given category is not part of this model. */ public void removeCategory(SourceListCategory category) { boolean removed = fCategories.remove(category); if (!removed) { throw new IllegalArgumentException("The given category does not exist in this model."); } fireCategoryRemoved(category); } /** * Removes the category at the given index from the model and fires an event such that * {@link SourceListModelListener}s will be notified. * * @param index the index of the {@link SourceListCategory} to remove. * @throws IllegalArgumentException if there is no category at the given index. */ public void removeCategoryAt(int index) { SourceListCategory category = fCategories.remove(index); if (category == null) { throw new IllegalArgumentException("There is no category at the index " + index + "."); } fireCategoryRemoved(category); } // SourceListItem methods ///////////////////////////////////////////////////////////////////// /** * Adds the given item to the given {@link SourceListCategory}. * * @param item the item to add. * @param category the category to add the item to. * @throws IllegalStateException if the given category is not in the model. */ public void addItemToCategory(SourceListItem item, SourceListCategory category) { addItemToCategory(item, category, category.getItemCount()); } /** * Adds the given item to the given {@link SourceListCategory} at the given index within that * category. * * @param item the item to add. * @param category the category to add the item to. * @param index the index in the category to add the item. * @throws IllegalStateException if the given category is not in the model. */ public void addItemToCategory(SourceListItem item, SourceListCategory category, int index) { validateCategoryIsInModel(category); category.addItem(index, item); item.addPropertyChangeListener(fPropertyChangeListener); fireItemAddedToCategory(item, category, index); } /** * Adds the given "child" item to the given "parent" item. * * @param childItem the item to add to the given parent item. * @param parentItem the item to add the child item to. * @throws IllegalStateException if the given parent item is not in the model. */ public void addItemToItem(SourceListItem childItem, SourceListItem parentItem) { addItemToItem(childItem, parentItem, parentItem.getChildItems().size()); } /** * Adds the given "child" item to the given "parent" item at the given index. The * parent {@link com.explodingpixels.macwidgets.SourceListItem} will be expanded if it was not a parent but becomes a parent * as a result of this call. * * @param childItem the item to add to the given parent item. * @param parentItem the item to add the child item to. * @param index the index of the parent item at which to add the child item. * @throws IllegalStateException if the given child or parent item is not in the model. */ public void addItemToItem(SourceListItem childItem, SourceListItem parentItem, int index) { validateItemIsInModel(parentItem); parentItem.addItem(index, childItem); childItem.addPropertyChangeListener(fPropertyChangeListener); fireItemAddedToItem(childItem, parentItem, index); } /** * Removes the given item from the given category. * * @param item the item to remove from the given category. * @param category the category form which to remove the given item. * @throws IllegalStateException if the given category is not in the model. */ public void removeItemFromCategory(SourceListItem item, SourceListCategory category) { validateItemIsInModel(item); validateCategoryIsInModel(category); category.removeItem(item); item.removePropertyChangeListener(fPropertyChangeListener); fireItemRemovedFromCategory(item, category); } /** * Removes the item at the given index from the given category. * * @param category the category from which to remove the item. * @param index the index of the item to remove. * @throws IllegalStateException if the given category is not in the model. */ public void removeItemFromCategoryAtIndex(SourceListCategory category, int index) { removeItemFromCategory(category.getItem(index), category); } /** * Removes the given child item at from the given parent item. * * @param childItem the item to remove. * @param parentItem the item from which to remove the given child item. * @throws IllegalStateException if the given child or parent item is not in the model. */ public void removeItemFromItem(SourceListItem childItem, SourceListItem parentItem) { validateItemIsInModel(childItem); validateItemIsInModel(parentItem); parentItem.removeItem(childItem); childItem.removePropertyChangeListener(fPropertyChangeListener); fireItemRemovedFromItem(childItem, parentItem); } /** * Removes the given child item at from the given parent item. * * @param parentItem the item from which to remove the given child item. * @param index the index of the item to remove. * @throws IllegalStateException if the given child or parent item is not in the model. */ public void removeItemFromItem(SourceListItem parentItem, int index) { validateItemIsInModel(parentItem); SourceListItem itemRemoved = parentItem.removeItem(index); fireItemRemovedFromItem(itemRemoved, parentItem); } // Utility methods. /////////////////////////////////////////////////////////////////////////// private PropertyChangeListener createSourceListItemListener() { return new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { SourceListItem item = (SourceListItem) event.getSource(); fireItemChanged(item); } }; } private void validateCategoryIsInModel(SourceListCategory category) { if (!fCategories.contains(category)) { throw new IllegalArgumentException( "The " + category.getText() + " category is not part of this model."); } } /** * Checks if the given {@link com.explodingpixels.macwidgets.SourceListItem} is in this model. * * @param item the item to check if is in this model. * @throws IllegalArgumentException if the given item is not part of this model. */ public void validateItemIsInModel(SourceListItem item) { boolean found = false; for (SourceListCategory category : fCategories) { found = category.containsItem(item); if (found) { break; } } if (!found) { throw new IllegalArgumentException("The given item is not part of this model."); } } // SourceListModelListener support. /////////////////////////////////////////////////////////// private void fireCategoryAdded(SourceListCategory category, int index) { for (SourceListModelListener listener : fListeners) { listener.categoryAdded(category, index); } } private void fireCategoryRemoved(SourceListCategory category) { for (SourceListModelListener listener : fListeners) { listener.categoryRemoved(category); } } private void fireItemAddedToCategory(SourceListItem item, SourceListCategory category, int index) { for (SourceListModelListener listener : fListeners) { listener.itemAddedToCategory(item, category, index); } } private void fireItemRemovedFromCategory(SourceListItem item, SourceListCategory category) { for (SourceListModelListener listener : fListeners) { listener.itemRemovedFromCategory(item, category); } } private void fireItemAddedToItem(SourceListItem childItem, SourceListItem parentItem, int index) { for (SourceListModelListener listener : fListeners) { listener.itemAddedToItem(childItem, parentItem, index); } } private void fireItemRemovedFromItem(SourceListItem childItem, SourceListItem parentItem) { for (SourceListModelListener listener : fListeners) { listener.itemRemovedFromItem(childItem, parentItem); } } private void fireItemChanged(SourceListItem item) { for (SourceListModelListener listener : fListeners) { listener.itemChanged(item); } } /** * Adds the given {@link SourceListModelListener} to the list of listeners. * * @param listener the listener to add. */ public void addSourceListModelListener(SourceListModelListener listener) { fListeners.add(listener); } /** * Removes the given {@link SourceListModelListener} from the list of listeners. * * @param listener the listener to remove. */ public void removeSourceListModelListener(SourceListModelListener listener) { fListeners.remove(listener); } }