// 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: AbstractDisplayModeManager.java,v 1.4 2008/02/28 11:35:39 spyromus Exp $
//
package com.salas.bb.core;
import com.salas.bb.utils.StringUtils;
import com.salas.bb.utils.i18n.Strings;
import com.salas.bb.utils.uif.UifUtilities;
import java.awt.*;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
/**
* Abstract display mode manager is a foundation for all filtered display
* management.
*/
public abstract class AbstractDisplayModeManager
{
private static final Logger LOG = Logger.getLogger(AbstractDisplayModeManager.class.getName());
/**
* Listeners.
*/
protected final java.util.List<IDisplayModeManagerListener> listeners;
/**
* Map of classes (keys) to modes (values).
*/
protected final Map<Integer, Color> colors;
/**
* The list of class priorities.
*/
private final int[] classPriorities;
/**
* The prefix to use for user preferences access.
*/
private final String propertyPrefix;
/**
* Creates DMM.
*
* @param classPriorities list of classes in priority order.
* @param propertyPrefix prefix to use for user preferences access.
*/
protected AbstractDisplayModeManager(int[] classPriorities, String propertyPrefix)
{
colors = new HashMap<Integer, Color>();
listeners = new CopyOnWriteArrayList<IDisplayModeManagerListener>();
this.classPriorities = classPriorities;
this.propertyPrefix = propertyPrefix;
}
/**
* Adds listener to receive color chnage notifications.
*
* @param aListener listener.
*/
public void addListener(IDisplayModeManagerListener aListener)
{
if (!listeners.contains(aListener)) listeners.add(aListener);
}
/**
* Removes listener from the list.
*
* @param l listener.
*/
public void removeListener(IDisplayModeManagerListener l)
{
listeners.remove(l);
}
/**
* Removes all existing color mappings.
*/
public void clear()
{
synchronized (colors)
{
colors.clear();
}
}
/**
* Returns <code>TRUE</code> if the object with this class is visible according to user preferences.
*
* @param cl classes to test.
*
* @return <code>TRUE</code> if visible.
*/
public boolean isVisible(int cl)
{
return getColor(cl, false) != null;
}
/**
* Sets the color for the given class.
*
* @param cl class.
* @param color color or <code>NULL</code> for invisibility.
*/
public void setColor(int cl, Color color)
{
Color oldColor;
synchronized (colors)
{
oldColor = getColor(cl, false);
colors.put(cl, color);
}
fireColorsChanged(cl, oldColor, color);
}
/**
* Fires change in color of the class to all listeners, if colors are different.
*
* @param cl class.
* @param oldColor old color.
* @param newColor new color.
*/
private void fireColorsChanged(int cl, Color oldColor, Color newColor)
{
if (areColorsDifferent(oldColor, newColor))
{
for (IDisplayModeManagerListener listener : listeners)
{
listener.onClassColorChanged(cl, oldColor, newColor);
}
}
}
/**
* Returns <code>TRUE</code> if colors are different. <code>NULL</code>'s aren't different.
*
* @param first first color sample.
* @param second second color sample.
*
* @return <code>TRUE</code> if colors are different.
*/
private static boolean areColorsDifferent(Color first, Color second)
{
return first == null
? second != null
: second == null || !first.equals(second);
}
/**
* Stores user preferences to preferences object.
*
* @param prefs preferences to put data in.
*
* @throws IllegalArgumentException if preferences object isn't specified.
*/
public void storePreferences(Preferences prefs)
{
if (prefs == null) throw new IllegalArgumentException(Strings.error("unspecified.preferences"));
synchronized (colors)
{
for (Map.Entry<Integer, Color> mapping : colors.entrySet())
{
Integer cl = mapping.getKey();
Color color = mapping.getValue();
String prefKey = propertyPrefix + cl;
String prefValue = UifUtilities.colorToHex(color);
prefs.put(prefKey, prefValue);
}
}
}
/**
* Restores user preferences from preferences object.
*
* @param prefs preferences to read data from.
*
* @throws IllegalArgumentException if preferences object isn't specified.
*/
public void restorePreferences(Preferences prefs)
{
if (prefs == null) throw new IllegalArgumentException(Strings.error("unspecified.preferences"));
boolean cleared = false;
try
{
String[] keys = prefs.keys();
for (String key : keys)
{
if (key != null && key.startsWith(propertyPrefix))
{
if (!cleared)
{
colors.clear();
cleared = true;
}
String colorValue = prefs.get(key, null);
restoreMapping(key, colorValue);
}
}
} catch (BackingStoreException e)
{
LOG.log(Level.WARNING, Strings.error("problems.with.backing.store"), e);
}
}
/**
* Restores single color mapping.
*
* @param aKey key of mapping having class ID in format "<propertyPrefix><class_id>".
* @param aColorValue color value (null or string in format "#rrggbb").
*/
private void restoreMapping(String aKey, String aColorValue)
{
try
{
Integer cl = Integer.decode(aKey.substring(5));
if (aColorValue != null)
{
Color color = StringUtils.isEmpty(aColorValue)
? null
: Color.decode(aColorValue);
setColor(cl, color);
}
} catch (RuntimeException e)
{
// Failed to process key or value -- skip to the next color
LOG.warning(MessageFormat.format(Strings.error("failed.to.process.key"), aKey));
}
}
/**
* Returns the color for a given class.
*
* @param cl class.
*
* @return color or <code>NULL</code> if should be invisible.
*/
public Color getColor(int cl)
{
return getColor(cl, false);
}
/**
* Returns the color for a given class.
*
* @param cl class.
* @param selected TRUE when selected.
*
* @return color or <code>NULL</code> if should be invisible.
*/
public Color getColor(int cl, boolean selected)
{
Color color = getDefaultColor(selected);
int index = 0;
synchronized (colors)
{
while (color != null && index < classPriorities.length)
{
if ((cl & classPriorities[index]) != 0) color = getClassColor(classPriorities[index], selected);
index++;
}
}
return color;
}
/**
* Returns the default color.
*
* @param selected TRUE when selected.
*
* @return color.
*/
protected Color getDefaultColor(boolean selected)
{
return Color.BLACK;
}
/**
* Returns color for a single class.
*
* @param cl class.
* @param selected TRUE when selected.
*
* @return color.
*/
private Color getClassColor(int cl, boolean selected)
{
Color aColor;
if (colors.containsKey(cl))
{
aColor = colors.get(cl);
} else
{
aColor = getDefaultColor(selected);
}
return aColor;
}
}