1 19 20 package org.netbeans.lib.editor.hyperlink; 21 22 import java.awt.Color ; 23 import java.awt.Cursor ; 24 import java.awt.event.InputEvent ; 25 import java.awt.event.KeyEvent ; 26 import java.awt.event.KeyListener ; 27 import java.awt.event.MouseEvent ; 28 import java.awt.event.MouseListener ; 29 import java.awt.event.MouseMotionListener ; 30 import java.beans.PropertyChangeEvent ; 31 import java.beans.PropertyChangeListener ; 32 import java.util.ArrayList ; 33 import java.util.Collections ; 34 import java.util.HashMap ; 35 import java.util.Iterator ; 36 import java.util.List ; 37 import javax.swing.SwingUtilities ; 38 import javax.swing.text.BadLocationException ; 39 import javax.swing.text.Document ; 40 import javax.swing.text.EditorKit ; 41 import javax.swing.text.JTextComponent ; 42 import javax.swing.text.Position ; 43 import org.netbeans.editor.*; 44 import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProvider; 45 import org.openide.ErrorManager; 46 47 51 public class HyperlinkOperation implements MouseListener , MouseMotionListener , PropertyChangeListener , KeyListener { 52 53 private static Cursor HAND_CURSOR = null; 54 55 private JTextComponent component; 56 private Document currentDocument; 57 private HyperlinkLayer layer; 58 private String mimeType; 59 private Cursor oldComponentsMouseCursor; 60 private boolean hyperlinkUp; 61 private boolean listenersSetUp; 62 63 private boolean hyperlinkEnabled; 64 private int actionKeyMask; 65 66 public static HyperlinkOperation create(JTextComponent component, String mimeType) { 67 return new HyperlinkOperation(component, mimeType); 68 } 69 70 private static synchronized Cursor getHandCursor() { 71 if (HAND_CURSOR == null) 72 HAND_CURSOR = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); 73 74 return HAND_CURSOR; 75 } 76 77 78 private HyperlinkOperation(JTextComponent component, String mimeType) { 79 this.component = component; 80 this.mimeType = mimeType; 81 this.oldComponentsMouseCursor = null; 82 this.hyperlinkUp = false; 83 this.listenersSetUp = false; 84 85 readSettings(); 86 87 if (hyperlinkEnabled) { 88 EditorUI ui = Utilities.getEditorUI(component); 90 91 layer = new HyperlinkLayer(); 92 93 if (ui != null) 94 ui.addLayer(layer, 1100); 95 96 component.addPropertyChangeListener("document", this); } 98 } 99 100 private void documentUpdated() { 101 if (!hyperlinkEnabled) 102 return ; 103 104 currentDocument = component.getDocument(); 105 106 if (currentDocument instanceof BaseDocument) { 107 if (!listenersSetUp) { 108 component.addMouseListener(this); 109 component.addMouseMotionListener(this); 110 component.addKeyListener(this); 111 listenersSetUp = true; 112 } 113 } 114 } 115 116 private void readSettings() { 117 String hyperlinkActivationKeyPropertyValue = System.getProperty("org.netbeans.lib.editor.hyperlink.HyperlinkOperation.activationKey"); 118 119 if (hyperlinkActivationKeyPropertyValue != null) { 120 if ("off".equals(hyperlinkActivationKeyPropertyValue)) { this.hyperlinkEnabled = false; 122 this.actionKeyMask = (-1); 123 } else { 124 this.hyperlinkEnabled = true; 125 this.actionKeyMask = (-1); 126 127 for (int cntr = 0; cntr < hyperlinkActivationKeyPropertyValue.length(); cntr++) { 128 int localMask = 0; 129 130 switch (hyperlinkActivationKeyPropertyValue.charAt(cntr)) { 131 case 'S': localMask = InputEvent.SHIFT_DOWN_MASK; break; 132 case 'C': localMask = InputEvent.CTRL_DOWN_MASK; break; 133 case 'A': localMask = InputEvent.ALT_DOWN_MASK; break; 134 case 'M': localMask = InputEvent.META_DOWN_MASK; break; 135 default: 136 ErrorManager.getDefault().log(ErrorManager.WARNING, "Incorrect value of org.netbeans.lib.editor.hyperlink.HyperlinkOperation.activationKey property (only letters CSAM are allowed): " + hyperlinkActivationKeyPropertyValue.charAt(cntr)); 137 } 138 139 if (localMask == 0) { 140 this.actionKeyMask = (-1); 142 break; 143 } 144 145 if (this.actionKeyMask == (-1)) 146 this.actionKeyMask = localMask; 147 else 148 this.actionKeyMask |= localMask; 149 } 150 151 if (this.actionKeyMask == (-1)) { 152 ErrorManager.getDefault().log(ErrorManager.WARNING, "Some problem with property org.netbeans.lib.editor.hyperlink.HyperlinkOperation.activationKey, more information might be given above. Falling back to the default behaviour."); 153 } else { 154 return; 155 } 156 } 157 } 158 159 this.hyperlinkEnabled = true; 160 161 Object activation = Settings.getValue(Utilities.getKitClass(component), SettingsNames.HYPERLINK_ACTIVATION_MODIFIERS); 162 163 if (activation != null && activation instanceof Integer ) { 164 this.actionKeyMask = ((Integer ) activation).intValue(); 165 } else { 166 this.actionKeyMask = InputEvent.CTRL_DOWN_MASK; 167 } 168 } 169 170 public void mouseMoved(MouseEvent e) { 171 if (isHyperlinkEvent(e)) { 172 int position = component.viewToModel(e.getPoint()); 173 174 if (position < 0) { 175 unHyperlink(true); 176 177 return ; 178 } 179 180 performHyperlinking(position); 181 } else { 182 unHyperlink(true); 183 } 184 } 185 186 public void mouseDragged(MouseEvent e) { 187 } 189 190 private boolean isHyperlinkEvent(InputEvent e) { 191 return ((e.getModifiers() | e.getModifiersEx()) & actionKeyMask) == actionKeyMask; 192 } 193 194 private void performHyperlinking(int position) { 195 HyperlinkProvider provider = findProvider(position); 196 197 if (provider != null) { 198 int[] offsets = provider.getHyperlinkSpan(component.getDocument(), position); 199 200 if (offsets != null) 201 makeHyperlink(offsets[0], offsets[1]); 202 } else { 203 unHyperlink(true); 204 } 205 } 206 207 private void performAction(int position) { 208 HyperlinkProvider provider = findProvider(position); 209 210 if (provider != null) { 211 unHyperlink(true); 212 213 component.getCaret().setDot(position); 215 JumpList.checkAddEntry(component, position); 216 217 provider.performClickAction(component.getDocument(), position); 218 } 219 } 220 221 private HyperlinkProvider findProvider(int position) { 222 Object mimeTypeObj = component.getDocument().getProperty("mimeType"); String mimeType; 224 225 if (mimeTypeObj instanceof String ) 226 mimeType = (String ) mimeTypeObj; 227 else { 228 mimeType = this.mimeType; 229 } 230 231 List providers = HyperlinkProviderManager.getDefault().getHyperlinkProviders(mimeType); 232 233 for (Iterator i = providers.iterator(); i.hasNext(); ) { 234 HyperlinkProvider provider = (HyperlinkProvider) i.next(); 235 236 if (provider.isHyperlinkPoint(component.getDocument(), position)) { 237 return provider; 238 } 239 } 240 241 return null; 242 } 243 244 private synchronized void makeHyperlink(final int start, final int end) { 245 boolean makeCursorSnapshot = true; 246 247 if (hyperlinkUp) { 248 unHyperlink(false); 249 makeCursorSnapshot = false; 250 } 251 252 try { 253 Position startPos = layer.hyperlinkStart = component.getDocument().createPosition(start); 254 Position endPos = layer.hyperlinkEnd = component.getDocument().createPosition(end); 255 256 hyperlinkUp = true; 257 258 damageRange(startPos, endPos); 259 260 if (makeCursorSnapshot) { 261 if (component.isCursorSet()) { 262 oldComponentsMouseCursor = component.getCursor(); 263 } else { 264 oldComponentsMouseCursor = null; 265 } 266 component.setCursor(getHandCursor()); 267 } 268 } catch (BadLocationException e) { 269 ErrorManager.getDefault().notify(e); 270 unHyperlink(false); 271 } 272 } 273 274 private void damageRange(final Position start, final Position end) { 275 if (start != null && end != null) { 276 SwingUtilities.invokeLater(new Runnable () { 277 public void run() { 278 component.getDocument().render(new Runnable () { 279 public void run() { 280 int startIndex = start.getOffset(); 281 int endIndex = end.getOffset(); 282 283 component.getUI().damageRange(component, startIndex, endIndex); 284 } 285 }); 286 } 287 }); 288 } 289 } 290 private synchronized void unHyperlink(boolean removeCursor) { 291 if (!hyperlinkUp) 292 return ; 293 294 final Position start = layer.hyperlinkStart; 295 final Position end = layer.hyperlinkEnd; 296 297 layer.hyperlinkStart = null; 298 layer.hyperlinkEnd = null; 299 300 damageRange(start, end); 301 302 if (removeCursor) { 303 if (component.isCursorSet() && component.getCursor() == getHandCursor()) { 304 component.setCursor(oldComponentsMouseCursor); 305 } 306 oldComponentsMouseCursor = null; 307 } 308 309 hyperlinkUp = false; 310 } 311 312 public void propertyChange(PropertyChangeEvent evt) { 313 if (currentDocument != component.getDocument()) 314 documentUpdated(); 315 } 316 317 public void keyTyped(KeyEvent e) { 318 } 320 321 public void keyReleased(KeyEvent e) { 322 if ((e.getModifiers() & actionKeyMask) == 0) 323 unHyperlink(true); 324 } 325 326 public void keyPressed(KeyEvent e) { 327 } 329 330 public void mouseReleased(MouseEvent e) { 331 } 333 334 public void mousePressed(MouseEvent e) { 335 } 337 338 public void mouseExited(MouseEvent e) { 339 } 341 342 public void mouseEntered(MouseEvent e) { 343 } 345 346 public void mouseClicked(MouseEvent e) { 347 if (isHyperlinkEvent(e) && !e.isPopupTrigger() && e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON1) { 348 int position = component.viewToModel(e.getPoint()); 349 350 if (position < 0) { 351 return ; 352 } 353 354 performAction(position); 355 } 356 } 357 358 private static class HyperlinkLayer extends DrawLayer.AbstractLayer { 359 360 private Position hyperlinkStart = null; 361 private Position hyperlinkEnd = null; 362 363 public static final String NAME = "hyperlink-layer"; 365 public static final int VISIBILITY = 1050; 366 367 private boolean initialized = false; 368 369 public HyperlinkLayer() { 370 super(NAME); 371 372 this.initialized = false; 373 } 374 375 private void checkDocument(Document doc) { 376 } 377 378 public boolean extendsEOL() { 379 return true; 380 } 381 382 public synchronized void init(final DrawContext ctx) { 383 if (!initialized) { 384 Document doc = ctx.getEditorUI().getDocument(); 385 386 initialized = true; 387 checkDocument(doc); 388 } 389 390 if (isActive()) 391 setNextActivityChangeOffset(hyperlinkStart.getOffset()); 392 } 393 394 private boolean isActive() { 395 return hyperlinkStart != null && hyperlinkEnd != null; 396 } 397 398 public boolean isActive(DrawContext ctx, MarkFactory.DrawMark mark) { 399 return isActive(); 400 } 401 402 private boolean isIn(int offset) { 403 return offset >= hyperlinkStart.getOffset() && offset < hyperlinkEnd.getOffset(); 404 } 405 406 private static Coloring hoverColoring = new Coloring(null, 0, Color.BLUE, null, Color.BLUE, null); 407 408 public void updateContext(DrawContext ctx) { 409 if (!isActive()) 410 return ; 411 412 int currentOffset = ctx.getFragmentOffset(); 413 414 if (isIn(currentOffset)) { 415 hoverColoring.apply(ctx); 416 417 if (isIn(currentOffset + ctx.getFragmentLength())) 418 setNextActivityChangeOffset(currentOffset + ctx.getFragmentLength()); 419 } 420 } 421 422 } 423 424 } 425 | Popular Tags |