001    /*
002     * $Id: LinkRenderer.java,v 1.22 2006/04/12 15:06:17 kleopatra Exp $
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;
022    
023    import java.awt.Component;
024    import java.awt.Point;
025    import java.awt.event.ActionEvent;
026    import java.awt.event.ActionListener;
027    
028    import javax.swing.AbstractCellEditor;
029    import javax.swing.JList;
030    import javax.swing.JTable;
031    import javax.swing.JTree;
032    import javax.swing.ListCellRenderer;
033    import javax.swing.UIManager;
034    import javax.swing.border.Border;
035    import javax.swing.border.EmptyBorder;
036    import javax.swing.table.TableCellEditor;
037    import javax.swing.table.TableCellRenderer;
038    import javax.swing.tree.TreeCellRenderer;
039    
040    import org.jdesktop.swingx.action.LinkAction;
041    
042    /**
043     * A Renderer/Editor for "Links". <p>
044     * 
045     * The renderer is configured with a LinkAction<T>. 
046     * It's mostly up to the developer to guarantee that the all
047     * values which are passed into the getXXRendererComponent(...) are
048     * compatible with T: she can provide a runtime class to check against.
049     * If it isn't the renderer will configure the
050     * action with a null target. <p>
051     * 
052     * It's recommended to not use the given Action anywhere else in code,
053     * as it is updated on each getXXRendererComponent() call which might
054     * lead to undesirable side-effects. <p>
055     * 
056     * Internally uses JXHyperlink for both CellRenderer and CellEditor
057     * It's recommended to not reuse the same instance for both functions. <p>
058     * 
059     * PENDING: make renderer respect selected cell state.
060     * 
061     * PENDING: TreeCellRenderer has several issues
062     *   - no icons
063     *   - usual background highlighter issues
064     * 
065     * @author Jeanette Winzenburg
066     */
067    public class LinkRenderer extends AbstractCellEditor implements
068            TableCellRenderer, TableCellEditor, ListCellRenderer, 
069            TreeCellRenderer, RolloverRenderer {
070    
071        private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
072    
073        private JXHyperlink linkButton;
074    
075        private LinkAction<Object> linkAction;
076        protected Class<?> targetClass;
077    
078        /**
079         * Instantiate a LinkRenderer with null LinkAction and null
080         * targetClass.
081         *
082         */
083        public LinkRenderer() {
084            this(null, null);
085        }
086    
087        /**
088         * Instantiate a LinkRenderer with the LinkAction to use with
089         * target values. 
090         * 
091         * @param linkAction the action that acts on values.
092         */
093        public LinkRenderer(LinkAction linkAction) {
094            this(linkAction, null);
095        }
096        
097        /**
098         * Instantiate a LinkRenderer with a LinkAction to use with
099         * target values and the type of values the action can cope with. <p>
100         * 
101         * It's up to developers to take care of matching types.
102         * 
103         * @param linkAction the action that acts on values.
104         * @param targetClass the type of values the action can handle.
105         */
106        public LinkRenderer(LinkAction linkAction, Class targetClass) {
107            linkButton = createHyperlink();
108            linkButton.addActionListener(createEditorActionListener());
109            setLinkAction(linkAction, targetClass);
110        }
111        
112        /**
113         * Sets the class the action is supposed to handle. <p>
114         * 
115         * PENDING: make sense to set independently of LinkAction?
116         * 
117         * @param targetClass the type of values the action can handle.
118         */
119        public void setTargetClass(Class targetClass) {
120            this.targetClass = targetClass;
121        }
122    
123        /**
124         * Sets the LinkAction for handling the values. <p>
125         * 
126         * The action is assumed to be able to cope with any type, that is
127         * this method is equivalent to setLinkAction(linkAction, null).
128         * 
129         * @param linkAction
130         */
131        public void setLinkAction(LinkAction linkAction) {
132            setLinkAction(linkAction, null);
133        }
134        
135        /**
136         * Sets the LinkAction for handling the values and the 
137         * class the action can handle. <p>
138         * 
139         * PENDING: in the general case this is not independent of the
140         * targetClass. Need api to set them combined?
141         * 
142         * @param linkAction
143         */
144        public void setLinkAction(LinkAction linkAction, Class targetClass) {
145            if (linkAction == null) {
146                linkAction = createDefaultLinkAction();
147            }
148            setTargetClass(targetClass); 
149            this.linkAction = linkAction;
150            linkButton.setAction(linkAction);
151            
152        }
153        /**
154         * decides if the given target is acceptable for setTarget.
155         * <p>
156         *  
157         *  target == null is acceptable for all types.
158         *  targetClass == null is the same as Object.class
159         *  
160         * @param target the target to set.
161         * @return true if setTarget can cope with the object, 
162         *  false otherwise.
163         * 
164         */
165        public  boolean isTargetable(Object target) {
166            // we accept everything
167            if (targetClass == null) return true;
168            if (target == null) return true;
169            return targetClass.isAssignableFrom(target.getClass());
170        }
171    
172    
173        /**
174         * creates and returns the hyperlink component used for rendering
175         * the value and activating the action on the target value.
176         * 
177         * @return the hyperlink renderer component.
178         */
179        protected JXHyperlink createHyperlink() {
180            return new JXHyperlink() {
181    
182                @Override
183                public void updateUI() {
184                    super.updateUI();
185                    setBorderPainted(true);
186                    setOpaque(true);
187                }
188                
189            };
190        }
191    
192        /** 
193         * default action - does nothing... except showing the target.
194         * 
195         * @return a default LinkAction for showing the target.
196         */
197        protected LinkAction createDefaultLinkAction() {
198            return new LinkAction<Object>(null) {
199    
200                public void actionPerformed(ActionEvent e) {
201                    // TODO Auto-generated method stub
202                    
203                }
204                
205            };
206        }
207    
208    //----------------------- Implement RolloverRenderer
209        
210        public boolean isEnabled() {
211            return true;
212        }
213    
214        public void doClick() {
215            linkButton.doClick();
216        }
217        
218    //---------------------- Implement ListCellRenderer
219        
220        public Component getListCellRendererComponent(JList list, Object value, 
221                int index, boolean isSelected, boolean cellHasFocus) {
222            if ((value != null) && !isTargetable(value)) {
223                value = null;
224            }
225            linkAction.setTarget(value);
226            if (list != null) {
227                Point p = (Point) list
228                    .getClientProperty(RolloverProducer.ROLLOVER_KEY);
229                if (/*cellHasFocus ||*/ (p != null && (p.y >= 0) && (p.y == index))) {
230                     linkButton.getModel().setRollover(true);
231                } else {
232                     linkButton.getModel().setRollover(false);
233                }
234                updateSelectionColors(list, isSelected);
235                updateFocusBorder(cellHasFocus);
236            };
237            return linkButton;
238        }
239        
240    
241        private void updateSelectionColors(JList table, boolean isSelected) {
242            if (isSelected) {
243                // linkButton.setForeground(table.getSelectionForeground());
244                linkButton.setBackground(table.getSelectionBackground());
245            } else {
246                // linkButton.setForeground(table.getForeground());
247                linkButton.setBackground(table.getBackground());
248            }
249    
250        }
251    
252    //------------------------ TableCellRenderer
253        
254        public Component getTableCellRendererComponent(JTable table, Object value,
255                boolean isSelected, boolean hasFocus, int row, int column) {
256            if ((value != null) && !isTargetable(value)) {
257                value = null;
258            }
259            linkAction.setTarget(value);
260            if (table !=  null) {
261                Point p = (Point) table
262                        .getClientProperty(RolloverProducer.ROLLOVER_KEY);
263                if (/*hasFocus || */(p != null && (p.x >= 0) && (p.x == column) && (p.y == row))) {
264                     linkButton.getModel().setRollover(true);
265                } else {
266                     linkButton.getModel().setRollover(false);
267                }
268                updateSelectionColors(table, isSelected);
269                updateFocusBorder(hasFocus);
270            }
271            return linkButton;
272        }
273    
274        private void updateSelectionColors(JTable table, boolean isSelected) {
275                if (isSelected) {
276    //                linkButton.setForeground(table.getSelectionForeground());
277                    linkButton.setBackground(table.getSelectionBackground());
278                }
279                else {
280    //                linkButton.setForeground(table.getForeground());
281                    linkButton.setBackground(table.getBackground());
282                }
283        
284        }
285    
286        private void updateFocusBorder(boolean hasFocus) {
287            if (hasFocus) {
288                linkButton.setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
289            } else {
290                linkButton.setBorder(noFocusBorder);
291            }
292    
293            
294        }
295    
296    //-------------------------- TableCellEditor
297        
298        public Component getTableCellEditorComponent(JTable table, Object value,
299                boolean isSelected, int row, int column) {
300            linkAction.setTarget(value);
301            linkButton.getModel().setRollover(true); 
302            updateSelectionColors(table, isSelected);
303            return linkButton;
304        }
305    
306        public Object getCellEditorValue() {
307            return linkAction.getTarget();
308        }
309    
310        
311     
312        @Override
313        protected void fireEditingStopped() {
314            fireEditingCanceled();
315        }
316    
317        private ActionListener createEditorActionListener() {
318            ActionListener l = new ActionListener() {
319    
320                public void actionPerformed(ActionEvent e) {
321                    cancelCellEditing();
322    
323                }
324    
325            };
326            return l;
327        }
328    
329    //----------------------- treeCellRenderer
330        
331        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean isSelected,
332                boolean expanded, boolean leaf, int row, boolean hasFocus) {
333            if ((value != null) && !isTargetable(value)) {
334                value = null;
335            }
336            linkAction.setTarget(value);
337            if (tree != null) {
338                Point p = (Point) tree
339                    .getClientProperty(RolloverProducer.ROLLOVER_KEY);
340                if (/*cellHasFocus ||*/ (p != null && (p.y >= 0) && (p.y == row))) {
341                     linkButton.getModel().setRollover(true);
342                } else {
343                     linkButton.getModel().setRollover(false);
344                }
345                updateSelectionColors(tree, isSelected);
346                updateFocusBorder(hasFocus);
347            }
348            return linkButton;
349            
350        }
351    
352        private void updateSelectionColors(JTree tree, boolean isSelected) {
353            if (isSelected) {
354    //          linkButton.setForeground(table.getSelectionForeground());
355              linkButton.setBackground(UIManager.getColor("Tree.selectionBackground"));
356          }
357          else {
358    //          linkButton.setForeground(table.getForeground());
359              linkButton.setBackground(tree.getBackground());
360          }
361            
362        }
363    
364    }