001    /*
002     * $Id: HyperlinkProvider.java 3294 2009-03-11 10:50:52Z 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     */
021    package org.jdesktop.swingx.renderer;
022    
023    import java.awt.Point;
024    import java.awt.event.ActionEvent;
025    
026    import org.jdesktop.swingx.JXHyperlink;
027    import org.jdesktop.swingx.hyperlink.AbstractHyperlinkAction;
028    import org.jdesktop.swingx.rollover.RolloverProducer;
029    import 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     */
059    public 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                public void actionPerformed(ActionEvent e) {
172                    // TODO Auto-generated method stub
173                    
174                }
175                
176            };
177        }
178    
179    //----------------------- Implement RolloverRenderer
180        
181        public boolean isEnabled() {
182            return true;
183        }
184    
185        public void doClick() {
186            rendererComponent.doClick();
187        }
188        
189    //------------------------ ComponentProvider 
190        
191        /**
192         * {@inheritDoc} <p>
193         * 
194         * PENDING JW: Needs to be overridden - doesn't comply to contract!. Not sure
195         * how to do it without disturbing the hyperlinks current setting?
196         * All hyperlink properties are defined by the LinkAction configured
197         * with the target ...  
198         */
199        @Override
200        public String getString(Object value) {
201            if (isTargetable(value)) {
202                Object oldTarget = linkAction.getTarget();
203                linkAction.setTarget(value);
204                String text = linkAction.getName();
205                linkAction.setTarget(oldTarget);
206                return text;
207            }
208            return super.getString(value);
209        }
210    
211       
212    
213        /**
214         * {@inheritDoc} <p>
215         * 
216         * Overridden to set the hyperlink's rollover state. 
217         */
218        @Override
219        protected void configureState(CellContext context) {
220    //        rendererComponent.setHorizontalAlignment(getHorizontalAlignment());
221            if (context.getComponent() !=  null) {
222                Point p = (Point) context.getComponent()
223                        .getClientProperty(RolloverProducer.ROLLOVER_KEY);
224                if (/*hasFocus || */(p != null && (p.x >= 0) && 
225                        (p.x == context.getColumn()) && (p.y == context.getRow()))) {
226                    if (!rendererComponent.getModel().isRollover())
227                     rendererComponent.getModel().setRollover(true);
228                } else {
229                    if (rendererComponent.getModel().isRollover())
230                     rendererComponent.getModel().setRollover(false);
231                }
232            }
233        }
234    
235        /**
236         * {@inheritDoc}
237         * 
238         * Overridden to set the LinkAction's target to the context's value, if 
239         * targetable.<p>
240         * 
241         * Forces foreground color to the one defined by hyperlink for unselected
242         * cells, doesn't change the foreground for selected (as darkish text on dark selection
243         * background might be unreadable, Issue #840-swingx). Not entirely safe because
244         * the unselected background might be dark as well. Need to find a better way in
245         * the long run. Until then, client code can use Highlighters to repair 
246         * (which is nasty!). <p>
247         * 
248         * PENDING JW: by-passes XXValues - state currently is completely defined by
249         * the action. Hmm ... 
250         * 
251         */
252        @Override
253        protected void format(CellContext context) {
254            Object value = context.getValue();
255            if (isTargetable(value)) {
256                linkAction.setTarget(value);
257            } else {
258                linkAction.setTarget(null);
259            }
260            // hmm... the hyperlink should do this automatically..
261            // Issue #840-swingx: hyperlink unreadable if selected (for dark selection colors)
262            // so we only force clicked/unclicked if unselected 
263            if (!context.isSelected()) {
264                rendererComponent.setForeground(linkAction.isVisited() ? 
265                    rendererComponent.getClickedColor() : rendererComponent.getUnclickedColor());
266            } else {
267                // JW: workaround #845-swingx which was introduced by fixing #840
268                // if we interfere with the colors, need to do always. Not quite understood
269                rendererComponent.setForeground(context.getSelectionForeground());
270            }
271        }
272    
273        /**
274         * {@inheritDoc}
275         */
276        @Override
277        protected JXHyperlink createRendererComponent() {
278            return new JXRendererHyperlink();
279        }
280    
281    
282    }