Added
Link Here
|
1 |
/* |
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
3 |
* |
4 |
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved. |
5 |
* |
6 |
* The contents of this file are subject to the terms of either the GNU |
7 |
* General Public License Version 2 only ("GPL") or the Common |
8 |
* Development and Distribution License("CDDL") (collectively, the |
9 |
* "License"). You may not use this file except in compliance with the |
10 |
* License. You can obtain a copy of the License at |
11 |
* http://www.netbeans.org/cddl-gplv2.html |
12 |
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the |
13 |
* specific language governing permissions and limitations under the |
14 |
* License. When distributing the software, include this License Header |
15 |
* Notice in each file and include the License file at |
16 |
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this |
17 |
* particular file as subject to the "Classpath" exception as provided |
18 |
* by Sun in the GPL Version 2 section of the License file that |
19 |
* accompanied this code. If applicable, add the following below the |
20 |
* License Header, with the fields enclosed by brackets [] replaced by |
21 |
* your own identifying information: |
22 |
* "Portions Copyrighted [year] [name of copyright owner]" |
23 |
* |
24 |
* Contributor(s): |
25 |
* |
26 |
* The Original Software is NetBeans. The Initial Developer of the Original |
27 |
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun |
28 |
* Microsystems, Inc. All Rights Reserved. |
29 |
* |
30 |
* If you wish your version of this file to be governed by only the CDDL |
31 |
* or only the GPL Version 2, indicate your decision by adding |
32 |
* "[Contributor] elects to include this software in this distribution |
33 |
* under the [CDDL or GPL Version 2] license." If you do not indicate a |
34 |
* single choice of license, a recipient has the option to distribute |
35 |
* your version of this file under either the CDDL, the GPL Version 2 or |
36 |
* to extend the choice of license to its licensees as provided above. |
37 |
* However, if you add GPL Version 2 code and therefore, elected the GPL |
38 |
* Version 2 license, then the option applies only if the new code is |
39 |
* made subject to such option by the copyright holder. |
40 |
*/ |
41 |
package org.netbeans.spi.debugger.ui; |
42 |
|
43 |
import java.awt.Color; |
44 |
import java.awt.Cursor; |
45 |
import java.awt.event.FocusEvent; |
46 |
import java.awt.event.FocusListener; |
47 |
import java.awt.event.KeyEvent; |
48 |
import java.awt.event.KeyListener; |
49 |
import java.awt.event.MouseEvent; |
50 |
import java.awt.event.MouseListener; |
51 |
import java.awt.event.MouseMotionListener; |
52 |
import java.lang.reflect.InvocationTargetException; |
53 |
import java.net.MalformedURLException; |
54 |
import java.net.URL; |
55 |
import java.util.ArrayList; |
56 |
import java.util.List; |
57 |
import javax.swing.JEditorPane; |
58 |
import javax.swing.KeyStroke; |
59 |
import javax.swing.SwingUtilities; |
60 |
import javax.swing.text.AttributeSet; |
61 |
import javax.swing.text.BadLocationException; |
62 |
import javax.swing.text.Caret; |
63 |
import javax.swing.text.Document; |
64 |
import javax.swing.text.JTextComponent; |
65 |
import javax.swing.text.StyleConstants; |
66 |
|
67 |
import org.netbeans.editor.BaseDocument; |
68 |
import org.netbeans.editor.BaseCaret; |
69 |
import org.netbeans.editor.Utilities; |
70 |
import org.netbeans.spi.editor.highlighting.HighlightAttributeValue; |
71 |
import org.netbeans.spi.editor.highlighting.support.OffsetsBag; |
72 |
import org.netbeans.api.editor.settings.AttributesUtilities; |
73 |
import org.netbeans.api.editor.settings.EditorStyleConstants; |
74 |
|
75 |
import org.netbeans.spi.editor.highlighting.HighlightsLayer; |
76 |
import org.netbeans.spi.editor.highlighting.HighlightsLayerFactory; |
77 |
import org.netbeans.spi.editor.highlighting.ZOrder; |
78 |
import org.openide.cookies.EditorCookie; |
79 |
import org.openide.filesystems.FileObject; |
80 |
import org.openide.filesystems.URLMapper; |
81 |
import org.openide.loaders.DataObject; |
82 |
import org.openide.loaders.DataObjectNotFoundException; |
83 |
import org.openide.util.Exceptions; |
84 |
import org.openide.util.NbBundle; |
85 |
|
86 |
/** |
87 |
* Support for Step Into action implementations. It allows the user to select |
88 |
* directly in a source file a method call the debugger should step into. |
89 |
* A simple graphical interface is provided. The user can navigate among available |
90 |
* method calls. The navigation can be done using a keyboard as well as a mouse. |
91 |
* |
92 |
* <p>The method chooser is initialized by an url (pointing to a source file), an array of |
93 |
* {@link Segment} elements (each of them corresponds typically to a method call name |
94 |
* in the source file) and an index of the segment element which is displayed |
95 |
* as the default selection. |
96 |
* |
97 |
* <p>Optionally, two sets of (additional) shortcuts that confirm, resp. cancel the selection |
98 |
* mode can be specified. |
99 |
* It is also possible to pass a text, which should be shown at the editor pane's |
100 |
* status line after the selection mode has been activated. This text serves as a hint |
101 |
* to the user how to make the method call selection. |
102 |
* |
103 |
* <p>Method chooser does not use any special highlighting for the background of the |
104 |
* area where the selection takes place. If it is required it can be done by attaching |
105 |
* instances of {@link Annotation} to the proper source file's lines. These annotation should |
106 |
* be added before calling {@link #showUI} and removed after calling {@link #releaseUI}. |
107 |
* |
108 |
* <p>To display the method chooser's ui correctly, it is required to register |
109 |
* {@link HighlightsLayerFactory} created by {@link #createHighlihgtsLayerFactory} |
110 |
* in an xml layer. An example follows. |
111 |
* |
112 |
* <pre class="examplecode"> |
113 |
<folder name="Editors"> |
114 |
<folder name="text"> |
115 |
<folder name="x-java"> |
116 |
<file name="org.netbeans.spi.editor.highlighting.HighlightsLayerFactory.instance"> |
117 |
<attr name="instanceCreate" methodvalue="org.netbeans.spi.debugger.ui.MethodChooser.createHighlihgtsLayerFactory"/> |
118 |
</file> |
119 |
</folder> |
120 |
</folder> |
121 |
</folder></pre> |
122 |
* <code>"x-java"</code> should be replaced by the targeted mime type. |
123 |
* |
124 |
* @author Daniel Prusa |
125 |
* @since 2.22 |
126 |
*/ |
127 |
public class MethodChooser { |
128 |
|
129 |
private static AttributeSet defaultHyperlinkHighlight; |
130 |
|
131 |
private String url; |
132 |
private Segment[] segments; |
133 |
private int selectedIndex = -1; |
134 |
private String hintText; |
135 |
private KeyStroke[] stopEvents; |
136 |
private KeyStroke[] confirmEvents; |
137 |
|
138 |
private AttributeSet attribsLeft = null; |
139 |
private AttributeSet attribsRight = null; |
140 |
private AttributeSet attribsMiddle = null; |
141 |
private AttributeSet attribsAll = null; |
142 |
|
143 |
private AttributeSet attribsArea = null; |
144 |
private AttributeSet attribsMethod = null; |
145 |
private AttributeSet attribsHyperlink = null; |
146 |
|
147 |
private Cursor handCursor; |
148 |
private Cursor arrowCursor; |
149 |
private Cursor originalCursor; |
150 |
|
151 |
private CentralListener mainListener; |
152 |
private Document doc; |
153 |
private JEditorPane editorPane; |
154 |
private List<ReleaseListener> releaseListeners = new ArrayList<ReleaseListener>(); |
155 |
|
156 |
private int startLine; |
157 |
private int endLine; |
158 |
private int mousedIndex = -1; |
159 |
private boolean isInSelectMode = false; |
160 |
|
161 |
/** |
162 |
* Creates an instance of {@link MethodChooser}. |
163 |
* |
164 |
* @param url Url of the source file. |
165 |
* @param segments Array of segments where each of the segments represents one method |
166 |
* call. The user traverses the calls in the order given by the array. |
167 |
* @param initialIndex Index of a call that should be preselected when the method chooser |
168 |
* is shown. |
169 |
*/ |
170 |
public MethodChooser(String url, Segment[] segments, int initialIndex) { |
171 |
this(url, segments, initialIndex, null, new KeyStroke[0], new KeyStroke[0]); |
172 |
} |
173 |
|
174 |
/** |
175 |
* Creates an instance of {@link MethodChooser}. Supports optional parameters. |
176 |
* |
177 |
* @param url Url of the source file. |
178 |
* @param segments Array of segments where each of the segments represents one method |
179 |
* call. The user traverses the calls in the order given by the array. |
180 |
* @param initialIndex Index of a call that should be preselected when the method chooser |
181 |
* is shown. |
182 |
* @param hintText Text which is displayed in the editor pane's status line. Serves as a hint |
183 |
* informing briefly the user how to make a selection. |
184 |
* @param stopEvents Custom key strokes which should stop the selection mode. |
185 |
* For example, it is possible to pass a {@link KeyStroke} corresponding to |
186 |
* the shortcut of Step Over action. Then, whenever the shorcut is pressed, the selection |
187 |
* mode is cancelled. The generated {@link KeyEvent} is not consumed thus can be |
188 |
* handled and invokes Step Over action. |
189 |
* Note that a method chooser can be always cancelled by Esc or by clicking outside the |
190 |
* visualized area in the source editor. |
191 |
* @param confirmEvents Custom key strokes which confirm the current selection. |
192 |
* By default, a selection can be confirmed by Enter or Space Bar. It is possible |
193 |
* to extend this set of confirmation keys. |
194 |
*/ |
195 |
public MethodChooser(String url, Segment[] segments, int initialIndex, String hintText, |
196 |
KeyStroke[] stopEvents, KeyStroke[] confirmEvents) { |
197 |
this.url = url; |
198 |
this.segments = segments; |
199 |
this.selectedIndex = initialIndex; |
200 |
this.hintText = hintText; |
201 |
if (stopEvents == null) { |
202 |
stopEvents = new KeyStroke[0]; |
203 |
} |
204 |
if (confirmEvents == null) { |
205 |
confirmEvents = new KeyStroke[0]; |
206 |
} |
207 |
this.stopEvents = stopEvents; |
208 |
this.confirmEvents = confirmEvents; |
209 |
} |
210 |
|
211 |
/** |
212 |
* Sets up and displays the method selection mode. |
213 |
* |
214 |
* @return <code>true</code> if a {@link JEditorPane} has been found and the selection mode |
215 |
* has been properly displayed |
216 |
*/ |
217 |
public boolean showUI() { |
218 |
findEditorPane(); |
219 |
if (editorPane == null) { |
220 |
return false; // cannot do anything without editor |
221 |
} |
222 |
doc = editorPane.getDocument(); |
223 |
// compute start line and end line |
224 |
int minOffs = Integer.MAX_VALUE; |
225 |
int maxOffs = 0; |
226 |
for (int x = 0; x < segments.length; x++) { |
227 |
minOffs = Math.min(segments[x].getStartOffset(), minOffs); |
228 |
maxOffs = Math.max(segments[x].getEndOffset(), maxOffs); |
229 |
} |
230 |
try { |
231 |
startLine = Utilities.getLineOffset((BaseDocument)doc, minOffs) + 1; |
232 |
endLine = Utilities.getLineOffset((BaseDocument)doc, maxOffs) + 1; |
233 |
} catch (BadLocationException e) { |
234 |
} |
235 |
// continue by showing method selection ui |
236 |
mainListener = new CentralListener(); |
237 |
editorPane.putClientProperty(MethodChooser.class, this); |
238 |
editorPane.addKeyListener(mainListener); |
239 |
editorPane.addMouseListener(mainListener); |
240 |
editorPane.addMouseMotionListener(mainListener); |
241 |
editorPane.addFocusListener(mainListener); |
242 |
originalCursor = editorPane.getCursor(); |
243 |
handCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); |
244 |
arrowCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); |
245 |
editorPane.setCursor(arrowCursor); |
246 |
Caret caret = editorPane.getCaret(); |
247 |
if (caret instanceof BaseCaret) { |
248 |
((BaseCaret)caret).setVisible(false); |
249 |
} |
250 |
requestRepaint(); |
251 |
if (hintText != null && hintText.trim().length() > 0) { |
252 |
Utilities.setStatusText(editorPane, " " + hintText); |
253 |
} |
254 |
isInSelectMode = true; |
255 |
return true; |
256 |
} |
257 |
|
258 |
/** |
259 |
* Ends the method selection mode, clears all used ui elements. Notifies each registered |
260 |
* {@link ReleaseListener}. |
261 |
* |
262 |
* @param performAction <code>true</code> indicates that the current selection should |
263 |
* be used to perform an action, <code>false</code> means that the selection mode |
264 |
* has beencancelled |
265 |
*/ |
266 |
public synchronized void releaseUI(boolean performAction) { |
267 |
if (!isInSelectMode) { |
268 |
return; // do nothing |
269 |
} |
270 |
getHighlightsBag(doc).clear(); |
271 |
editorPane.removeKeyListener(mainListener); |
272 |
editorPane.removeMouseListener(mainListener); |
273 |
editorPane.removeMouseMotionListener(mainListener); |
274 |
editorPane.removeFocusListener(mainListener); |
275 |
editorPane.putClientProperty(MethodChooser.class, null); |
276 |
editorPane.setCursor(originalCursor); |
277 |
Caret caret = editorPane.getCaret(); |
278 |
if (caret instanceof BaseCaret) { |
279 |
((BaseCaret)caret).setVisible(true); |
280 |
} |
281 |
|
282 |
if (hintText != null && hintText.trim().length() > 0) { |
283 |
Utilities.clearStatusText(editorPane); |
284 |
} |
285 |
isInSelectMode = false; |
286 |
for (ReleaseListener listener : releaseListeners) { |
287 |
listener.released(performAction); |
288 |
} |
289 |
} |
290 |
|
291 |
/** |
292 |
* Can be used to check whether the selection mode is activated. |
293 |
* |
294 |
* @return <code>true</code> if the method selection mode is currently displayed |
295 |
*/ |
296 |
public boolean isUIActive() { |
297 |
return isInSelectMode; |
298 |
} |
299 |
|
300 |
/** |
301 |
* Returns index of {@link Segment} that is currently selected. If the method |
302 |
* chooser has been released, it corresponds to the final selection made by the user. |
303 |
* |
304 |
* @return index of currently selected method |
305 |
*/ |
306 |
public int getSelectedIndex() { |
307 |
return selectedIndex; |
308 |
} |
309 |
|
310 |
/** |
311 |
* Registers {@link ReleaseListener}. The listener is notified when the selection |
312 |
* mode finishes. This occurs whenever the user comfirms (or cancels) the current |
313 |
* selection. It also occrus when {@link #releaseUI} is called. |
314 |
* |
315 |
* @param listener an instance of {@link ReleaseListener} to be registered |
316 |
*/ |
317 |
public synchronized void addReleaseListener(ReleaseListener listener) { |
318 |
releaseListeners.add(listener); |
319 |
} |
320 |
|
321 |
/** |
322 |
* Unregisters {@link ReleaseListener}. |
323 |
* |
324 |
* @param listener an instance of {@link ReleaseListener} to be unregistered |
325 |
*/ |
326 |
public synchronized void removeReleaseListener(ReleaseListener listener) { |
327 |
releaseListeners.remove(listener); |
328 |
} |
329 |
|
330 |
/** |
331 |
* This method should be referenced in xml layer files. To display the method |
332 |
* chooser ui correctly, it is required to register an instance of |
333 |
* {@link HighlightsLayerFactory} using the following pattern. |
334 |
* <pre class="examplecode"> |
335 |
<folder name="Editors"> |
336 |
<folder name="text"> |
337 |
<folder name="x-java"> |
338 |
<file name="org.netbeans.spi.editor.highlighting.HighlightsLayerFactory.instance"> |
339 |
<attr name="instanceCreate" methodvalue="org.netbeans.spi.debugger.ui.MethodChooser.createHighlihgtsLayerFactory"/> |
340 |
</file> |
341 |
</folder> |
342 |
</folder> |
343 |
</folder></pre> |
344 |
* <code>"x-java"</code> should be replaced by the targeted mime type |
345 |
* |
346 |
* @return highligts layer factory that handles method chooser ui visualization |
347 |
*/ |
348 |
public static HighlightsLayerFactory createHighlihgtsLayerFactory() { |
349 |
return new MethodChooserHighlightsLayerFactory(); |
350 |
} |
351 |
|
352 |
static OffsetsBag getHighlightsBag(Document doc) { |
353 |
OffsetsBag bag = (OffsetsBag) doc.getProperty(MethodChooser.class); |
354 |
if (bag == null) { |
355 |
doc.putProperty(MethodChooser.class, bag = new OffsetsBag(doc, true)); |
356 |
} |
357 |
return bag; |
358 |
} |
359 |
|
360 |
private void findEditorPane() { |
361 |
editorPane = null; |
362 |
FileObject file; |
363 |
try { |
364 |
file = URLMapper.findFileObject(new URL(url)); |
365 |
} catch (MalformedURLException e) { |
366 |
return; |
367 |
} |
368 |
if (file == null) { |
369 |
return; |
370 |
} |
371 |
DataObject dobj = null; |
372 |
try { |
373 |
dobj = DataObject.find(file); |
374 |
} catch (DataObjectNotFoundException ex) { |
375 |
} |
376 |
if (dobj == null) { |
377 |
return; |
378 |
} |
379 |
final EditorCookie ec = (EditorCookie) dobj.getCookie(EditorCookie.class); |
380 |
try { |
381 |
SwingUtilities.invokeAndWait(new Runnable() { |
382 |
public void run() { |
383 |
JEditorPane[] openedPanes = ec.getOpenedPanes(); |
384 |
if (openedPanes != null) { |
385 |
editorPane = openedPanes[0]; |
386 |
} |
387 |
} |
388 |
}); |
389 |
} catch (InterruptedException ex) { |
390 |
Exceptions.printStackTrace(ex); |
391 |
} catch (InvocationTargetException ex) { |
392 |
Exceptions.printStackTrace(ex); |
393 |
} |
394 |
} |
395 |
|
396 |
private void requestRepaint() { |
397 |
if (attribsLeft == null) { |
398 |
Color foreground = editorPane.getForeground(); |
399 |
|
400 |
attribsLeft = createAttribs(EditorStyleConstants.LeftBorderLineColor, foreground, EditorStyleConstants.TopBorderLineColor, foreground, EditorStyleConstants.BottomBorderLineColor, foreground); |
401 |
attribsRight = createAttribs(EditorStyleConstants.RightBorderLineColor, foreground, EditorStyleConstants.TopBorderLineColor, foreground, EditorStyleConstants.BottomBorderLineColor, foreground); |
402 |
attribsMiddle = createAttribs(EditorStyleConstants.TopBorderLineColor, foreground, EditorStyleConstants.BottomBorderLineColor, foreground); |
403 |
attribsAll = createAttribs(EditorStyleConstants.LeftBorderLineColor, foreground, EditorStyleConstants.RightBorderLineColor, foreground, EditorStyleConstants.TopBorderLineColor, foreground, EditorStyleConstants.BottomBorderLineColor, foreground); |
404 |
|
405 |
attribsHyperlink = getHyperlinkHighlight(); |
406 |
|
407 |
attribsMethod = createAttribs(StyleConstants.Foreground, foreground, |
408 |
StyleConstants.Bold, Boolean.TRUE); |
409 |
|
410 |
attribsArea = createAttribs( |
411 |
StyleConstants.Foreground, foreground, |
412 |
StyleConstants.Italic, Boolean.FALSE, |
413 |
StyleConstants.Bold, Boolean.FALSE); |
414 |
} |
415 |
|
416 |
OffsetsBag newBag = new OffsetsBag(doc, true); |
417 |
int start = segments[0].getStartOffset(); |
418 |
int end = segments[segments.length - 1].getEndOffset(); |
419 |
newBag.addHighlight(start, end, attribsArea); |
420 |
|
421 |
for (int i = 0; i < segments.length; i++) { |
422 |
int startOffset = segments[i].getStartOffset(); |
423 |
int endOffset = segments[i].getEndOffset(); |
424 |
newBag.addHighlight(startOffset, endOffset, attribsMethod); |
425 |
if (selectedIndex == i) { |
426 |
int size = endOffset - startOffset; |
427 |
if (size == 1) { |
428 |
newBag.addHighlight(startOffset, endOffset, attribsAll); |
429 |
} else if (size > 1) { |
430 |
newBag.addHighlight(startOffset, startOffset + 1, attribsLeft); |
431 |
newBag.addHighlight(endOffset - 1, endOffset, attribsRight); |
432 |
if (size > 2) { |
433 |
newBag.addHighlight(startOffset + 1, endOffset - 1, attribsMiddle); |
434 |
} |
435 |
} |
436 |
} |
437 |
if (mousedIndex == i) { |
438 |
AttributeSet attr = AttributesUtilities.createComposite( |
439 |
attribsHyperlink, |
440 |
AttributesUtilities.createImmutable(EditorStyleConstants.Tooltip, new TooltipResolver()) |
441 |
); |
442 |
newBag.addHighlight(startOffset, endOffset, attr); |
443 |
} |
444 |
} |
445 |
|
446 |
OffsetsBag bag = getHighlightsBag(doc); |
447 |
bag.setHighlights(newBag); |
448 |
} |
449 |
|
450 |
private AttributeSet createAttribs(Object... keyValuePairs) { |
451 |
List<Object> list = new ArrayList<Object>(); |
452 |
for (int i = keyValuePairs.length / 2 - 1; i >= 0; i--) { |
453 |
Object attrKey = keyValuePairs[2 * i]; |
454 |
Object attrValue = keyValuePairs[2 * i + 1]; |
455 |
|
456 |
if (attrKey != null && attrValue != null) { |
457 |
list.add(attrKey); |
458 |
list.add(attrValue); |
459 |
} |
460 |
} |
461 |
return AttributesUtilities.createImmutable(list.toArray()); |
462 |
} |
463 |
|
464 |
private AttributeSet getHyperlinkHighlight() { |
465 |
synchronized(this) { |
466 |
if (defaultHyperlinkHighlight == null) { |
467 |
defaultHyperlinkHighlight = AttributesUtilities.createImmutable( |
468 |
StyleConstants.Foreground, Color.BLUE, StyleConstants.Underline, Color.BLUE); |
469 |
} |
470 |
} |
471 |
return defaultHyperlinkHighlight; |
472 |
} |
473 |
|
474 |
// ************************************************************************** |
475 |
// public inner classes |
476 |
// ************************************************************************** |
477 |
|
478 |
/** |
479 |
* Represents an interval of offsets in a document. Used to pass entry points |
480 |
* of method calls among which the user selects the desired one to step into. |
481 |
*/ |
482 |
public static class Segment { |
483 |
int startOffset; |
484 |
int endOffset; |
485 |
|
486 |
/** |
487 |
* Creates a new instance of {@link Segment}. |
488 |
* |
489 |
* @param startOffset segment start offset (inclusive) |
490 |
* @param endOffset segment end offset (exclusive) |
491 |
*/ |
492 |
public Segment(int startOffset, int endOffset) { |
493 |
this.startOffset = startOffset; |
494 |
this.endOffset = endOffset; |
495 |
} |
496 |
|
497 |
/** |
498 |
* Returns the start offset. |
499 |
* |
500 |
* @return the start offset |
501 |
*/ |
502 |
public int getStartOffset() { |
503 |
return startOffset; |
504 |
} |
505 |
|
506 |
/** |
507 |
* Returns the endt offset. |
508 |
* |
509 |
* @return the end offset |
510 |
*/ |
511 |
public int getEndOffset() { |
512 |
return endOffset; |
513 |
} |
514 |
|
515 |
} |
516 |
|
517 |
/** |
518 |
* An instance of {@link ReleaseListener} can be registered using {@link MethodChooser#addReleaseListener}. |
519 |
* It is notified when the selection mode finishes (e.g. if the user confirms the current selection). |
520 |
* The selection mode finishes whenever {@link #releaseUI} is called. |
521 |
*/ |
522 |
public interface ReleaseListener { |
523 |
|
524 |
/** |
525 |
* Called on the method selection mode finish. |
526 |
* |
527 |
* @param performAction <code>true</code> means that the current selection has been confirmed |
528 |
* and the proper action (Step Into) is expected to be performed, <code>false</code> |
529 |
* means that the method chooser has been cancelled |
530 |
*/ |
531 |
public void released(boolean performAction); |
532 |
|
533 |
} |
534 |
|
535 |
// ************************************************************************** |
536 |
// private inner classes |
537 |
// ************************************************************************** |
538 |
|
539 |
private class CentralListener implements KeyListener, MouseListener, MouseMotionListener, FocusListener { |
540 |
|
541 |
// ************************************************************************** |
542 |
// KeyListener implementation |
543 |
// ************************************************************************** |
544 |
|
545 |
@Override |
546 |
public void keyTyped(KeyEvent e) { |
547 |
e.consume(); |
548 |
} |
549 |
|
550 |
@Override |
551 |
public void keyPressed(KeyEvent e) { |
552 |
int code = e.getKeyCode(); |
553 |
boolean consumeEvent = true; |
554 |
switch (code) { |
555 |
case KeyEvent.VK_ENTER: |
556 |
case KeyEvent.VK_SPACE: |
557 |
// selection confirmed |
558 |
releaseUI(true); |
559 |
break; |
560 |
case KeyEvent.VK_ESCAPE: |
561 |
// action canceled |
562 |
releaseUI(false); |
563 |
break; |
564 |
case KeyEvent.VK_RIGHT: |
565 |
case KeyEvent.VK_DOWN: |
566 |
case KeyEvent.VK_TAB: |
567 |
selectedIndex++; |
568 |
if (selectedIndex == segments.length) { |
569 |
selectedIndex = 0; |
570 |
} |
571 |
requestRepaint(); |
572 |
break; |
573 |
case KeyEvent.VK_LEFT: |
574 |
case KeyEvent.VK_UP: |
575 |
selectedIndex--; |
576 |
if (selectedIndex < 0) { |
577 |
selectedIndex = segments.length - 1; |
578 |
} |
579 |
requestRepaint(); |
580 |
break; |
581 |
case KeyEvent.VK_HOME: |
582 |
selectedIndex = 0; |
583 |
requestRepaint(); |
584 |
break; |
585 |
case KeyEvent.VK_END: |
586 |
selectedIndex = segments.length - 1; |
587 |
requestRepaint(); |
588 |
break; |
589 |
default: |
590 |
int mods = e.getModifiersEx(); |
591 |
for (int x = 0; x < stopEvents.length; x++) { |
592 |
if (stopEvents[x].getKeyCode() == code && |
593 |
(stopEvents[x].getModifiers() & mods) == stopEvents[x].getModifiers()) { |
594 |
releaseUI(false); |
595 |
consumeEvent = false; |
596 |
break; |
597 |
} |
598 |
} |
599 |
for (int x = 0; x < confirmEvents.length; x++) { |
600 |
if (confirmEvents[x].getKeyCode() == code && |
601 |
(confirmEvents[x].getModifiers() & mods) == confirmEvents[x].getModifiers()) { |
602 |
releaseUI(true); |
603 |
break; |
604 |
} |
605 |
} |
606 |
} |
607 |
if (consumeEvent) { |
608 |
e.consume(); |
609 |
} |
610 |
} |
611 |
|
612 |
@Override |
613 |
public void keyReleased(KeyEvent e) { |
614 |
e.consume(); |
615 |
} |
616 |
|
617 |
// ************************************************************************** |
618 |
// MouseListener and MouseMotionListener implementation |
619 |
// ************************************************************************** |
620 |
|
621 |
@Override |
622 |
public void mouseClicked(MouseEvent e) { |
623 |
if (e.isPopupTrigger()) { |
624 |
return; |
625 |
} |
626 |
e.consume(); |
627 |
int position = editorPane.viewToModel(e.getPoint()); |
628 |
if (e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON1) { |
629 |
if (position < 0) { |
630 |
return; |
631 |
} |
632 |
if (mousedIndex != -1) { |
633 |
selectedIndex = mousedIndex; |
634 |
releaseUI(true); |
635 |
return; |
636 |
} |
637 |
} |
638 |
try { |
639 |
int line = Utilities.getLineOffset((BaseDocument) doc, position) + 1; |
640 |
if (line < startLine || line > endLine) { |
641 |
releaseUI(false); |
642 |
return; |
643 |
} |
644 |
} catch (BadLocationException ex) { |
645 |
} |
646 |
} |
647 |
|
648 |
@Override |
649 |
public void mouseMoved(MouseEvent e) { |
650 |
e.consume(); |
651 |
int position = editorPane.viewToModel(e.getPoint()); |
652 |
int newIndex = -1; |
653 |
if (position >= 0) { |
654 |
for (int x = 0; x < segments.length; x++) { |
655 |
int start = segments[x].getStartOffset(); |
656 |
int end = segments[x].getEndOffset(); |
657 |
if (position >= start && position <= end) { |
658 |
newIndex = x; |
659 |
break; |
660 |
} |
661 |
} // for |
662 |
} // if |
663 |
if (newIndex != mousedIndex) { |
664 |
if (newIndex == -1) { |
665 |
editorPane.setCursor(arrowCursor); |
666 |
} else { |
667 |
editorPane.setCursor(handCursor); |
668 |
} |
669 |
mousedIndex = newIndex; |
670 |
requestRepaint(); |
671 |
} |
672 |
} |
673 |
|
674 |
@Override |
675 |
public void mouseReleased(MouseEvent e) { |
676 |
e.consume(); |
677 |
} |
678 |
|
679 |
@Override |
680 |
public void mousePressed(MouseEvent e) { |
681 |
e.consume(); |
682 |
} |
683 |
|
684 |
@Override |
685 |
public void mouseExited(MouseEvent e) { |
686 |
e.consume(); |
687 |
} |
688 |
|
689 |
@Override |
690 |
public void mouseEntered(MouseEvent e) { |
691 |
e.consume(); |
692 |
} |
693 |
|
694 |
@Override |
695 |
public void mouseDragged(MouseEvent e) { |
696 |
e.consume(); |
697 |
} |
698 |
|
699 |
// ************************************************************************** |
700 |
// FocusListener implementation |
701 |
// ************************************************************************** |
702 |
|
703 |
@Override |
704 |
public void focusGained(FocusEvent e) { |
705 |
editorPane.getCaret().setVisible(false); |
706 |
} |
707 |
|
708 |
@Override |
709 |
public void focusLost(FocusEvent e) { |
710 |
} |
711 |
|
712 |
} |
713 |
|
714 |
static class MethodChooserHighlightsLayerFactory implements HighlightsLayerFactory { |
715 |
|
716 |
@Override |
717 |
public HighlightsLayer[] createLayers(Context context) { |
718 |
return new HighlightsLayer[] { |
719 |
HighlightsLayer.create(MethodChooser.class.getName(), |
720 |
ZOrder.TOP_RACK, false, MethodChooser.getHighlightsBag(context.getDocument())) |
721 |
}; |
722 |
} |
723 |
|
724 |
} |
725 |
|
726 |
private static final class TooltipResolver implements HighlightAttributeValue<String> { |
727 |
|
728 |
public TooltipResolver() { |
729 |
} |
730 |
|
731 |
@Override |
732 |
public String getValue(JTextComponent component, Document document, Object attributeKey, int startOffset, int endOffset) { |
733 |
return NbBundle.getMessage(MethodChooser.class, "MSG_Step_Into_Method"); |
734 |
} |
735 |
|
736 |
} |
737 |
|
738 |
} |