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 }