001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.editor.ext;
043:
044: import java.awt.Dimension;
045: import java.awt.Point;
046: import java.awt.Rectangle;
047: import java.awt.Font;
048: import java.awt.Color;
049: import java.awt.event.ActionListener;
050: import java.awt.event.ActionEvent;
051: import java.awt.event.MouseMotionListener;
052: import java.awt.event.MouseAdapter;
053: import java.awt.event.MouseEvent;
054: import java.awt.event.FocusListener;
055: import java.awt.event.FocusEvent;
056: import java.beans.PropertyChangeListener;
057: import java.beans.PropertyChangeEvent;
058: import java.beans.PropertyChangeSupport;
059: import javax.swing.JComponent;
060: import javax.swing.Timer;
061: import javax.swing.Action;
062: import javax.swing.ActionMap;
063: import javax.swing.BorderFactory;
064: import javax.swing.JEditorPane;
065: import javax.swing.UIManager;
066: import javax.swing.text.JTextComponent;
067: import javax.swing.text.BadLocationException;
068: import org.netbeans.editor.SettingsChangeListener;
069: import org.netbeans.editor.SettingsChangeEvent;
070: import org.netbeans.editor.Settings;
071: import org.netbeans.editor.Utilities;
072: import org.netbeans.editor.BaseKit;
073: import org.netbeans.editor.BaseTextUI;
074: import org.netbeans.editor.BaseDocument;
075: import org.netbeans.editor.WeakTimerListener;
076: import org.netbeans.editor.PopupManager;
077: import javax.swing.JTextArea;
078: import org.netbeans.editor.GlyphGutter;
079: import javax.swing.JViewport;
080: import javax.swing.text.Document;
081: import javax.swing.text.Element;
082:
083: /**
084: * Support for editor tooltips. Once the user stops moving the mouse
085: * for the {@link #INITIAL_DELAY} milliseconds the enterTimer fires
086: * and the {@link #updateToolTip()} method is called which searches
087: * for the action named {@link ExtKit#buildToolTipAction} and if found
088: * it executes it. The tooltips can be displayed by either calling
089: * {@link #setToolTipText(java.lang.String)}
090: * or {@link #setToolTip(javax.swing.JComponent)}.<BR>
091: * However only one of the above ways should be used
092: * not a combination of both because in such case
093: * the text could be propagated in the previously set
094: * custom tooltip component.
095: *
096: * @author Miloslav Metelka
097: * @version 1.00
098: */
099:
100: public class ToolTipSupport extends MouseAdapter implements
101: MouseMotionListener, ActionListener, PropertyChangeListener,
102: SettingsChangeListener, FocusListener {
103:
104: /** Property for the tooltip component change */
105: public static final String PROP_TOOL_TIP = "toolTip"; // NOI18N
106:
107: /** Property for the tooltip text change */
108: public static final String PROP_TOOL_TIP_TEXT = "toolTipText"; // NOI18N
109:
110: /** Property for the visibility status change. */
111: public static final String PROP_STATUS = "status"; // NOI18N
112:
113: /** Property for the enabled flag change */
114: public static final String PROP_ENABLED = "enabled"; // NOI18N
115:
116: /** Property for the initial delay change */
117: public static final String PROP_INITIAL_DELAY = "initialDelay"; // NOI18N
118:
119: /** Property for the dismiss delay change */
120: public static final String PROP_DISMISS_DELAY = "dismissDelay"; // NOI18N
121:
122: private static final String UI_PREFIX = "ToolTip"; // NOI18N
123:
124: /** Initial delay before the tooltip is shown in milliseconds. */
125: public static final int INITIAL_DELAY = 200;
126:
127: /** Delay after which the tooltip will be hidden automatically
128: * in milliseconds.
129: */
130: public static final int DISMISS_DELAY = 60000;
131:
132: /** Status indicating that the tooltip is not showing on the screen. */
133: public static final int STATUS_HIDDEN = 0;
134: /** Status indicating that the tooltip is not showing on the screen
135: * but once either the {@link #setToolTipText(java.lang.String)}
136: * or {@link #setToolTip(javax.swing.JComponent)} gets called
137: * the tooltip will become visible.
138: */
139: public static final int STATUS_VISIBILITY_ENABLED = 1;
140: /** Status indicating that the tooltip is visible
141: * because {@link #setToolTipText(java.lang.String)}
142: * was called.
143: */
144: public static final int STATUS_TEXT_VISIBLE = 2;
145: /** Status indicating that the tooltip is visible
146: * because {@link #setToolTip(javax.swing.JComponent)}
147: * was called.
148: */
149: public static final int STATUS_COMPONENT_VISIBLE = 3;
150:
151: /** Extra height added to the rectangle of modelToView() for mouse
152: * cursor coordinates.
153: */
154: private static final int MOUSE_EXTRA_HEIGHT = 5;
155:
156: private static final String HTML_PREFIX_LOWERCASE = "<html"; //NOI18N
157: private static final String HTML_PREFIX_UPPERCASE = "<HTML"; //NOI18N
158:
159: private ExtEditorUI extEditorUI;
160:
161: private JComponent toolTip;
162:
163: private String toolTipText;
164:
165: private Timer enterTimer;
166:
167: private Timer exitTimer;
168:
169: private boolean enabled;
170:
171: /** Status of the tooltip visibility. */
172: private int status;
173:
174: private MouseEvent lastMouseEvent;
175:
176: private PropertyChangeSupport pcs;
177:
178: private PopupManager.HorizontalBounds horizontalBounds = PopupManager.ViewPortBounds;
179: private PopupManager.Placement placement = PopupManager.AbovePreferred;
180:
181: private int verticalAdjustment;
182: private int horizontalAdjustment;
183:
184: private boolean glyphListenerAdded = false;
185:
186: /** Construct new support for tooltips.
187: */
188: public ToolTipSupport(ExtEditorUI extEditorUI) {
189: this .extEditorUI = extEditorUI;
190:
191: enterTimer = new Timer(INITIAL_DELAY, new WeakTimerListener(
192: this ));
193: enterTimer.setRepeats(false);
194: exitTimer = new Timer(DISMISS_DELAY,
195: new WeakTimerListener(this ));
196: exitTimer.setRepeats(false);
197:
198: Settings.addSettingsChangeListener(this );
199: extEditorUI.addPropertyChangeListener(this );
200:
201: setEnabled(true);
202: }
203:
204: /** @return the component that either contains the tooltip
205: * or is responsible for displaying of text tooltips.
206: */
207: public final JComponent getToolTip() {
208: if (toolTip == null) {
209: setToolTip(createDefaultToolTip());
210: }
211:
212: return toolTip;
213: }
214:
215: /** Set the tooltip component.
216: * It can be called either to set the custom component
217: * that will display the text tooltips or to display
218: * the generic component with the tooltip after
219: * the tooltip timer has fired.
220: * @param toolTip component that either contains the tooltip
221: * or that will display a text tooltip.
222: */
223: public void setToolTip(JComponent toolTip) {
224: setToolTip(toolTip, PopupManager.ViewPortBounds,
225: PopupManager.AbovePreferred);
226: }
227:
228: public void setToolTip(JComponent toolTip,
229: PopupManager.HorizontalBounds horizontalBounds,
230: PopupManager.Placement placement) {
231: setToolTip(toolTip, PopupManager.ViewPortBounds,
232: PopupManager.AbovePreferred, 0, 0);
233: }
234:
235: public void setToolTip(JComponent toolTip,
236: PopupManager.HorizontalBounds horizontalBounds,
237: PopupManager.Placement placement, int horizontalAdjustment,
238: int verticalAdjustment) {
239: JComponent oldToolTip = this .toolTip;
240: this .toolTip = toolTip;
241: this .horizontalBounds = horizontalBounds;
242: this .placement = placement;
243: this .horizontalAdjustment = horizontalAdjustment;
244: this .verticalAdjustment = verticalAdjustment;
245:
246: if (status >= STATUS_VISIBILITY_ENABLED) {
247: ensureVisibility();
248: }
249:
250: firePropertyChange(PROP_TOOL_TIP, oldToolTip, this .toolTip);
251: }
252:
253: /** Create the default tooltip component.
254: */
255: protected JComponent createDefaultToolTip() {
256: return createTextToolTip(false);
257: }
258:
259: private JEditorPane createHtmlTextToolTip() {
260: JEditorPane tt = new JEditorPane() {
261: public @Override
262: void setSize(int width, int height) {
263: Dimension prefSize = getPreferredSize();
264: if (width >= prefSize.width) {
265: width = prefSize.width;
266: } else { // smaller available width
267: super .setSize(width, 10000); // the height is unimportant
268: prefSize = getPreferredSize(); // re-read new pref width
269: }
270: if (height >= prefSize.height) { // enough height
271: height = prefSize.height;
272: }
273: super .setSize(width, height);
274: }
275: };
276:
277: Font font = UIManager.getFont(UI_PREFIX + ".font"); // NOI18N
278: Color backColor = UIManager.getColor(UI_PREFIX + ".background"); // NOI18N
279: Color foreColor = UIManager.getColor(UI_PREFIX + ".foreground"); // NOI18N
280:
281: if (font != null) {
282: tt.setFont(font);
283: }
284: if (foreColor != null) {
285: tt.setForeground(foreColor);
286: }
287: if (backColor != null) {
288: tt.setBackground(backColor);
289: }
290:
291: tt.setOpaque(true);
292: tt.setBorder(BorderFactory.createCompoundBorder(BorderFactory
293: .createLineBorder(tt.getForeground()), BorderFactory
294: .createEmptyBorder(0, 3, 0, 3)));
295: tt.setContentType("text/html"); //NOI18N
296:
297: return tt;
298: }
299:
300: private JTextArea createTextToolTip(final boolean wrapLines) {
301: JTextArea tt = new JTextArea() {
302: public @Override
303: void setSize(int width, int height) {
304: Dimension prefSize = getPreferredSize();
305: if (width >= prefSize.width) {
306: width = prefSize.width;
307: } else { // smaller available width
308: // Set line wrapping and do super.setSize() to determine
309: // the real height (it will change due to line wrapping)
310:
311: if (wrapLines) {
312: setLineWrap(true);
313: setWrapStyleWord(true);
314: }
315:
316: super .setSize(width, 10000); // the height is unimportant
317: prefSize = getPreferredSize(); // re-read new pref width
318: }
319: if (height >= prefSize.height) { // enough height
320: height = prefSize.height;
321: } else { // smaller available height
322: // Check how much can be displayed - cannot rely on line count
323: // because line wrapping may display single physical line
324: // into several visual lines
325: // Before using viewToModel() a setSize() must be called
326: // because otherwise the viewToModel() would return -1.
327: super .setSize(width, 10000);
328: int offset = viewToModel(new Point(0, height));
329: Document doc = getDocument();
330: Element root = doc.getDefaultRootElement();
331: int lineIndex = root.getElementIndex(offset);
332: lineIndex--; // go to previous line
333: if (lineIndex >= 0) {
334: Element lineElem = root.getElement(lineIndex);
335: if (lineElem != null) {
336: try {
337: offset = lineElem.getStartOffset();
338: doc.remove(offset, doc.getLength()
339: - offset);
340: doc.insertString(offset, "...", null);
341: } catch (BadLocationException e) {
342: // "..." will likely not be displayed but otherwise should be ok
343: }
344: // Recalculate the prefSize as it may be smaller
345: // than the present preferred height
346: height = Math.min(height,
347: getPreferredSize().height);
348: }
349: }
350: }
351: super .setSize(width, height);
352: }
353: };
354:
355: // bugfix of #43174
356: tt.setActionMap(new ActionMap());
357: tt.setInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
358: null);
359:
360: Font font = UIManager.getFont(UI_PREFIX + ".font"); // NOI18N
361: Color backColor = UIManager.getColor(UI_PREFIX + ".background"); // NOI18N
362: Color foreColor = UIManager.getColor(UI_PREFIX + ".foreground"); // NOI18N
363:
364: if (font != null) {
365: tt.setFont(font);
366: }
367: if (foreColor != null) {
368: tt.setForeground(foreColor);
369: }
370: if (backColor != null) {
371: tt.setBackground(backColor);
372: }
373:
374: tt.setOpaque(true);
375: tt.setBorder(BorderFactory.createCompoundBorder(BorderFactory
376: .createLineBorder(tt.getForeground()), BorderFactory
377: .createEmptyBorder(0, 3, 0, 3)));
378:
379: return tt;
380: }
381:
382: public void settingsChange(SettingsChangeEvent evt) {
383: }
384:
385: public void propertyChange(PropertyChangeEvent evt) {
386: String propName = evt.getPropertyName();
387:
388: if (ExtEditorUI.COMPONENT_PROPERTY.equals(propName)) {
389: JTextComponent component = (JTextComponent) evt
390: .getNewValue();
391: if (component != null) { // just installed
392:
393: component.addPropertyChangeListener(this );
394:
395: disableSwingToolTip(component);
396:
397: component.addFocusListener(this );
398: if (component.hasFocus()) {
399: focusGained(new FocusEvent(component,
400: FocusEvent.FOCUS_GAINED));
401: }
402: component.addMouseListener(this );
403: component.addMouseMotionListener(this );
404:
405: GlyphGutter gg = extEditorUI.getGlyphGutter();
406: if (gg != null && !glyphListenerAdded) {
407: glyphListenerAdded = true;
408: gg.addMouseListener(this );
409: gg.addMouseMotionListener(this );
410: }
411:
412: } else { // just deinstalled
413: component = (JTextComponent) evt.getOldValue();
414:
415: component.removeFocusListener(this );
416: component.removePropertyChangeListener(this );
417:
418: component.removeMouseListener(this );
419: component.removeMouseMotionListener(this );
420:
421: GlyphGutter gg = extEditorUI.getGlyphGutter();
422: if (gg != null) {
423: gg.removeMouseListener(this );
424: gg.removeMouseMotionListener(this );
425: }
426: setToolTipVisible(false);
427:
428: }
429: }
430:
431: if (JComponent.TOOL_TIP_TEXT_KEY.equals(propName)) {
432: JComponent component = (JComponent) evt.getSource();
433: disableSwingToolTip(component);
434:
435: componentToolTipTextChanged(evt);
436: }
437:
438: }
439:
440: private void disableSwingToolTip(final JComponent component) {
441: javax.swing.SwingUtilities.invokeLater(new Runnable() {
442: public void run() {
443: // Prevent default swing tooltip manager
444: javax.swing.ToolTipManager.sharedInstance()
445: .unregisterComponent(component);
446:
447: // Also disable the swing tooltip manager on gutter component
448: GlyphGutter gg = extEditorUI.getGlyphGutter();
449: if (gg != null) {
450: javax.swing.ToolTipManager.sharedInstance()
451: .unregisterComponent(gg);
452: }
453: }
454: });
455: }
456:
457: /** Update the tooltip by running corresponding action
458: * {@link ExtKit#buildToolTipAction}. This method gets
459: * called once the enterTimer fires and it can be overriden
460: * by children.
461: */
462: protected void updateToolTip() {
463: ExtEditorUI ui = extEditorUI;
464: if (ui == null)
465: return;
466: JTextComponent comp = ui.getComponent();
467: if (comp == null)
468: return;
469:
470: if (isGlyphGutterMouseEvent(lastMouseEvent)) {
471: setToolTipText(extEditorUI.getGlyphGutter().getToolTipText(
472: lastMouseEvent));
473: } else { // over the text component
474: BaseKit kit = Utilities.getKit(comp);
475: if (kit != null) {
476: Action a = kit
477: .getActionByName(ExtKit.buildToolTipAction);
478: if (a != null) {
479: a.actionPerformed(new ActionEvent(comp, 0, "")); // NOI18N
480: }
481: }
482: }
483: }
484:
485: /** Set the visibility of the tooltip.
486: * @param visible whether tooltip should become visible or not.
487: * If true the status is changed
488: * to {@link { #STATUS_VISIBILITY_ENABLED}
489: * and @link #updateToolTip()} is called.<BR>
490: * It is still possible that the tooltip will not be showing
491: * on the screen in case the tooltip or tooltip text are left
492: * unchanged.
493: */
494: protected void setToolTipVisible(boolean visible) {
495: if (!visible) { // ensure the timers are stopped
496: enterTimer.stop();
497: exitTimer.stop();
498: }
499:
500: if (visible && status < STATUS_VISIBILITY_ENABLED || !visible
501: && status >= STATUS_VISIBILITY_ENABLED) {
502: if (visible) { // try to show the tooltip
503: if (enabled) {
504: setStatus(STATUS_VISIBILITY_ENABLED);
505: updateToolTip();
506: }
507:
508: } else { // hide tip
509: if (toolTip != null) {
510: if (toolTip.isVisible()) {
511: toolTip.setVisible(false);
512: PopupManager pm = extEditorUI.getPopupManager();
513: if (pm != null) {
514: pm.uninstall(toolTip);
515: }
516: }
517: }
518:
519: setStatus(STATUS_HIDDEN);
520: }
521: }
522: }
523:
524: /** @return Whether the tooltip is showing on the screen.
525: * {@link #getStatus() } gives the exact visibility state.
526: */
527: public boolean isToolTipVisible() {
528: return status > STATUS_VISIBILITY_ENABLED;
529: }
530:
531: /** @return status of the tooltip visibility. It can
532: * be {@link #STATUS_HIDDEN}
533: * or {@link #STATUS_VISIBILITY_ENABLED}
534: * or {@link #STATUS_TEXT_VISIBLE}
535: * or {@link #STATUS_COMPONENT_VISIBLE}.
536: */
537: public final int getStatus() {
538: return status;
539: }
540:
541: private void setStatus(int status) {
542: if (this .status != status) {
543: int oldStatus = this .status;
544: this .status = status;
545: firePropertyChange(PROP_STATUS, new Integer(oldStatus),
546: new Integer(this .status));
547: }
548: }
549:
550: /** @return the current tooltip text.
551: */
552: public String getToolTipText() {
553: return toolTipText;
554: }
555:
556: /**
557: * Makes the given String displayble. Probably there doesn't exists
558: * perfect solution for all situation. (someone prefer display those
559: * squares for undisplayable chars, someone unicode placeholders). So lets
560: * try do the best compromise.
561: */
562: private static String makeDisplayable(String str, Font f) {
563: if (str == null || f == null) {
564: return str;
565: }
566: StringBuffer buf = new StringBuffer(str.length());
567: char[] chars = str.toCharArray();
568: for (int i = 0; i < chars.length; i++) {
569: char c = chars[i];
570: switch (c) {
571: case '\t':
572: buf.append(c);
573: break;
574: case '\n':
575: buf.append(c);
576: break;
577: case '\r':
578: buf.append(c);
579: break;
580: case '\b':
581: buf.append("\\b");
582: break; // NOI18N
583: case '\f':
584: buf.append("\\f");
585: break; // NOI18N
586: default:
587: if (f == null || f.canDisplay(c)) {
588: buf.append(c);
589: } else {
590: buf.append("\\u"); // NOI18N
591: String hex = Integer.toHexString(c);
592: for (int j = 0; j < 4 - hex.length(); j++) {
593: buf.append('0'); //NOI18N
594: }
595: buf.append(hex);
596: }
597: }
598: }
599: return buf.toString();
600: }
601:
602: /** Set the tooltip text to make the tooltip
603: * to be shown on the screen.
604: * @param text tooltip text to be displayed.
605: */
606: public void setToolTipText(String text) {
607:
608: final String displayableText = makeDisplayable(text, UIManager
609: .getFont(UI_PREFIX + ".font")); //NOI18N
610:
611: Utilities.runInEventDispatchThread(new Runnable() {
612: public void run() {
613: String oldText = toolTipText;
614: toolTipText = displayableText;
615:
616: firePropertyChange(PROP_TOOL_TIP_TEXT, oldText,
617: toolTipText);
618:
619: if (toolTipText != null) {
620: if (toolTipText.startsWith(HTML_PREFIX_LOWERCASE)
621: || toolTipText
622: .startsWith(HTML_PREFIX_UPPERCASE)) {
623: JEditorPane jep = createHtmlTextToolTip();
624: jep.setText(toolTipText);
625: setToolTip(jep);
626: } else {
627: boolean multiLineText = toolTipText
628: .contains("\n"); //NOI18N
629: JTextArea ta = createTextToolTip(!multiLineText);
630: ta.setText(toolTipText);
631: setToolTip(ta);
632: }
633: } else { // null text
634: if (status == STATUS_TEXT_VISIBLE) {
635: setToolTipVisible(false);
636: }
637: }
638: }
639: });
640: }
641:
642: private boolean isGlyphGutterMouseEvent(MouseEvent evt) {
643: return (evt != null && evt.getSource() == extEditorUI
644: .getGlyphGutter());
645: }
646:
647: private void ensureVisibility() {
648: // Find the visual position in the document
649: JTextComponent component = extEditorUI.getComponent();
650: if (component != null) {
651: // Try to display the tooltip above (or below) the line it corresponds to
652: int pos = component.viewToModel(getLastMouseEventPoint());
653: Rectangle cursorBounds = null;
654: if (pos >= 0) {
655: try {
656: cursorBounds = component.modelToView(pos);
657: if (horizontalBounds == PopupManager.ScrollBarBounds) {
658:
659: } else {
660: if (placement == PopupManager.AbovePreferred
661: || placement == PopupManager.Above) {
662: // Enlarge the height slightly to not interfere with mouse cursor
663: cursorBounds.y -= MOUSE_EXTRA_HEIGHT;
664: cursorBounds.height += 2 * MOUSE_EXTRA_HEIGHT; // above and below
665: } else if (placement == PopupManager.BelowPreferred
666: || placement == PopupManager.Below) {
667: cursorBounds.y = cursorBounds.y
668: + cursorBounds.height
669: + MOUSE_EXTRA_HEIGHT + 1;
670: cursorBounds.height += MOUSE_EXTRA_HEIGHT; // above and below
671: }
672: }
673:
674: } catch (BadLocationException e) {
675: }
676: }
677: if (cursorBounds == null) { // get mose rect
678: cursorBounds = new Rectangle(getLastMouseEventPoint(),
679: new Dimension(1, 1));
680: }
681:
682: // updateToolTipBounds();
683: PopupManager pm = extEditorUI.getPopupManager();
684:
685: if (toolTip != null && toolTip.isVisible()) {
686: toolTip.setVisible(false);
687: }
688: pm.install(toolTip, cursorBounds, placement,
689: horizontalBounds, horizontalAdjustment,
690: verticalAdjustment);
691: if (toolTip != null) {
692: toolTip.setVisible(true);
693: }
694: }
695: exitTimer.restart();
696: }
697:
698: /** Helper method to get the identifier
699: * under the mouse cursor.
700: * @return string containing identifier under
701: * mouse cursor.
702: */
703: public String getIdentifierUnderCursor() {
704: String word = null;
705: if (!isGlyphGutterMouseEvent(lastMouseEvent)) {
706: try {
707: JTextComponent component = extEditorUI.getComponent();
708: BaseTextUI ui = (BaseTextUI) component.getUI();
709: Point lmePoint = getLastMouseEventPoint();
710: int pos = ui.viewToModel(component, lmePoint);
711: if (pos >= 0) {
712: BaseDocument doc = (BaseDocument) component
713: .getDocument();
714: int eolPos = Utilities.getRowEnd(doc, pos);
715: Rectangle eolRect = ui.modelToView(component,
716: eolPos);
717: int lineHeight = extEditorUI.getLineHeight();
718: if (lmePoint.x <= eolRect.x
719: && lmePoint.y <= eolRect.y + lineHeight) {
720: word = Utilities.getIdentifier(doc, pos);
721: }
722: }
723: } catch (BadLocationException e) {
724: // word will be null
725: }
726: }
727:
728: return word;
729: }
730:
731: /** @return whether the tooltip support is enabled. If it's
732: * disabled the tooltip does not become visible.
733: */
734: public boolean isEnabled() {
735: return enabled;
736: }
737:
738: /** Set whether the tooltip support is enabled. If it's
739: * disabled the tooltip does not become visible.
740: * @param enabled whether the tooltip will be enabled or not.
741: */
742: public void setEnabled(boolean enabled) {
743: if (enabled != this .enabled) {
744: this .enabled = enabled;
745:
746: firePropertyChange(PROP_ENABLED, enabled ? Boolean.FALSE
747: : Boolean.TRUE, enabled ? Boolean.TRUE
748: : Boolean.FALSE);
749:
750: if (!enabled) {
751: setToolTipVisible(false);
752: }
753: }
754: }
755:
756: /** @return the delay between stopping
757: * mouse movement and displaying
758: * of the tooltip in milliseconds.
759: */
760: public int getInitialDelay() {
761: return enterTimer.getDelay();
762: }
763:
764: /** Set the delay between stopping
765: * mouse movement and displaying
766: * of the tooltip in milliseconds.
767: */
768: public void setInitialDelay(int delay) {
769: if (enterTimer.getDelay() != delay) {
770: int oldDelay = enterTimer.getDelay();
771: enterTimer.setDelay(delay);
772:
773: firePropertyChange(PROP_INITIAL_DELAY,
774: new Integer(oldDelay), new Integer(enterTimer
775: .getDelay()));
776: }
777: }
778:
779: /** @return the delay between displaying
780: * of the tooltip and its automatic hiding
781: * in milliseconds.
782: */
783: public int getDismissDelay() {
784: return exitTimer.getDelay();
785: }
786:
787: /** Set the delay between displaying
788: * of the tooltip and its automatic hiding
789: * in milliseconds.
790: */
791: public void setDismissDelay(int delay) {
792: if (exitTimer.getDelay() != delay) {
793: int oldDelay = exitTimer.getDelay();
794: exitTimer.setDelay(delay);
795:
796: firePropertyChange(PROP_DISMISS_DELAY,
797: new Integer(oldDelay), new Integer(exitTimer
798: .getDelay()));
799: }
800: }
801:
802: public void actionPerformed(ActionEvent evt) {
803: if (evt.getSource() == enterTimer) {
804: setToolTipVisible(true);
805:
806: } else if (evt.getSource() == exitTimer) {
807: setToolTipVisible(false);
808: }
809: }
810:
811: public @Override
812: void mouseClicked(MouseEvent evt) {
813: lastMouseEvent = evt;
814: setToolTipVisible(false);
815: }
816:
817: public @Override
818: void mousePressed(MouseEvent evt) {
819: lastMouseEvent = evt;
820: setToolTipVisible(false);
821: }
822:
823: public @Override
824: void mouseReleased(MouseEvent evt) {
825: lastMouseEvent = evt;
826: setToolTipVisible(false);
827:
828: // Check that if a selection becomes visible by dragging a mouse
829: // the tooltip evaluation should be posted.
830: ExtEditorUI ui = extEditorUI;
831: if (ui != null) {
832: JTextComponent component = ui.getComponent();
833: if (enabled && component != null
834: && Utilities.isSelectionShowing(component)) {
835: enterTimer.restart();
836: }
837: }
838: }
839:
840: public @Override
841: void mouseEntered(MouseEvent evt) {
842: lastMouseEvent = evt;
843: }
844:
845: public @Override
846: void mouseExited(MouseEvent evt) {
847: lastMouseEvent = evt;
848: setToolTipVisible(false);
849: }
850:
851: public void mouseDragged(MouseEvent evt) {
852: lastMouseEvent = evt;
853: setToolTipVisible(false);
854: }
855:
856: public void mouseMoved(MouseEvent evt) {
857: setToolTipVisible(false);
858: if (enabled) {
859: enterTimer.restart();
860:
861: }
862: lastMouseEvent = evt;
863: }
864:
865: /** @return last mouse event captured by this support.
866: * This method can be used by the action that evaluates
867: * the tooltip.
868: */
869: public final MouseEvent getLastMouseEvent() {
870: return lastMouseEvent;
871: }
872:
873: /** Possibly do translation when over the gutter.
874: */
875: private Point getLastMouseEventPoint() {
876: Point p = null;
877: MouseEvent lme = lastMouseEvent;
878: if (lme != null) {
879: p = lme.getPoint();
880: if (lme.getSource() == extEditorUI.getGlyphGutter()) {
881: // Over glyph gutter - change coords
882: JTextComponent c = extEditorUI.getComponent();
883: if (c != null) {
884: if (c.getParent() instanceof JViewport) {
885: JViewport vp = (JViewport) c.getParent();
886: p = new Point(vp.getViewPosition().x, p.y);
887: }
888: }
889: }
890: }
891:
892: return p;
893: }
894:
895: /** Called automatically when the
896: * {@link javax.swing.JComponent#TOOL_TIP_TEXT_KEY}
897: * property of the corresponding editor component
898: * gets changed.<BR>
899: * By default it calls {@link #setToolTipText(java.lang.String)}
900: * with the new tooltip text of the component.
901: */
902: protected void componentToolTipTextChanged(PropertyChangeEvent evt) {
903: JComponent component = (JComponent) evt.getSource();
904: setToolTipText(component.getToolTipText());
905: }
906:
907: private synchronized PropertyChangeSupport getPCS() {
908: if (pcs == null) {
909: pcs = new PropertyChangeSupport(this );
910: }
911: return pcs;
912: }
913:
914: /** Add the listener for the property changes. The names
915: * of the supported properties are defined
916: * as "PROP_" public static string constants.
917: * @param listener listener to be added.
918: */
919: public void addPropertyChangeListener(
920: PropertyChangeListener listener) {
921: getPCS().addPropertyChangeListener(listener);
922: }
923:
924: public void removePropertyChangeListener(
925: PropertyChangeListener listener) {
926: getPCS().removePropertyChangeListener(listener);
927: }
928:
929: /** Fire the change of the given property.
930: * @param propertyName name of the fired property
931: * @param oldValue old value of the property
932: * @param newValue new value of the property.
933: */
934: protected void firePropertyChange(String propertyName,
935: Object oldValue, Object newValue) {
936: getPCS().firePropertyChange(propertyName, oldValue, newValue);
937: }
938:
939: public void focusGained(FocusEvent e) {
940: // JComponent component = (JComponent)e.getSource();
941: // component.addMouseListener(this);
942: // component.addMouseMotionListener(this);
943: GlyphGutter gg = extEditorUI.getGlyphGutter();
944: if (gg != null && !glyphListenerAdded) {
945: glyphListenerAdded = true;
946: gg.addMouseListener(this );
947: gg.addMouseMotionListener(this );
948: }
949: }
950:
951: public void focusLost(FocusEvent e) {
952: /*
953: JComponent component = (JComponent)e.getSource();
954: component.removeMouseListener(this);
955: component.removeMouseMotionListener(this);
956: GlyphGutter gg = extEditorUI.getGlyphGutter();
957: if (gg != null) {
958: gg.removeMouseListener(this);
959: gg.removeMouseMotionListener(this);
960: }
961: setToolTipVisible(false);
962: */
963: }
964:
965: }
|