/*
* $Id: HyperlinkProvider.java 3927 2011-02-22 16:34:11Z kleopatra $
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx.renderer;
import java.awt.Point;
import java.awt.event.ActionEvent;
import org.jdesktop.swingx.JXHyperlink;
import org.jdesktop.swingx.hyperlink.AbstractHyperlinkAction;
import org.jdesktop.swingx.rollover.RolloverProducer;
import org.jdesktop.swingx.rollover.RolloverRenderer;
/**
* Renderer for hyperlinks". <p>
*
* The renderer is configured with a LinkAction<T>.
* It's mostly up to the developer to guarantee that the all
* values which are passed into the getXXRendererComponent(...) are
* compatible with T: she can provide a runtime class to check against.
* If it isn't the renderer will configure the
* action with a null target. <p>
*
* It's recommended to not use the given Action anywhere else in code,
* as it is updated on each getXXRendererComponent() call which might
* lead to undesirable side-effects. <p>
*
* Internally uses JXHyperlink as rendering component. <p>
*
* PENDING: can go from ButtonProvider? <p>
*
* PENDING: make renderer respect selected cell state. <p>
*
* PENDING: TreeCellRenderer has several issues <p>
* <ol>
* <li> no icons
* <li> usual background highlighter issues
* </ol>
*
* @author Jeanette Winzenburg
*/
public class HyperlinkProvider
extends ComponentProvider<JXHyperlink> implements
RolloverRenderer {
private AbstractHyperlinkAction<Object> linkAction;
protected Class<?> targetClass;
/**
* Instantiate a LinkRenderer with null LinkAction and null
* targetClass.
*
*/
public HyperlinkProvider() {
this(null, null);
}
/**
* Instantiate a LinkRenderer with the LinkAction to use with
* target values.
*
* @param linkAction the action that acts on values.
*/
public HyperlinkProvider(AbstractHyperlinkAction linkAction) {
this(linkAction, null);
}
/**
* Instantiate a LinkRenderer with a LinkAction to use with
* target values and the type of values the action can cope with. <p>
*
* It's up to developers to take care of matching types.
*
* @param linkAction the action that acts on values.
* @param targetClass the type of values the action can handle.
*/
public HyperlinkProvider(AbstractHyperlinkAction linkAction, Class<?> targetClass) {
super();
// rendererComponent.addActionListener(createEditorActionListener());
setLinkAction(linkAction, targetClass);
}
/**
* Sets the class the action is supposed to handle. <p>
*
* PENDING: make sense to set independently of LinkAction?
*
* @param targetClass the type of values the action can handle.
*/
public void setTargetClass(Class<?> targetClass) {
this.targetClass = targetClass;
}
/**
* Sets the LinkAction for handling the values. <p>
*
* The action is assumed to be able to cope with any type, that is
* this method is equivalent to setLinkAction(linkAction, null).
*
* @param linkAction
*/
public void setLinkAction(AbstractHyperlinkAction linkAction) {
setLinkAction(linkAction, null);
}
/**
* Sets the LinkAction for handling the values and the
* class the action can handle. <p>
*
* PENDING: in the general case this is not independent of the
* targetClass. Need api to set them combined?
*
* @param linkAction
*/
public void setLinkAction(AbstractHyperlinkAction linkAction, Class<?> targetClass) {
if (linkAction == null) {
linkAction = createDefaultLinkAction();
}
setTargetClass(targetClass);
this.linkAction = linkAction;
rendererComponent.setAction(linkAction);
}
/**
* decides if the given target is acceptable for setTarget.
* <p>
*
* target == null is acceptable for all types.
* targetClass == null is the same as Object.class
*
* @param target the target to set.
* @return true if setTarget can cope with the object,
* false otherwise.
*
*/
public boolean isTargetable(Object target) {
// we accept everything
if (targetClass == null) return true;
if (target == null) return true;
return targetClass.isAssignableFrom(target.getClass());
}
/**
* default action - does nothing... except showing the target.
*
* @return a default LinkAction for showing the target.
*/
protected AbstractHyperlinkAction createDefaultLinkAction() {
return new AbstractHyperlinkAction<Object>(null) {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
}
};
}
//----------------------- Implement RolloverRenderer
@Override
public boolean isEnabled() {
return true;
}
@Override
public void doClick() {
rendererComponent.doClick();
}
//------------------------ ComponentProvider
/**
* {@inheritDoc} <p>
*
* PENDING JW: Needs to be overridden - doesn't comply to contract!. Not sure
* how to do it without disturbing the hyperlinks current setting?
* All hyperlink properties are defined by the LinkAction configured
* with the target ...
*/
@Override
public String getString(Object value) {
if (isTargetable(value)) {
Object oldTarget = linkAction.getTarget();
linkAction.setTarget(value);
String text = linkAction.getName();
linkAction.setTarget(oldTarget);
return text;
}
return super.getString(value);
}
/**
* {@inheritDoc} <p>
*
* Overridden to set the hyperlink's rollover state.
*/
@Override
protected void configureState(CellContext context) {
// rendererComponent.setHorizontalAlignment(getHorizontalAlignment());
if (context.getComponent() != null) {
Point p = (Point) context.getComponent()
.getClientProperty(RolloverProducer.ROLLOVER_KEY);
if (/*hasFocus || */(p != null && (p.x >= 0) &&
(p.x == context.getColumn()) && (p.y == context.getRow()))) {
if (!rendererComponent.getModel().isRollover())
rendererComponent.getModel().setRollover(true);
} else {
if (rendererComponent.getModel().isRollover())
rendererComponent.getModel().setRollover(false);
}
}
}
/**
* {@inheritDoc}
*
* Overridden to set the LinkAction's target to the context's value, if
* targetable.<p>
*
* Forces foreground color to the one defined by hyperlink for unselected
* cells, doesn't change the foreground for selected (as darkish text on dark selection
* background might be unreadable, Issue #840-swingx). Not entirely safe because
* the unselected background might be dark as well. Need to find a better way in
* the long run. Until then, client code can use Highlighters to repair
* (which is nasty!). <p>
*
* PENDING JW: by-passes XXValues - state currently is completely defined by
* the action. Hmm ...
*
*/
@Override
protected void format(CellContext context) {
Object value = context.getValue();
if (isTargetable(value)) {
linkAction.setTarget(value);
} else {
linkAction.setTarget(null);
}
// hmm... the hyperlink should do this automatically..
// Issue #840-swingx: hyperlink unreadable if selected (for dark selection colors)
// so we only force clicked/unclicked if unselected
if (!context.isSelected()) {
rendererComponent.setForeground(linkAction.isVisited() ?
rendererComponent.getClickedColor() : rendererComponent.getUnclickedColor());
} else {
// JW: workaround #845-swingx which was introduced by fixing #840
// if we interfere with the colors, need to do always. Not quite understood
rendererComponent.setForeground(context.getSelectionForeground());
}
}
/**
* {@inheritDoc}
*/
@Override
protected JXHyperlink createRendererComponent() {
return new JXRendererHyperlink();
}
}