001    /*
002     * $Id: JXHyperlink.java,v 1.11 2006/03/28 15:22:18 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.Color;
024    import java.awt.event.ActionEvent;
025    import java.beans.PropertyChangeEvent;
026    import java.beans.PropertyChangeListener;
027    
028    import javax.swing.Action;
029    import javax.swing.JButton;
030    
031    import org.jdesktop.swingx.action.LinkAction;
032    import org.jdesktop.swingx.plaf.JXHyperlinkAddon;
033    import org.jdesktop.swingx.plaf.LookAndFeelAddons;
034    
035    /**
036     * A hyperlink component that derives from JButton to provide compatibility
037     * mostly for binding actions enabled/disabled behavior accesilibity i18n etc...
038     * <p>
039     *
040     * This button has visual state related to a notion of "clicked": 
041     * foreground color is unclickedColor or clickedColor depending on 
042     * its boolean bound property clicked being false or true, respectively.
043     * If the hyperlink has an action, it guarantees to synchronize its 
044     * "clicked" state to an action value with key LinkAction.VISITED_KEY. 
045     * Synchronization happens on setAction() and on propertyChange notification
046     * from the action. JXHyperlink accepts any type of action - 
047     * {@link LinkAction} is a convenience implementation to
048     * simplify clicked control.
049     * <p>
050     * 
051     * <pre> <code>
052     *      LinkAction linkAction = new LinkAction("http://swinglabs.org") {
053     *            public void actionPerformed(ActionEvent e) {
054     *                doSomething(getTarget());
055     *                setVisited(true);
056     *            }
057     *      };
058     *      JXHyperlink hyperlink = new JXHyperlink(linkAction);
059     * <code> </pre>
060     * 
061     * The hyperlink can be configured to always update its clicked 
062     * property after firing the actionPerformed:
063     * 
064     * <pre> <code>
065     *      JXHyperlink hyperlink = new JXHyperlink(action);
066     *      hyperlink.setOverrulesActionOnClick(true);
067     * <code> </pre>
068     * 
069     * By default, this property is false. The hyperlink will 
070     * auto-click only if it has no action. Developers can change the
071     * behaviour by overriding {@link JXHyperlink#isAutoSetClicked()};
072     * 
073     * 
074     * 
075     * @author Richard Bair
076     * @author Shai Almog
077     * @author Jeanette Winzenburg
078     */
079    public class JXHyperlink extends JButton {
080    
081        /**
082         * @see #getUIClassID
083         * @see #readObject
084         */
085        public static final String uiClassID = "HyperlinkUI";
086    
087        // ensure at least the default ui is registered
088        static {
089          LookAndFeelAddons.contribute(new JXHyperlinkAddon());
090        }
091    
092        private boolean hasBeenVisited = false;
093    
094        /**
095         * Color for the hyper link if it has not yet been clicked. This color can
096         * be set both in code, and through the UIManager with the property
097         * "JXHyperlink.unclickedColor".
098         */
099        private Color unclickedColor = new Color(0, 0x33, 0xFF);
100    
101        /**
102         * Color for the hyper link if it has already been clicked. This color can
103         * be set both in code, and through the UIManager with the property
104         * "JXHyperlink.clickedColor".
105         */
106        private Color clickedColor = new Color(0x99, 0, 0x99);
107    
108        private boolean overrulesActionOnClick;
109    
110        /**
111         * Creates a new instance of JXHyperlink with default parameters
112         */
113        public JXHyperlink() {
114            this(null);
115        }
116    
117        /**
118         * Creates a new instance of JHyperLink and configures it from provided Action.
119         *
120         * @param action Action whose parameters will be borrowed to configure newly 
121         *        created JXHyperLink
122         */
123        public JXHyperlink(Action action) {
124            super();
125            setAction(action);
126            init();
127        }
128    
129        /**
130         * @return Color for the hyper link if it has not yet been clicked.
131         */
132        public Color getUnclickedColor() {
133            return unclickedColor;
134        }
135    
136        /**
137         * Sets the color for the previously not visited link. This value will override the one
138         * set by the "JXHyperlink.unclickedColor" UIManager property and defaults.
139         *
140         * @param color Color for the hyper link if it has not yet been clicked.
141         */
142        public void setClickedColor(Color color) {
143            Color old = getClickedColor();
144            clickedColor = color;
145            if (isClicked()) {
146                setForeground(getClickedColor());
147            }
148            firePropertyChange("clickedColor", old, getClickedColor());
149        }
150    
151        /**
152         * @return Color for the hyper link if it has already been clicked.
153         */
154        public Color getClickedColor() {
155            return clickedColor;
156        }
157    
158        /**
159         * Sets the color for the previously visited link. This value will override the one
160         * set by the "JXHyperlink.clickedColor" UIManager property and defaults.
161         *
162         * @param color Color for the hyper link if it has already been clicked.
163         */
164        public void setUnclickedColor(Color color) {
165            Color old = getUnclickedColor();
166            unclickedColor = color;
167            if (!isClicked()) {
168                setForeground(getUnclickedColor());
169            }
170            firePropertyChange("unclickedColor", old, getUnclickedColor());
171        }
172    
173        /**
174         * Sets the clicked property and updates visual state depending
175         * on clicked.
176         * Here: the dependent visual state is the foreground color.
177         * NOTE: as with all  button's visual properties, this will not update 
178         * the backing action's "visited" state.
179         * 
180         * @param clicked flag to indicate if the button should be regarded
181         *    as having been clicked or not.
182         */
183        public void setClicked(boolean clicked) {
184            boolean old = isClicked();
185            hasBeenVisited = clicked;
186            setForeground(isClicked() ? getClickedColor() : getUnclickedColor());
187            firePropertyChange("clicked", old, isClicked());
188       }
189    
190        /**
191         * @return <code>true</code> if hyper link has already been clicked.
192         */
193        public boolean isClicked() {
194            return hasBeenVisited;
195        }
196    
197        /**
198         * Control auto-click property. 
199         * 
200         * @param overrule if true, fireActionPerformed will set clicked to true
201         *   independent of action.
202         * 
203         */
204        public void setOverrulesActionOnClick(boolean overrule) {
205            boolean old = getOverrulesActionOnClick();
206            this.overrulesActionOnClick = overrule;
207            firePropertyChange("overrulesActionOnClick", old, getOverrulesActionOnClick());
208        }
209        
210        /**
211         * Returns whether the clicked property should be set always on clicked.
212         * 
213         * Defaults to false.
214         * 
215         * @return overrulesActionOnClick 
216         */
217        public boolean getOverrulesActionOnClick() {
218            return overrulesActionOnClick;
219        }
220    
221        /**
222         * override to control auto-clicked. 
223         */
224        @Override
225        protected void fireActionPerformed(ActionEvent event) {
226            super.fireActionPerformed(event);
227            if (isAutoSetClicked()) {
228                setClicked(true);
229            }
230        }
231    
232        /**
233         * Decides auto-setting of clicked property after firing action events.
234         * Here: true if no action or overrulesAction property is true.
235         * @return true if fireActionEvent should force a clicked, false if not.
236         */
237        protected boolean isAutoSetClicked() {
238            return getAction() == null || getOverrulesActionOnClick();
239        }
240    
241        /**
242         * Create listener that will watch the changes of the provided <code>Action</code>
243         * and will update JXHyperlink's properties accordingly.
244         */
245        protected PropertyChangeListener createActionPropertyChangeListener(
246                final Action a) {
247            final PropertyChangeListener superListener = super
248                    .createActionPropertyChangeListener(a);
249            // JW: need to do something better - only weak refs allowed!
250            // no way to hook into super
251            PropertyChangeListener l = new PropertyChangeListener() {
252    
253                public void propertyChange(PropertyChangeEvent evt) {
254                    if (LinkAction.VISITED_KEY.equals(evt.getPropertyName())) {
255                        configureClickedPropertyFromAction(a);
256                    } else {
257                        superListener.propertyChange(evt);
258                    }
259    
260                }
261    
262            };
263            return l;
264        }
265    
266        /**
267         * Read all the essentional properties from the provided <code>Action</code>
268         * and apply it to the <code>JXHyperlink</code>
269         */
270        @Override
271        protected void configurePropertiesFromAction(Action a) {
272            super.configurePropertiesFromAction(a);
273            configureClickedPropertyFromAction(a);
274        }
275    
276        private void configureClickedPropertyFromAction(Action a) {
277            boolean clicked = false;
278            if (a != null) {
279                clicked = Boolean.TRUE.equals(a.getValue(LinkAction.VISITED_KEY));
280                
281            }
282            setClicked(clicked);
283        }
284    
285        private void init() {
286            setForeground(isClicked() ? getClickedColor() : getUnclickedColor());
287        }
288    
289        /**
290         * Returns a string that specifies the name of the L&F class
291         * that renders this component.
292         */
293        public String getUIClassID() {
294            return uiClassID;
295        }
296    }