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