/* * $Id$ * * 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(); } }