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