001/*
002 * $Id: HyperlinkProvider.java 3927 2011-02-22 16:34:11Z kleopatra $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021package org.jdesktop.swingx.renderer;
022
023import java.awt.Point;
024import java.awt.event.ActionEvent;
025
026import org.jdesktop.swingx.JXHyperlink;
027import org.jdesktop.swingx.hyperlink.AbstractHyperlinkAction;
028import org.jdesktop.swingx.rollover.RolloverProducer;
029import org.jdesktop.swingx.rollover.RolloverRenderer;
030
031/**
032 * Renderer for hyperlinks". <p>
033 * 
034 * The renderer is configured with a LinkAction<T>. 
035 * It's mostly up to the developer to guarantee that the all
036 * values which are passed into the getXXRendererComponent(...) are
037 * compatible with T: she can provide a runtime class to check against.
038 * If it isn't the renderer will configure the
039 * action with a null target. <p>
040 * 
041 * It's recommended to not use the given Action anywhere else in code,
042 * as it is updated on each getXXRendererComponent() call which might
043 * lead to undesirable side-effects. <p>
044 * 
045 * Internally uses JXHyperlink as rendering component. <p>
046 * 
047 * PENDING: can go from ButtonProvider? <p>
048 * 
049 * PENDING: make renderer respect selected cell state. <p>
050 * 
051 * PENDING: TreeCellRenderer has several issues <p>
052 * <ol>
053 *   <li> no icons
054 *   <li> usual background highlighter issues
055 * </ol>  
056 * 
057 * @author Jeanette Winzenburg
058 */
059public class HyperlinkProvider
060   extends ComponentProvider<JXHyperlink> implements
061         RolloverRenderer {
062
063
064    private AbstractHyperlinkAction<Object> linkAction;
065    protected Class<?> targetClass;
066
067    /**
068     * Instantiate a LinkRenderer with null LinkAction and null
069     * targetClass.
070     *
071     */
072    public HyperlinkProvider() {
073        this(null, null);
074    }
075
076    /**
077     * Instantiate a LinkRenderer with the LinkAction to use with
078     * target values. 
079     * 
080     * @param linkAction the action that acts on values.
081     */
082    public HyperlinkProvider(AbstractHyperlinkAction linkAction) {
083        this(linkAction, null);
084    }
085    
086    /**
087     * Instantiate a LinkRenderer with a LinkAction to use with
088     * target values and the type of values the action can cope with. <p>
089     * 
090     * It's up to developers to take care of matching types.
091     * 
092     * @param linkAction the action that acts on values.
093     * @param targetClass the type of values the action can handle.
094     */
095    public HyperlinkProvider(AbstractHyperlinkAction linkAction, Class<?> targetClass) {
096        super();
097//        rendererComponent.addActionListener(createEditorActionListener());
098        setLinkAction(linkAction, targetClass);
099    }
100    
101    /**
102     * Sets the class the action is supposed to handle. <p>
103     * 
104     * PENDING: make sense to set independently of LinkAction?
105     * 
106     * @param targetClass the type of values the action can handle.
107     */
108    public void setTargetClass(Class<?> targetClass) {
109        this.targetClass = targetClass;
110    }
111
112    /**
113     * Sets the LinkAction for handling the values. <p>
114     * 
115     * The action is assumed to be able to cope with any type, that is
116     * this method is equivalent to setLinkAction(linkAction, null).
117     * 
118     * @param linkAction
119     */
120    public void setLinkAction(AbstractHyperlinkAction linkAction) {
121        setLinkAction(linkAction, null);
122    }
123    
124    /**
125     * Sets the LinkAction for handling the values and the 
126     * class the action can handle. <p>
127     * 
128     * PENDING: in the general case this is not independent of the
129     * targetClass. Need api to set them combined?
130     * 
131     * @param linkAction
132     */
133    public void setLinkAction(AbstractHyperlinkAction linkAction, Class<?> targetClass) {
134        if (linkAction == null) {
135            linkAction = createDefaultLinkAction();
136        }
137        setTargetClass(targetClass); 
138        this.linkAction = linkAction;
139        rendererComponent.setAction(linkAction);
140        
141    }
142    /**
143     * decides if the given target is acceptable for setTarget.
144     * <p>
145     *  
146     *  target == null is acceptable for all types.
147     *  targetClass == null is the same as Object.class
148     *  
149     * @param target the target to set.
150     * @return true if setTarget can cope with the object, 
151     *  false otherwise.
152     * 
153     */
154    public  boolean isTargetable(Object target) {
155        // we accept everything
156        if (targetClass == null) return true;
157        if (target == null) return true;
158        return targetClass.isAssignableFrom(target.getClass());
159    }
160
161
162
163    /** 
164     * default action - does nothing... except showing the target.
165     * 
166     * @return a default LinkAction for showing the target.
167     */
168    protected AbstractHyperlinkAction createDefaultLinkAction() {
169        return new AbstractHyperlinkAction<Object>(null) {
170
171            @Override
172            public void actionPerformed(ActionEvent e) {
173                // TODO Auto-generated method stub
174                
175            }
176            
177        };
178    }
179
180//----------------------- Implement RolloverRenderer
181    
182    @Override
183    public boolean isEnabled() {
184        return true;
185    }
186
187    @Override
188    public void doClick() {
189        rendererComponent.doClick();
190    }
191    
192//------------------------ ComponentProvider 
193    
194    /**
195     * {@inheritDoc} <p>
196     * 
197     * PENDING JW: Needs to be overridden - doesn't comply to contract!. Not sure
198     * how to do it without disturbing the hyperlinks current setting?
199     * All hyperlink properties are defined by the LinkAction configured
200     * with the target ...  
201     */
202    @Override
203    public String getString(Object value) {
204        if (isTargetable(value)) {
205            Object oldTarget = linkAction.getTarget();
206            linkAction.setTarget(value);
207            String text = linkAction.getName();
208            linkAction.setTarget(oldTarget);
209            return text;
210        }
211        return super.getString(value);
212    }
213
214   
215
216    /**
217     * {@inheritDoc} <p>
218     * 
219     * Overridden to set the hyperlink's rollover state. 
220     */
221    @Override
222    protected void configureState(CellContext context) {
223//        rendererComponent.setHorizontalAlignment(getHorizontalAlignment());
224        if (context.getComponent() !=  null) {
225            Point p = (Point) context.getComponent()
226                    .getClientProperty(RolloverProducer.ROLLOVER_KEY);
227            if (/*hasFocus || */(p != null && (p.x >= 0) && 
228                    (p.x == context.getColumn()) && (p.y == context.getRow()))) {
229                if (!rendererComponent.getModel().isRollover())
230                 rendererComponent.getModel().setRollover(true);
231            } else {
232                if (rendererComponent.getModel().isRollover())
233                 rendererComponent.getModel().setRollover(false);
234            }
235        }
236    }
237
238    /**
239     * {@inheritDoc}
240     * 
241     * Overridden to set the LinkAction's target to the context's value, if 
242     * targetable.<p>
243     * 
244     * Forces foreground color to the one defined by hyperlink for unselected
245     * cells, doesn't change the foreground for selected (as darkish text on dark selection
246     * background might be unreadable, Issue #840-swingx). Not entirely safe because
247     * the unselected background might be dark as well. Need to find a better way in
248     * the long run. Until then, client code can use Highlighters to repair 
249     * (which is nasty!). <p>
250     * 
251     * PENDING JW: by-passes XXValues - state currently is completely defined by
252     * the action. Hmm ... 
253     * 
254     */
255    @Override
256    protected void format(CellContext context) {
257        Object value = context.getValue();
258        if (isTargetable(value)) {
259            linkAction.setTarget(value);
260        } else {
261            linkAction.setTarget(null);
262        }
263        // hmm... the hyperlink should do this automatically..
264        // Issue #840-swingx: hyperlink unreadable if selected (for dark selection colors)
265        // so we only force clicked/unclicked if unselected 
266        if (!context.isSelected()) {
267            rendererComponent.setForeground(linkAction.isVisited() ? 
268                rendererComponent.getClickedColor() : rendererComponent.getUnclickedColor());
269        } else {
270            // JW: workaround #845-swingx which was introduced by fixing #840
271            // if we interfere with the colors, need to do always. Not quite understood
272            rendererComponent.setForeground(context.getSelectionForeground());
273        }
274    }
275
276    /**
277     * {@inheritDoc}
278     */
279    @Override
280    protected JXHyperlink createRendererComponent() {
281        return new JXRendererHyperlink();
282    }
283
284
285}