package org.limewire.ui.swing.nav; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.util.StringUtils; import com.google.inject.Singleton; @Singleton class NavigatorImpl implements Navigator { private static final Log LOG = LogFactory.getLog(NavigatorImpl.class); // CoW to allow listeners to remove themselves during iteration private final List<NavigationListener> listeners = new CopyOnWriteArrayList<NavigationListener>(); private final List<NavItemImpl> navItems = new ArrayList<NavItemImpl>(); private final Map<NavCategory, Integer> categoryCount = new EnumMap<NavCategory, Integer>(NavCategory.class); private final List<NavItemImpl> selectionHistory = new ArrayList<NavItemImpl>(); private NavItemImpl selectedItem; public NavigatorImpl() { for(NavCategory category : NavCategory.values()) { categoryCount.put(category, 0); } } @Override public NavItem createNavItem(NavCategory category, String id, NavMediator navMediator) { NavItemImpl item = new NavItemImpl(category, id, navMediator); addNavItem(item); return item; } @Override public NavItem getNavItem(NavCategory category, String id) { for(NavItemImpl item : navItems) { LOG.debugf("Returning NavItem for id {0} navItem{1}", id, item); if(category.equals(item.category) && id.equals(item.getId())) { return item; } } return null; } @Override public boolean hasNavItem(NavCategory category, String id) { return getNavItem(category, id) != null; } @Override public void addNavigationListener(NavigationListener itemListener) { listeners.add(itemListener); for(NavItemImpl item : navItems) { itemListener.itemAdded(item.category, item); } } @Override public void removeNavigationListener(NavigationListener itemListener) { listeners.remove(itemListener); } @Override public boolean goBack() { if(selectionHistory.size() < 2) { return false; } else { // Remove the current. selectionHistory.remove(selectionHistory.size() - 1); // Remove & get the prior. NavItem item = selectionHistory.remove(selectionHistory.size() - 1); // And select it. item.select(); return true; } } @Override public void showNothing() { if(selectedItem != null) { NavItemImpl item = selectedItem; selectedItem = null; item.fireSelected(false); for(NavigationListener listener : listeners) { listener.itemSelected(null, null, null, null); } } } private void addNavItem(NavItemImpl item) { LOG.debugf("Adding item {0}", item); navItems.add(item); for(NavigationListener listener : listeners) { listener.itemAdded(item.category, item); } categoryCount.put(item.category, categoryCount.get(item.category)+1); if(categoryCount.get(item.category) == 1) { for(NavigationListener listener : listeners) { listener.categoryAdded(item.category); } } } /** * Removes all instances of item from the history. Returns the item that * should be selected in its place, if it was selected. */ private NavItemImpl removeFromHistory(NavItemImpl item) { if(!selectionHistory.isEmpty()) { NavItemImpl priorSelection = null; boolean found = false; for(ListIterator<NavItemImpl> iter = selectionHistory.listIterator(selectionHistory.size()); iter.hasPrevious(); ) { NavItemImpl prior = iter.previous(); if(prior == item) { iter.remove(); found = true; } else if(found && priorSelection == null) { priorSelection = prior; } } return priorSelection; } else { return null; } } private void addToHistory(NavItemImpl item) { selectionHistory.add(item); if(selectionHistory.size() > 10) { selectionHistory.remove(0); } } private void removeNavItem(NavItemImpl item) { if(navItems.remove(item)) { LOG.debugf("Removed item {0}", item); NavItemImpl priorSelected = removeFromHistory(item); boolean wasSelected = (selectedItem == item); for(NavigationListener listener : listeners) { listener.itemRemoved(item.category, item, wasSelected); if(wasSelected) { listener.itemSelected(null, null, null, null); } } if(wasSelected){ selectedItem = null; item.fireSelected(false); } item.fireRemoved(wasSelected); categoryCount.put(item.category, categoryCount.get(item.category)-1); if(categoryCount.get(item.category) == 0) { for(NavigationListener listener : listeners) { listener.categoryRemoved(item.category, wasSelected); } } // if it was selected, and no listeners responded to the prior // callbacks by selecting something else, then select // the item that was previously selected. if(wasSelected && priorSelected != null && selectedItem == null) { assert priorSelected.valid; selectNavItem(priorSelected, null, false); } } else { LOG.debugf("Item {0} not contained in list.", item); } } private void selectNavItem(NavItemImpl item, NavSelectable selectable, boolean addToHistory) { if(item != selectedItem) { if(addToHistory) { addToHistory(item); } if(selectedItem != null) { selectedItem.fireSelected(false); } selectedItem = item; item.fireSelected(true); for(NavigationListener listener : listeners) { listener.itemSelected(item.category, item, selectable, item.navMediator); } } } private class NavItemImpl implements NavItem { // CoW to allow listeners to remove themselves during iteration private final List<NavItemListener> listeners = new CopyOnWriteArrayList<NavItemListener>(); private final NavCategory category; private final String id; private final NavMediator navMediator; private boolean valid = true; public NavItemImpl(NavCategory category, String id, NavMediator navMediator) { this.category = category; this.id = id; this.navMediator = navMediator; } @Override public String getId() { return id; } @Override public void remove() { valid = false; removeNavItem(this); } @Override public void select() { select(null); } @Override public void select(NavSelectable selectable) { assert valid; selectNavItem(this, selectable, true); } @Override public String toString() { return StringUtils.toString(this); } @Override public void addNavItemListener(NavItemListener listener) { listeners.add(listener); } @Override public void removeNavItemListener(NavItemListener listener) { listeners.remove(listener); } void fireSelected(boolean selected) { for(NavItemListener listener : listeners) { listener.itemSelected(selected); } } void fireRemoved(boolean wasSelected) { for(NavItemListener listener : listeners) { listener.itemRemoved(wasSelected); } } @Override public boolean isSelected() { return selectedItem == this; } } }