001/* 002 * $Id$ 003 * 004 * Copyright 2009 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 */ 022package org.jdesktop.swingx.hyperlink; 023 024import java.awt.Desktop; 025import java.awt.GraphicsEnvironment; 026import java.awt.HeadlessException; 027import java.awt.Desktop.Action; 028import java.awt.event.ActionEvent; 029import java.io.IOException; 030import java.net.URI; 031import java.util.logging.Logger; 032 033/** 034 * A implementation wrapping <code>Desktop</code> actions BROWSE and MAIL, that is 035 * URI-related. 036 * 037 * @author Jeanette Winzenburg 038 */ 039public class HyperlinkAction extends AbstractHyperlinkAction<URI> { 040 @SuppressWarnings("unused") 041 private static final Logger LOG = Logger.getLogger(HyperlinkAction.class 042 .getName()); 043 044 private Action desktopAction; 045 private URIVisitor visitor; 046 047 /** 048 * Factory method to create and return a HyperlinkAction for the given uri. Tries 049 * to guess the appropriate type from the uri. If uri is not null and has a 050 * scheme of mailto, create one of type Mail. In all other cases, creates one 051 * for BROWSE. 052 * 053 * @param uri to uri to create a HyperlinkAction for, maybe null. 054 * @return a HyperlinkAction for the given URI. 055 * @throws HeadlessException if {@link 056 * GraphicsEnvironment#isHeadless()} returns {@code true} 057 * @throws UnsupportedOperationException if the current platform doesn't support 058 * Desktop 059 */ 060 public static HyperlinkAction createHyperlinkAction(URI uri) { 061 Action type = isMailURI(uri) ? Action.MAIL : Action.BROWSE; 062 return createHyperlinkAction(uri, type); 063 } 064 065 /** 066 * Creates and returns a HyperlinkAction with the given target and action type. 067 * @param uri the target uri, maybe null. 068 * @param desktopAction the type of desktop action this class should perform, must be 069 * BROWSE or MAIL 070 * @return a HyperlinkAction 071 * @throws HeadlessException if {@link 072 * GraphicsEnvironment#isHeadless()} returns {@code true} 073 * @throws UnsupportedOperationException if the current platform doesn't support 074 * Desktop 075 * @throws IllegalArgumentException if unsupported action type 076 */ 077 public static HyperlinkAction createHyperlinkAction(URI uri, Action type) { 078 return new HyperlinkAction(uri, type); 079 } 080 081 /** 082 * @param uri 083 * @return 084 */ 085 private static boolean isMailURI(URI uri) { 086 return uri != null && "mailto".equalsIgnoreCase(uri.getScheme()); 087 } 088 089 /** 090 * Instantiates a HyperlinkAction with action type BROWSE. 091 * 092 * @throws HeadlessException if {@link 093 * GraphicsEnvironment#isHeadless()} returns {@code true} 094 * @throws UnsupportedOperationException if the current platform doesn't support 095 * Desktop 096 * @throws IllegalArgumentException if unsupported action type 097 */ 098 public HyperlinkAction() { 099 this(Action.BROWSE); 100 } 101 102 /** 103 * Instantiates a HyperlinkAction with the given action type. 104 * 105 * @param desktopAction the type of desktop action this class should perform, must be 106 * BROWSE or MAIL 107 * @throws HeadlessException if {@link 108 * GraphicsEnvironment#isHeadless()} returns {@code true} 109 * @throws UnsupportedOperationException if the current platform doesn't support 110 * Desktop 111 * @throws IllegalArgumentException if unsupported action type 112 */ 113 public HyperlinkAction(Action desktopAction) { 114 this(null, desktopAction); 115 } 116 117 /** 118 * 119 * @param uri the target uri, maybe null. 120 * @param desktopAction the type of desktop action this class should perform, must be 121 * BROWSE or MAIL 122 * @throws HeadlessException if {@link 123 * GraphicsEnvironment#isHeadless()} returns {@code true} 124 * @throws UnsupportedOperationException if the current platform doesn't support 125 * Desktop 126 * @throws IllegalArgumentException if unsupported action type 127 */ 128 public HyperlinkAction(URI uri, Action desktopAction) { 129 super(); 130 if (!Desktop.isDesktopSupported()) { 131 throw new UnsupportedOperationException("Desktop API is not " + 132 "supported on the current platform"); 133 } 134 if (desktopAction != Desktop.Action.BROWSE && desktopAction != Desktop.Action.MAIL) { 135 throw new IllegalArgumentException("Illegal action type: " + desktopAction + 136 ". Must be BROWSE or MAIL"); 137 } 138 this.desktopAction = desktopAction; 139 getURIVisitor(); 140 setTarget(uri); 141 } 142 143 /** 144 * {@inheritDoc} <p> 145 * 146 * Implemented to perform the appropriate Desktop action if supported on the current 147 * target. Sets the visited property to true if the desktop action doesn't throw 148 * an exception or to false if it did. 149 * 150 * Does nothing if the action isn't supported. 151 */ 152 @Override 153 public void actionPerformed(ActionEvent e) { 154 if (!getURIVisitor().isEnabled(getTarget())) return; 155 try { 156 getURIVisitor().visit(getTarget()); 157 setVisited(true); 158 } catch (IOException e1) { 159 setVisited(false); 160 LOG.fine("cant visit Desktop " + e); 161 } 162 } 163 164 /** 165 * @return 166 */ 167 public Action getDesktopAction() { 168 return desktopAction; 169 } 170 171 172 @Override 173 protected void installTarget() { 174 // doohh ... this is called from super's constructor before we are 175 // fully initialized 176 if (visitor == null) return; 177 super.installTarget(); 178 updateEnabled(); 179 } 180 181 /** 182 * 183 */ 184 private void updateEnabled() { 185 setEnabled(getURIVisitor().isEnabled(getTarget())); 186 } 187 188 /** 189 * @return 190 */ 191 private URIVisitor getURIVisitor() { 192 if (visitor == null) { 193 visitor = createURIVisitor(); 194 } 195 return visitor; 196 } 197 198 /** 199 * @return 200 */ 201 private URIVisitor createURIVisitor() { 202 return getDesktopAction() == Action.BROWSE ? 203 new BrowseVisitor() : new MailVisitor(); 204 } 205 206 /** 207 * Thin wrapper around Desktop functionality to allow uniform handling of 208 * different actions in HyperlinkAction. 209 * 210 */ 211 private abstract class URIVisitor { 212 protected boolean desktopSupported = Desktop.isDesktopSupported(); 213 214 /** 215 * Returns a boolean indicating whether the action is supported on the 216 * given URI. This implementation returns true if both the Desktop is 217 * generally supported and <code>isActionSupported()</code>. 218 * 219 * PENDING JW: hmm ... which class exactly has to check for valid combination 220 * of Action and URI? 221 * 222 * @param uri 223 * @return 224 * 225 * @see #isActionSupported() 226 */ 227 public boolean isEnabled(URI uri) { 228 return desktopSupported && isActionSupported(); 229 } 230 231 /** 232 * Visits the given URI via Desktop functionality. Must not be called if not 233 * enabled. 234 * 235 * @param uri the URI to visit 236 * @throws IOException if the Desktop method throws IOException. 237 * 238 */ 239 public abstract void visit(URI uri) throws IOException; 240 241 /** 242 * Returns a boolean indicating if the action is supported by the current 243 * Desktop. 244 * 245 * @return true if the Action is supported by the current desktop, false 246 * otherwise. 247 */ 248 protected abstract boolean isActionSupported(); 249 } 250 251 private class BrowseVisitor extends URIVisitor { 252 253 /** 254 * {@inheritDoc} <p> 255 * 256 * Implemented to message the browse method of Desktop. 257 */ 258 @Override 259 public void visit(URI uri) throws IOException { 260 Desktop.getDesktop().browse(uri); 261 } 262 263 /** 264 * {@inheritDoc} <p> 265 * 266 * Implemented to query the Desktop for support of BROWSE action. 267 */ 268 @Override 269 protected boolean isActionSupported() { 270 return Desktop.getDesktop().isSupported(Desktop.Action.BROWSE); 271 } 272 273 /** 274 * {@inheritDoc} <p> 275 * 276 * Implemented to guard against null URI in addition to super. 277 */ 278 @Override 279 public boolean isEnabled(URI uri) { 280 return uri != null && super.isEnabled(uri); 281 } 282 283 284 } 285 286 private class MailVisitor extends URIVisitor { 287 288 /** 289 * {@inheritDoc} <p> 290 * 291 * Implemented to message the mail function of Desktop. 292 */ 293 @Override 294 public void visit(URI uri) throws IOException { 295 if (uri == null) { 296 Desktop.getDesktop().mail(); 297 } else { 298 Desktop.getDesktop().mail(uri); 299 } 300 } 301 /** 302 * {@inheritDoc} <p> 303 * 304 * Implemented to query the Desktop for support of MAIL action. 305 */ 306 @Override 307 protected boolean isActionSupported() { 308 return Desktop.getDesktop().isSupported(Desktop.Action.MAIL); 309 } 310 311 } 312}