MethodChooser
added.
+ MethodChooser
class added. It is a support for Step Into action
+ implementations. Providing a simple graphical interface, it allows the user
+ to select in a source file a method call the debugger should step into.
+ It has been originally implemented in the jpda debugger module, now it can be
+ reused by other debuggers.
+
The method chooser is initialized by an url (pointing to a source file), an array of + * {@link Segment} elements (each of them corresponds typically to a method call name + * in the source file) and an index of the segment element which is displayed + * as the default selection. + * + *
Optionally, two sets of (additional) shortcuts that confirm, resp. cancel the selection + * mode can be specified. + * It is also possible to pass a text, which should be shown at the editor pane's + * status line after the selection mode has been activated. This text serves as a hint + * to the user how to make the method call selection. + * + *
Method chooser does not use any special highlighting for the background of the + * area where the selection takes place. If it is required it can be done by attaching + * instances of {@link Annotation} to the proper source file's lines. These annotation should + * be added before calling {@link #showUI} and removed after calling {@link #releaseUI}. + * + *
To display the method chooser's ui correctly, it is required to register + * {@link HighlightsLayerFactory} created by {@link #createHighlihgtsLayerFactory} + * in an xml layer. An example follows. + * + *
+ <folder name="Editors"> + <folder name="text"> + <folder name="x-java"> + <file name="org.netbeans.spi.editor.highlighting.HighlightsLayerFactory.instance"> + <attr name="instanceCreate" methodvalue="org.netbeans.spi.debugger.ui.MethodChooser.createHighlihgtsLayerFactory"/> + </file> + </folder> + </folder> + </folder>+ *
"x-java"
should be replaced by the targeted mime type.
+ *
+ * @author Daniel Prusa
+ * @since 2.22
+ */
+public class MethodChooser {
+
+ private static AttributeSet defaultHyperlinkHighlight;
+
+ private String url;
+ private Segment[] segments;
+ private int selectedIndex = -1;
+ private String hintText;
+ private KeyStroke[] stopEvents;
+ private KeyStroke[] confirmEvents;
+
+ private AttributeSet attribsLeft = null;
+ private AttributeSet attribsRight = null;
+ private AttributeSet attribsMiddle = null;
+ private AttributeSet attribsAll = null;
+
+ private AttributeSet attribsArea = null;
+ private AttributeSet attribsMethod = null;
+ private AttributeSet attribsHyperlink = null;
+
+ private Cursor handCursor;
+ private Cursor arrowCursor;
+ private Cursor originalCursor;
+
+ private CentralListener mainListener;
+ private Document doc;
+ private JEditorPane editorPane;
+ private Listtrue
if a {@link JEditorPane} has been found and the selection mode
+ * has been properly displayed
+ */
+ public boolean showUI() {
+ findEditorPane();
+ if (editorPane == null) {
+ return false; // cannot do anything without editor
+ }
+ doc = editorPane.getDocument();
+ // compute start line and end line
+ int minOffs = Integer.MAX_VALUE;
+ int maxOffs = 0;
+ for (int x = 0; x < segments.length; x++) {
+ minOffs = Math.min(segments[x].getStartOffset(), minOffs);
+ maxOffs = Math.max(segments[x].getEndOffset(), maxOffs);
+ }
+ try {
+ startLine = Utilities.getLineOffset((BaseDocument)doc, minOffs) + 1;
+ endLine = Utilities.getLineOffset((BaseDocument)doc, maxOffs) + 1;
+ } catch (BadLocationException e) {
+ }
+ // continue by showing method selection ui
+ mainListener = new CentralListener();
+ editorPane.putClientProperty(MethodChooser.class, this);
+ editorPane.addKeyListener(mainListener);
+ editorPane.addMouseListener(mainListener);
+ editorPane.addMouseMotionListener(mainListener);
+ editorPane.addFocusListener(mainListener);
+ originalCursor = editorPane.getCursor();
+ handCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
+ arrowCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
+ editorPane.setCursor(arrowCursor);
+ Caret caret = editorPane.getCaret();
+ if (caret instanceof BaseCaret) {
+ ((BaseCaret)caret).setVisible(false);
+ }
+ requestRepaint();
+ if (hintText != null && hintText.trim().length() > 0) {
+ Utilities.setStatusText(editorPane, " " + hintText);
+ }
+ isInSelectMode = true;
+ return true;
+ }
+
+ /**
+ * Ends the method selection mode, clears all used ui elements. Notifies each registered
+ * {@link ReleaseListener}.
+ *
+ * @param performAction true
indicates that the current selection should
+ * be used to perform an action, false
means that the selection mode
+ * has beencancelled
+ */
+ public synchronized void releaseUI(boolean performAction) {
+ if (!isInSelectMode) {
+ return; // do nothing
+ }
+ getHighlightsBag(doc).clear();
+ editorPane.removeKeyListener(mainListener);
+ editorPane.removeMouseListener(mainListener);
+ editorPane.removeMouseMotionListener(mainListener);
+ editorPane.removeFocusListener(mainListener);
+ editorPane.putClientProperty(MethodChooser.class, null);
+ editorPane.setCursor(originalCursor);
+ Caret caret = editorPane.getCaret();
+ if (caret instanceof BaseCaret) {
+ ((BaseCaret)caret).setVisible(true);
+ }
+
+ if (hintText != null && hintText.trim().length() > 0) {
+ Utilities.clearStatusText(editorPane);
+ }
+ isInSelectMode = false;
+ for (ReleaseListener listener : releaseListeners) {
+ listener.released(performAction);
+ }
+ }
+
+ /**
+ * Can be used to check whether the selection mode is activated.
+ *
+ * @return true
if the method selection mode is currently displayed
+ */
+ public boolean isUIActive() {
+ return isInSelectMode;
+ }
+
+ /**
+ * Returns index of {@link Segment} that is currently selected. If the method
+ * chooser has been released, it corresponds to the final selection made by the user.
+ *
+ * @return index of currently selected method
+ */
+ public int getSelectedIndex() {
+ return selectedIndex;
+ }
+
+ /**
+ * Registers {@link ReleaseListener}. The listener is notified when the selection
+ * mode finishes. This occurs whenever the user comfirms (or cancels) the current
+ * selection. It also occrus when {@link #releaseUI} is called.
+ *
+ * @param listener an instance of {@link ReleaseListener} to be registered
+ */
+ public synchronized void addReleaseListener(ReleaseListener listener) {
+ releaseListeners.add(listener);
+ }
+
+ /**
+ * Unregisters {@link ReleaseListener}.
+ *
+ * @param listener an instance of {@link ReleaseListener} to be unregistered
+ */
+ public synchronized void removeReleaseListener(ReleaseListener listener) {
+ releaseListeners.remove(listener);
+ }
+
+ /**
+ * This method should be referenced in xml layer files. To display the method
+ * chooser ui correctly, it is required to register an instance of
+ * {@link HighlightsLayerFactory} using the following pattern.
+ * + <folder name="Editors"> + <folder name="text"> + <folder name="x-java"> + <file name="org.netbeans.spi.editor.highlighting.HighlightsLayerFactory.instance"> + <attr name="instanceCreate" methodvalue="org.netbeans.spi.debugger.ui.MethodChooser.createHighlihgtsLayerFactory"/> + </file> + </folder> + </folder> + </folder>+ *
"x-java"
should be replaced by the targeted mime type
+ *
+ * @return highligts layer factory that handles method chooser ui visualization
+ */
+ public static HighlightsLayerFactory createHighlihgtsLayerFactory() {
+ return new MethodChooserHighlightsLayerFactory();
+ }
+
+ static OffsetsBag getHighlightsBag(Document doc) {
+ OffsetsBag bag = (OffsetsBag) doc.getProperty(MethodChooser.class);
+ if (bag == null) {
+ doc.putProperty(MethodChooser.class, bag = new OffsetsBag(doc, true));
+ }
+ return bag;
+ }
+
+ private void findEditorPane() {
+ editorPane = null;
+ FileObject file;
+ try {
+ file = URLMapper.findFileObject(new URL(url));
+ } catch (MalformedURLException e) {
+ return;
+ }
+ if (file == null) {
+ return;
+ }
+ DataObject dobj = null;
+ try {
+ dobj = DataObject.find(file);
+ } catch (DataObjectNotFoundException ex) {
+ }
+ if (dobj == null) {
+ return;
+ }
+ final EditorCookie ec = (EditorCookie) dobj.getCookie(EditorCookie.class);
+ try {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ JEditorPane[] openedPanes = ec.getOpenedPanes();
+ if (openedPanes != null) {
+ editorPane = openedPanes[0];
+ }
+ }
+ });
+ } catch (InterruptedException ex) {
+ Exceptions.printStackTrace(ex);
+ } catch (InvocationTargetException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ }
+
+ private void requestRepaint() {
+ if (attribsLeft == null) {
+ Color foreground = editorPane.getForeground();
+
+ attribsLeft = createAttribs(EditorStyleConstants.LeftBorderLineColor, foreground, EditorStyleConstants.TopBorderLineColor, foreground, EditorStyleConstants.BottomBorderLineColor, foreground);
+ attribsRight = createAttribs(EditorStyleConstants.RightBorderLineColor, foreground, EditorStyleConstants.TopBorderLineColor, foreground, EditorStyleConstants.BottomBorderLineColor, foreground);
+ attribsMiddle = createAttribs(EditorStyleConstants.TopBorderLineColor, foreground, EditorStyleConstants.BottomBorderLineColor, foreground);
+ attribsAll = createAttribs(EditorStyleConstants.LeftBorderLineColor, foreground, EditorStyleConstants.RightBorderLineColor, foreground, EditorStyleConstants.TopBorderLineColor, foreground, EditorStyleConstants.BottomBorderLineColor, foreground);
+
+ attribsHyperlink = getHyperlinkHighlight();
+
+ attribsMethod = createAttribs(StyleConstants.Foreground, foreground,
+ StyleConstants.Bold, Boolean.TRUE);
+
+ attribsArea = createAttribs(
+ StyleConstants.Foreground, foreground,
+ StyleConstants.Italic, Boolean.FALSE,
+ StyleConstants.Bold, Boolean.FALSE);
+ }
+
+ OffsetsBag newBag = new OffsetsBag(doc, true);
+ int start = segments[0].getStartOffset();
+ int end = segments[segments.length - 1].getEndOffset();
+ newBag.addHighlight(start, end, attribsArea);
+
+ for (int i = 0; i < segments.length; i++) {
+ int startOffset = segments[i].getStartOffset();
+ int endOffset = segments[i].getEndOffset();
+ newBag.addHighlight(startOffset, endOffset, attribsMethod);
+ if (selectedIndex == i) {
+ int size = endOffset - startOffset;
+ if (size == 1) {
+ newBag.addHighlight(startOffset, endOffset, attribsAll);
+ } else if (size > 1) {
+ newBag.addHighlight(startOffset, startOffset + 1, attribsLeft);
+ newBag.addHighlight(endOffset - 1, endOffset, attribsRight);
+ if (size > 2) {
+ newBag.addHighlight(startOffset + 1, endOffset - 1, attribsMiddle);
+ }
+ }
+ }
+ if (mousedIndex == i) {
+ AttributeSet attr = AttributesUtilities.createComposite(
+ attribsHyperlink,
+ AttributesUtilities.createImmutable(EditorStyleConstants.Tooltip, new TooltipResolver())
+ );
+ newBag.addHighlight(startOffset, endOffset, attr);
+ }
+ }
+
+ OffsetsBag bag = getHighlightsBag(doc);
+ bag.setHighlights(newBag);
+ }
+
+ private AttributeSet createAttribs(Object... keyValuePairs) {
+ List