// BlogBridge -- RSS feed reader, manager, and web based service
// Copyright (C) 2002-2006 by R. Pito Salas
//
// This program is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software Foundation;
// either version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with this program;
// if not, write to the Free Software Foundation, Inc., 59 Temple Place,
// Suite 330, Boston, MA 02111-1307 USA
//
// Contact: R. Pito Salas
// mailto:pitosalas@users.sourceforge.net
// More information: about BlogBridge
// http://www.blogbridge.com
// http://sourceforge.net/projects/blogbridge
//
// $Id: GuidesListModel.java,v 1.12 2007/10/04 09:55:07 spyromus Exp $
//
package com.salas.bb.core;
import com.salas.bb.domain.*;
import com.salas.bb.domain.prefs.UserPreferences;
import com.salas.bb.domain.utils.DomainAdapter;
import com.salas.bb.utils.IdentityList;
import com.salas.bb.utils.uif.UifUtilities;
import javax.swing.*;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
/**
* Adapter between guides set and the guides list model.
* Guides set is the source of guides. This model monitors
* the set it's assigned to for changes in the guides list
* and guides themselves. Changes in guides aren't monitored
* (so far) and must be delievered externally.
*/
public class GuidesListModel extends AbstractListModel
implements IGuidesSetListener, IDisplayModeManagerListener
{
/** Set this to <code>TRUE</code> when unit-testing. */
public boolean testing;
private final List<IGuide> guides;
private final GuideDisplayModeManager dmm;
private final DomainListener domainListener;
private final UserPreferencesListener userPreferencesListener;
private final ControllerListener controllerListener;
private GuidesSet set;
/**
* When a batch of new guides is added, before the last one, this property
* contains <code>TRUE</code> if there were any visible guides.
*/
private boolean addedGuidesBatchHasVisible;
/**
* Adapts the set to list model.
*/
public GuidesListModel()
{
guides = new IdentityList<IGuide>();
dmm = GuideDisplayModeManager.getInstance();
dmm.addListener(this);
domainListener = new DomainListener();
userPreferencesListener = new UserPreferencesListener();
controllerListener = new ControllerListener();
}
/**
* Registers the guide set to monitor and reflect.
*
* @param set set.
*/
public void setGuidesSet(GuidesSet set)
{
if (this.set != null)
{
set.removeListener(this);
int gc = guides.size();
guides.clear();
fireIntervalRemoved(this, 0, gc);
}
this.set = set;
if (set != null)
{
reloadGuidesSet();
set.addListener(this);
}
}
/**
* Returns the domain listener for subscriptions.
*
* @return listener.
*/
public DomainListener getDomainListener()
{
return domainListener;
}
/**
* Returns user preferences listener object.
*
* @return listener.
*/
public UserPreferencesListener getUserPreferencesListener()
{
return userPreferencesListener;
}
/**
* Returns the controller listener.
*
* @return listener.
*/
public ControllerListener getControllerListener()
{
return controllerListener;
}
private void reloadGuidesSet()
{
int lpos = 0;
int size = getSize();
if (set != null)
{
IGuide selected = GlobalModel.SINGLETON == null ? null : GlobalModel.SINGLETON.getSelectedGuide();
for (int i = 0; i < set.getGuidesCount(); i++)
{
IGuide guide = set.getGuideAt(i);
if (selected == guide || dmm.isVisible(guide))
{
if (lpos < size)
{
setGuide(lpos, guide);
} else
{
addGuide(guide);
}
lpos++;
} else
{
removeGuide(guide);
}
}
}
// Remove the remainder
int from = lpos;
int to = guides.size() - 1;
if (to - from >= 0)
{
for (int i = guides.size() - 1; i >= lpos; i--)
{
guides.remove(i);
}
fireIntervalRemoved(this, from, to);
}
}
/**
* Returns the value at the specified index.
*
* @param index the requested index
*
* @return the value at <code>index</code>
*/
public Object getElementAt(int index)
{
return guides.get(index);
}
/**
* Returns the length of the list.
*
* @return the length of the list
*/
public int getSize()
{
return guides.size();
}
// ------------------------------------------------------------------------
// Model review methods
// ------------------------------------------------------------------------
/**
* Invoked when a new guide is added.
*
* @param batchHasVisibleGuides <code>TRUE</code> if batch had visible guides.
*/
public void onGuideAdded(boolean batchHasVisibleGuides)
{
if (batchHasVisibleGuides) reloadGuidesSet();
}
/**
* Invoked when a guide is removed.
*
* @param guide guide.
*/
public void onGuideRemoved(IGuide guide)
{
if (guides.contains(guide)) removeGuide(guide);
}
/**
* Invoked when a guide is moved.
*
* @param guide guide.
* @param oldIndex old index.
* @param newIndex new index.
*/
public void onGuideMoved(IGuide guide, int oldIndex, int newIndex)
{
if (guides.contains(guide))
{
reloadGuidesSet();
}
}
/**
* Invoked when a guide is updated.
*
* @param guide guide.
*/
public void onGuideUpdated(IGuide guide)
{
}
/**
* Invoked when guide contents are updated and the guide has to
* be reviewed.
*
* @param guide guide.
*/
public void onGuideContentsUpdated(IGuide guide)
{
boolean curVisible = guides.contains(guide);
boolean shoVisible = GlobalModel.SINGLETON.getSelectedGuide() == guide || dmm.isVisible(guide);
if (curVisible && !shoVisible) removeGuide(guide);
else if (!curVisible && shoVisible) reloadGuidesSet();
}
/**
* Invoked when color of some class changes.
*
* @param cl class.
* @param oldColor old color value.
* @param newColor new color value.
*/
public void onClassColorChanged(int cl, Color oldColor, Color newColor)
{
// Ignore options change if visibility isn't affected (nothing has changed
// from or to hidden state)
if (oldColor != null && newColor != null) return;
reloadGuidesSet();
}
// ------------------------------------------------------------------------
// Supplementary functions
// ------------------------------------------------------------------------
/**
* Returns index of the guide.
*
* @param guide guide.
*
* @return index.
*/
public int indexOf(IGuide guide)
{
return guides.indexOf(guide);
}
private void setGuide(int pos, IGuide guide)
{
if (guides.get(pos) != guide)
{
guides.set(pos, guide);
fireContentsChanged(this, pos, pos);
}
}
private void addGuide(IGuide guide)
{
int index = guides.size();
guides.add(guide);
fireIntervalAdded(this, index, index);
}
private void removeGuide(IGuide guide)
{
int index = guides.indexOf(guide);
if (index > -1)
{
guides.remove(guide);
fireIntervalRemoved(this, index, index);
}
}
// ------------------------------------------------------------------------
// Guide Set listener
// ------------------------------------------------------------------------
/**
* Invoked when new guide has been added to the set.
*
* @param set guides set.
* @param guide added guide.
* @param lastInBatch <code>TRUE</code> when this is the last even in batch.
*/
public void guideAdded(GuidesSet set, final IGuide guide, boolean lastInBatch)
{
addedGuidesBatchHasVisible |= dmm.isVisible(guide);
if (lastInBatch)
{
// Save the state and reset.
final boolean batchHasVisible = addedGuidesBatchHasVisible;
addedGuidesBatchHasVisible = false;
if (testing || UifUtilities.isEDT())
{
onGuideAdded(batchHasVisible);
} else SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
onGuideAdded(batchHasVisible);
}
});
}
}
/**
* Invoked when the guide has been removed from the set.
*
* @param set guides set.
* @param guide removed guide.
* @param index old guide index.
*/
public void guideRemoved(GuidesSet set, final IGuide guide, int index)
{
if (testing || UifUtilities.isEDT())
{
onGuideRemoved(guide);
} else SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
onGuideRemoved(guide);
}
});
}
/**
* Invoked when the guide has been moved to a new location in list.
*
* @param set guides set.
* @param guide guide which has been removed.
* @param oldIndex old guide index.
* @param newIndex new guide index.
*/
public void guideMoved(GuidesSet set, final IGuide guide,
final int oldIndex, final int newIndex)
{
if (testing || UifUtilities.isEDT())
{
onGuideMoved(guide, oldIndex, newIndex);
} else SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
onGuideMoved(guide, oldIndex, newIndex);
}
});
}
/**
* Listens to domain changes.
*/
private class DomainListener extends DomainAdapter
{
@Override
public void propertyChanged(final IFeed feed, String property, Object oldValue, Object newValue)
{
if (IFeed.PROP_UNREAD_ARTICLES_COUNT.equals(property) ||
DirectFeed.PROP_DISABLED.equals(property))
{
if (UifUtilities.isEDT())
{
reviewAllParentGuides(feed);
} else SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
reviewAllParentGuides(feed);
}
});
}
}
private void reviewAllParentGuides(IFeed feed)
{
IGuide[] pguides = feed.getParentGuides();
for (IGuide guide : pguides) onGuideContentsUpdated(guide);
}
}
/**
* Listens to important user preferences properties changes.
*/
private class UserPreferencesListener implements PropertyChangeListener
{
/**
* This method gets called when a bound property is changed.
*
* @param evt A PropertyChangeEvent object describing the event source and the property that has changed.
*/
public void propertyChange(PropertyChangeEvent evt)
{
if (!UserPreferences.FEED_VISIBILITY_PROPERTIES.contains(evt.getPropertyName())) return;
reloadGuidesSet();
}
}
/**
* Listens to controller events.
*/
private class ControllerListener extends ControllerAdapter
{
private IGuide selection;
@Override
public void guideSelected(IGuide guide)
{
if (selection != null)
{
onGuideContentsUpdated(guide);
}
selection = guide;
}
}
}