diff --git a/editor.lib/nbproject/project.properties b/editor.lib/nbproject/project.properties
--- a/editor.lib/nbproject/project.properties
+++ b/editor.lib/nbproject/project.properties
@@ -42,7 +42,7 @@
javac.compilerargs=-Xlint:unchecked
javac.source=1.7
-spec.version.base=4.5.0
+spec.version.base=4.6.0
is.autoload=true
javadoc.arch=${basedir}/arch.xml
diff --git a/editor.lib/src/org/netbeans/editor/BaseKit.java b/editor.lib/src/org/netbeans/editor/BaseKit.java
--- a/editor.lib/src/org/netbeans/editor/BaseKit.java
+++ b/editor.lib/src/org/netbeans/editor/BaseKit.java
@@ -122,6 +122,7 @@
import org.netbeans.modules.editor.lib.KitsTracker;
import org.netbeans.api.editor.NavigationHistory;
import org.netbeans.api.editor.caret.CaretMoveContext;
+import org.netbeans.api.editor.caret.MoveCaretsOrigin;
import org.netbeans.spi.editor.caret.CaretMoveHandler;
import org.netbeans.lib.editor.util.swing.PositionRegion;
import org.netbeans.modules.editor.lib.SettingsConversions;
@@ -2551,7 +2552,9 @@
}
}
}
- });
+ }, new MoveCaretsOrigin(
+ MoveCaretsOrigin.DIRECT_NAVIGATION, SwingConstants.NORTH)
+ );
} else {
try {
int dot = caret.getDot();
@@ -2643,7 +2646,9 @@
}
}
}
- });
+ }, new MoveCaretsOrigin(
+ MoveCaretsOrigin.DIRECT_NAVIGATION, SwingConstants.SOUTH)
+ );
} else {
try {
int dot = caret.getDot();
@@ -2785,6 +2790,7 @@
}
// Update magic caret position
+ newCaretBounds = target.modelToView(caretInfo.getDot());
magicCaretPosition.y = newCaretBounds.y;
context.setMagicCaretPosition(caretInfo, magicCaretPosition);
}
@@ -2792,7 +2798,9 @@
target.getToolkit().beep();
}
}
- });
+ }, new MoveCaretsOrigin(
+ MoveCaretsOrigin.DIRECT_NAVIGATION, SwingConstants.NORTH)
+ );
} else {
int caretOffset = caret.getDot();
Rectangle caretBounds = ((BaseTextUI)target.getUI()).modelToView(target, caretOffset);
@@ -2929,7 +2937,9 @@
}
}
}
- });
+ }, new MoveCaretsOrigin(
+ MoveCaretsOrigin.DIRECT_NAVIGATION, SwingConstants.EAST)
+ );
}
} else {
try {
@@ -3073,7 +3083,9 @@
target.getToolkit().beep();
}
}
- });
+ }, new MoveCaretsOrigin(
+ MoveCaretsOrigin.DIRECT_NAVIGATION, SwingConstants.SOUTH)
+ );
} else {
int caretOffset = caret.getDot();
Rectangle caretBounds = ((BaseTextUI)target.getUI()).modelToView(target, caretOffset);
@@ -3205,7 +3217,9 @@
}
}
}
- });
+ }, new MoveCaretsOrigin(
+ MoveCaretsOrigin.DIRECT_NAVIGATION, SwingConstants.WEST)
+ );
}
} else {
try {
@@ -3343,7 +3357,9 @@
}
}
}
- });
+ }, new MoveCaretsOrigin(
+ MoveCaretsOrigin.DIRECT_NAVIGATION, SwingConstants.WEST)
+ );
} else {
try {
int dot = caret.getDot();
@@ -3470,8 +3486,9 @@
}
}
}
- });
-
+ }, new MoveCaretsOrigin(
+ MoveCaretsOrigin.DIRECT_NAVIGATION, SwingConstants.EAST)
+ );
} else {
try {
// #232675: if bounds are defined, use them rather than line start/end
diff --git a/editor.lib2/apichanges.xml b/editor.lib2/apichanges.xml
--- a/editor.lib2/apichanges.xml
+++ b/editor.lib2/apichanges.xml
@@ -107,6 +107,30 @@
+
+ Support for Navigation Filters, and caret move origins
+
+
+
+
+
+
+ Swing NavigationFilters implemented on top of the Caret API. Caret API caller may describe the operation which causes
+ the caret to be moved, so that filters and caret listeners can react on specific action groups.
+
+
+ A boilerplate NavigationFilter is provided, that supports chaining of filters on the caret
+
+
+
+
+
+
+
+
+
+
+
Added EditorRegistry.findComponent
diff --git a/editor.lib2/manifest.mf b/editor.lib2/manifest.mf
--- a/editor.lib2/manifest.mf
+++ b/editor.lib2/manifest.mf
@@ -1,6 +1,6 @@
Manifest-Version: 1.0
OpenIDE-Module: org.netbeans.modules.editor.lib2/1
-OpenIDE-Module-Implementation-Version: 45
+OpenIDE-Module-Implementation-Version: 46
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/lib2/Bundle.properties
OpenIDE-Module-Layer: org/netbeans/modules/editor/lib2/resources/layer.xml
OpenIDE-Module-Needs: org.netbeans.modules.editor.actions
diff --git a/editor.lib2/nbproject/project.properties b/editor.lib2/nbproject/project.properties
--- a/editor.lib2/nbproject/project.properties
+++ b/editor.lib2/nbproject/project.properties
@@ -43,7 +43,7 @@
is.autoload=true
javac.source=1.7
javac.compilerargs=-Xlint:unchecked
-spec.version.base=2.8.0
+spec.version.base=2.9.0
javadoc.arch=${basedir}/arch.xml
javadoc.apichanges=${basedir}/apichanges.xml
diff --git a/editor.lib2/src/org/netbeans/api/editor/caret/CaretMoveContext.java b/editor.lib2/src/org/netbeans/api/editor/caret/CaretMoveContext.java
--- a/editor.lib2/src/org/netbeans/api/editor/caret/CaretMoveContext.java
+++ b/editor.lib2/src/org/netbeans/api/editor/caret/CaretMoveContext.java
@@ -45,11 +45,12 @@
import java.util.List;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
+import javax.swing.text.NavigationFilter;
import javax.swing.text.Position;
import org.netbeans.api.annotations.common.NonNull;
/**
- * Context for carets moving within {@link CaretMoveHandler}.
+ * Context for carets moving within {@link org.netbeans.spi.editor.caret.CaretMoveHandler}.
*
* @author Miloslav Metelka
* @since 2.6
@@ -113,19 +114,33 @@
* or true otherwise.
*/
public boolean setDot(@NonNull CaretInfo caret, @NonNull Position dotPos) {
- return setDotAndMark(caret, dotPos, dotPos);
+ NavigationFilter naviFilter = transaction.getCaret().getNavigationFilterNoDefault(getMoveOrigin());
+ if (naviFilter != null) {
+ FilterBypassImpl fbi = new FilterBypassImpl(transaction, caret, transaction.getDocument());
+ naviFilter.setDot(fbi, dotPos.getOffset(), Position.Bias.Forward);
+ return fbi.getResult();
+ } else {
+ return setDotAndMark(caret, dotPos, dotPos);
+ }
}
/**
* Move dot of the given getCaret so getCaret selection gets created or changed.
*
- * @param caret non-null getCaret.
+ * @param caret Nebnon-null getCaret.
* @param dotPos new dot position.
* @return false if passed caret is obsolete or invalid (e.g. a member of another {@link EditorCaret})
* or true otherwise.
*/
public boolean moveDot(@NonNull CaretInfo caret, @NonNull Position dotPos) {
- return transaction.moveDot(caret.getCaretItem(), dotPos);
+ NavigationFilter naviFilter = transaction.getCaret().getNavigationFilterNoDefault(getMoveOrigin());
+ if (naviFilter != null) {
+ FilterBypassImpl fbi = new FilterBypassImpl(transaction, caret, transaction.getDocument());
+ naviFilter.moveDot(fbi, dotPos.getOffset(), Position.Bias.Forward);
+ return fbi.getResult();
+ } else {
+ return transaction.moveDot(caret.getCaretItem(), dotPos);
+ }
}
/**
@@ -172,5 +187,18 @@
public Document getDocument() {
return getComponent().getDocument();
}
+
+ /**
+ * Describes the origin of the movement, the calling operation. Can be null
+ * if the origin could not be described. Should identify {@link MoveCaretsOrigin#DIRECT_NAVIGATION}
+ * actionType for simple navigation actions.
+ *
+ * @return movement description
+ * @see MoveCaretsOrigin
+ * @since 2.9
+ */
+ public @NonNull MoveCaretsOrigin getMoveOrigin() {
+ return transaction.getOrigin();
+ }
}
diff --git a/editor.lib2/src/org/netbeans/api/editor/caret/CaretTransaction.java b/editor.lib2/src/org/netbeans/api/editor/caret/CaretTransaction.java
--- a/editor.lib2/src/org/netbeans/api/editor/caret/CaretTransaction.java
+++ b/editor.lib2/src/org/netbeans/api/editor/caret/CaretTransaction.java
@@ -119,12 +119,14 @@
private boolean fullResort;
+ private final MoveCaretsOrigin origin;
- CaretTransaction(EditorCaret caret, JTextComponent component, Document doc) {
+ CaretTransaction(EditorCaret caret, JTextComponent component, Document doc, MoveCaretsOrigin origin) {
this.editorCaret = caret;
this.component = component;
this.doc = doc;
this.origCaretItems = editorCaret.getCaretItems();
+ this.origin = origin;
}
@@ -265,6 +267,10 @@
// Current impl ignores possible replaceItems content - see getOriginalCarets()
return editorCaret.getSortedCarets();
}
+
+ MoveCaretsOrigin getOrigin() {
+ return origin;
+ }
void replaceCarets(RemoveType removeType, int offset, CaretItem[] addCaretItems) {
int size = origCaretItems.size();
diff --git a/editor.lib2/src/org/netbeans/api/editor/caret/EditorCaret.java b/editor.lib2/src/org/netbeans/api/editor/caret/EditorCaret.java
--- a/editor.lib2/src/org/netbeans/api/editor/caret/EditorCaret.java
+++ b/editor.lib2/src/org/netbeans/api/editor/caret/EditorCaret.java
@@ -41,6 +41,7 @@
*/
package org.netbeans.api.editor.caret;
+import org.netbeans.spi.editor.caret.CascadingNavigationFilter;
import org.netbeans.spi.editor.caret.CaretMoveHandler;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
@@ -76,7 +77,9 @@
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -103,10 +106,12 @@
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
+import javax.swing.text.NavigationFilter;
import javax.swing.text.Position;
import javax.swing.text.StyleConstants;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.editor.EditorUtilities;
import org.netbeans.api.editor.document.AtomicLockDocument;
import org.netbeans.api.editor.document.AtomicLockEvent;
@@ -133,7 +138,9 @@
import org.netbeans.modules.editor.lib2.view.ViewHierarchyEvent;
import org.netbeans.modules.editor.lib2.view.ViewHierarchyListener;
import org.netbeans.modules.editor.lib2.view.ViewUtils;
+import org.netbeans.spi.editor.caret.NavigationFilterBypass;
import org.openide.util.Exceptions;
+import org.openide.util.Parameters;
import org.openide.util.WeakListeners;
/**
@@ -159,6 +166,7 @@
// Temporary until rectangular selection gets ported to multi-caret support
private static final String RECTANGULAR_SELECTION_PROPERTY = "rectangular-selection"; // NOI18N
private static final String RECTANGULAR_SELECTION_REGIONS_PROPERTY = "rectangular-selection-regions"; // NOI18N
+ private static final String NAVIGATION_FILTER_PROPERTY = EditorCaret.class.getName() + ".navigationFilters"; // NOI18N
// -J-Dorg.netbeans.api.editor.caret.EditorCaret.level=FINEST
private static final Logger LOG = Logger.getLogger(EditorCaret.class.getName());
@@ -371,6 +379,11 @@
* with the same cursor.
*/
private boolean showingTextCursor = true;
+
+ /**
+ * Bottom navigation filter, which delegates to the Component.
+ */
+ private NavigationFilter chainNavigationFilter;
public EditorCaret() {
caretItems = new GapList<>();
@@ -504,6 +517,28 @@
* @see Caret#setDot(int)
*/
public @Override void setDot(final int offset) {
+ setDot(offset, MoveCaretsOrigin.DEFAULT);
+ }
+
+ /**
+ * Assign a new offset to the caret and identify the operation which
+ * originated the caret movement.
+ *
+ * In addition to {@link #setDot(int)},
+ * the caller may identify the operation that originated the caret movement.
+ * This information is received by {@link NavigationFilter}s or {@link ChangeListener}s
+ * and may be used to react or modify the caret movements.
+ *
+ * Use {@code null} or {@link MoveCaretsOrigin#DEFAULT} if the operation not known. Use
+ * {@link MoveCaretsOrigin#DIRECT_NAVIGATION} action type to identify simple navigational
+ * actions (pg up, pg down, left, right, ...).
+ *
+ * @param offset new offset for the caret
+ * @param orig specifies the operation which caused the caret to move.
+ * @see #setDot(int)
+ * @since 2.9
+ */
+ public void setDot(final int offset, MoveCaretsOrigin orig) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("setDot: offset=" + offset); //NOI18N
if (LOG.isLoggable(Level.FINEST)) {
@@ -523,7 +558,7 @@
}
}
}
- });
+ }, orig);
}
/**
@@ -537,6 +572,29 @@
* @see Caret#moveDot(int)
*/
public @Override void moveDot(final int offset) {
+ moveDot(offset, MoveCaretsOrigin.DEFAULT);
+ }
+
+ /**
+ * Moves the caret position (dot) to some other position, leaving behind the
+ * mark.
+ *
+ * In addition to {@link #setDot(int)},
+ * the caller may identify the operation that originated the caret movement.
+ * This information is received by {@link NavigationFilter}s or {@link EditorCaretListener}s
+ * and may be used to react or modify the caret movements.
+ *
+ * Use {@code null} or {@link MoveCaretsOrigin#DEFAULT} if the operation not known. Use
+ * {@link MoveCaretsOrigin#DIRECT_NAVIGATION} action type to identify simple navigational
+ * actions (pg up, pg down, left, right, ...).
+ *
+ *
+ * @param offset new offset for the caret
+ * @param orig specifies the operation which caused the caret to move.
+ * @see #moveDot(int)
+ * @since 2.9
+ */
+ public void moveDot(final int offset, MoveCaretsOrigin orig) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("moveDot: offset=" + offset); //NOI18N
}
@@ -554,7 +612,7 @@
}
}
}
- });
+ }, orig);
}
/**
@@ -593,8 +651,39 @@
* or no document installed in the text component.
*/
public int moveCarets(@NonNull CaretMoveHandler moveHandler) {
+ Parameters.notNull("moveHandler", moveHandler);
return runTransaction(CaretTransaction.RemoveType.NO_REMOVE, 0, null, moveHandler);
}
+
+ /**
+ * Move multiple carets or create/modify selections, specifies the originating operation.
+ *
+ * In addition to {@link #moveCarets(org.netbeans.spi.editor.caret.CaretMoveHandler)}, the caller may specify
+ * what operation causes the caret movements in the `origin' parameter, see {@link MoveCaretsOrigin} class.
+ * This information is received by {@link NavigationFilter}s or {@link EditorCaretListener}s
+ * and may be used to react or modify the caret movements.
+ *
+ * Use {@code null} or {@link MoveCaretsOrigin#DEFAULT} if the operation not known. Use
+ * {@link MoveCaretsOrigin#DIRECT_NAVIGATION} action type to identify simple navigational
+ * actions (pg up, pg down, left, right, ...).
+ *
+ * See the {@link #moveCarets(org.netbeans.spi.editor.caret.CaretMoveHandler) } for detailed description of
+ * how carets are moved.
+ *
+ *
+ * @param moveHandler handler which moves individual carets
+ * @param origin description of the originating operation. Use {@code null} or {@link MoveCaretsOrigin#DEFAULT} for default/unspecified operation.
+ * @return difference between number of carets, see {@link #moveCarets(org.netbeans.spi.editor.caret.CaretMoveHandler)}.
+ * @see #moveCarets(org.netbeans.spi.editor.caret.CaretMoveHandler)
+ * @since 2.9
+ */
+ public int moveCarets(@NonNull CaretMoveHandler moveHandler, MoveCaretsOrigin origin) {
+ Parameters.notNull("moveHandler", moveHandler);
+ if (origin == null) {
+ origin = MoveCaretsOrigin.DEFAULT;
+ }
+ return runTransaction(CaretTransaction.RemoveType.NO_REMOVE, 0, null, moveHandler, origin);
+ }
/**
* Create a new caret at the given position with a possible selection.
@@ -855,6 +944,8 @@
}
listenerImpl.focusGained(null); // emulate focus gained
}
+
+ this.chainNavigationFilter = new ChainNavigationFilter(component);
invalidateCaretBounds(0);
dispatchUpdate(false);
@@ -1036,6 +1127,174 @@
}
}
}
+
+ /**
+ * Returns the navigation filter for a certain operation.
+ * {@link NavigationFilter} can be
+ * registered to receive only limited set of operations. This method returns the filter
+ * for the specified operation. Use {@link MoveCaretsOrigin#DEFAULT} to get text
+ * component's navigation filter (equivalent to {@link JTextComponent#getNavigationFilter()
+ * JTextComponent.getNavigationFilter()}. That filter receives all caret movements.
+ * @param origin the operation description
+ * @return the current navigation filter.
+ * @since 2.9
+ */
+ public @CheckForNull NavigationFilter getNavigationFilter(@NonNull MoveCaretsOrigin origin) {
+ Parameters.notNull("origin", origin);
+ if (origin == MoveCaretsOrigin.DEFAULT) {
+ return component.getNavigationFilter();
+ }
+ NavigationFilter navi = doGetNavigationFilter(origin.getActionType());
+ // Note: a special delegator is returned, since the component's navigation filter queue
+ // can be manipulated after call to getNavigationFilter. So if we would have returned the global filter instance directly,
+ // the calling client may unknowingly bypass certain (global) filters registered after call to this method.
+ // In other words, there are two possible insertion points into the navigation filter chanin
+ return navi != null ? navi : chainNavigationFilter;
+ }
+
+ /**
+ * Variant of {@link #getNavigationFilter}, which does not default to chaining navigation
+ * filter
+ * @param origin operation specifier
+ * @return navigation filter or {@code null}
+ */
+ @CheckForNull NavigationFilter getNavigationFilterNoDefault(@NonNull MoveCaretsOrigin origin) {
+ if (origin == MoveCaretsOrigin.DEFAULT) {
+ return component.getNavigationFilter();
+ }
+ NavigationFilter navi2 = doGetNavigationFilter(origin.getActionType());
+ return navi2 != null ? navi2 : component.getNavigationFilter();
+ }
+
+ /**
+ * Bottom navigation filter which delegates to the main NavigationFilter in the
+ * Component (if it exists).
+ */
+ private static class ChainNavigationFilter extends NavigationFilter {
+ private final JTextComponent component;
+
+ public ChainNavigationFilter(JTextComponent component) {
+ this.component = component;
+ }
+
+ @Override
+ public int getNextVisualPositionFrom(JTextComponent text, int pos, Position.Bias bias, int direction, Position.Bias[] biasRet) throws BadLocationException {
+ NavigationFilter chain = component.getNavigationFilter();
+ return chain != null ? chain.getNextVisualPositionFrom(text, pos, bias, direction, biasRet) : super.getNextVisualPositionFrom(text, pos, bias, direction, biasRet);
+ }
+
+ @Override
+ public void moveDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias) {
+ NavigationFilter chain = component.getNavigationFilter();
+ if (chain != null) {
+ chain.moveDot(fb, dot, bias);
+ } else {
+ super.moveDot(fb, dot, bias);
+ }
+ }
+
+ @Override
+ public void setDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias) {
+ NavigationFilter chain = component.getNavigationFilter();
+ if (chain != null) {
+ chain.setDot(fb, dot, bias);
+ } else {
+ super.setDot(fb, dot, bias);
+ }
+ }
+ }
+
+ /**
+ * Sets navigation filter for a certain operation type, defined by {@link MoveCaretsOrigin}.
+ *
+ * The registered filter will receive only those caret movements, which correspond to the
+ * passed {@link MoveCaretsOrigin}. To receive all caret movements, register for {@link MoveCaretsOrigin#DEFAULT}
+ * or use {@link JTextComponent#setNavigationFilter}.
+ *
+ * All the key part(s) of MoveCaretOrigin of a caret operation and `origin' parameter in this function must
+ * match in order for the filter to be invoked.
+ *
+ * The NavigationFilter implementation may downcast the passed {@link NavigationFilter.FilterBypass FilterBypass}
+ * parameter to {@link NavigationFilterBypass} to get full infomration about the movement.
+ *
+ * @param origin the origin
+ * @param naviFilter the installed filter
+ * @see JTextComponent#setNavigationFilter
+ * @see NavigationFilterBypass
+ * @since 2.9
+ */
+ public void setNavigationFilter(MoveCaretsOrigin origin, @NullAllowed NavigationFilter naviFilter) {
+ if (origin == null) {
+ origin = MoveCaretsOrigin.DEFAULT;
+ }
+ final NavigationFilter prev = getNavigationFilter(origin);
+ if (naviFilter != null) {
+ // Note:
+ // if the caller passes in a non-cascading filter, we would loose the filter chain information.
+ // the alien filter is wrapped by CascadingNavigationFilter delegator, so the previous filter
+ // link is preserved.
+ if (!(naviFilter instanceof CascadingNavigationFilter)) {
+ final NavigationFilter del = naviFilter;
+ naviFilter = new CascadingNavigationFilter() {
+ @Override
+ public void setDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias) {
+ del.setDot(fb, dot, bias);
+ }
+
+ @Override
+ public void moveDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias) {
+ del.moveDot(fb, dot, bias);
+ }
+
+ @Override
+ public int getNextVisualPositionFrom(JTextComponent text, int pos, Position.Bias bias, int direction, Position.Bias[] biasRet) throws BadLocationException {
+ return del.getNextVisualPositionFrom(text, pos, bias, direction, biasRet);
+ }
+ };
+ }
+ ((CascadingNavigationFilter)naviFilter).setOwnerAndPrevious(this, origin, prev);
+ }
+ if (MoveCaretsOrigin.DEFAULT == origin) {
+ component.setNavigationFilter(naviFilter);
+ } else {
+ doPutNavigationFilter(origin.getActionType(), prev);
+ }
+ }
+
+ /**
+ * Records the navigation filter. Note that the filter is stored in the JTextComponent rather than
+ * in this Caret. If the Component's UI changes or the caret is recreated for some reason, the
+ * navigation filters remain registered.
+ *
+ * @param type type of nav filter
+ * @param n the filter instance
+ */
+ private void doPutNavigationFilter(String type, NavigationFilter n) {
+ if (component == null) {
+ throw new IllegalStateException("Not attached to a Component");
+ }
+ Map m = (Map)component.getClientProperty(NAVIGATION_FILTER_PROPERTY);
+ if (m == null) {
+ if (n == null) {
+ return;
+ }
+ m = new HashMap<>();
+ component.putClientProperty(NAVIGATION_FILTER_PROPERTY, m);
+ }
+ if (n == null) {
+ m.remove(type);
+ } else {
+ m.put(type, n);
+ }
+ }
+
+ private NavigationFilter doGetNavigationFilter(String n) {
+ if (component == null) {
+ throw new IllegalStateException("Not attached to a Component");
+ }
+ Map m = (Map)component.getClientProperty(NAVIGATION_FILTER_PROPERTY);
+ return m == null ? null : m.get(n);
+ }
@Override
public int getBlinkRate() {
@@ -1143,7 +1402,7 @@
moveDot(newDotOffset); // updates rs and fires state change
} else {
updateRectangularSelectionPaintRect();
- fireStateChanged();
+ fireStateChanged(null);
}
} catch (BadLocationException ex) {
// Leave selection as is
@@ -1190,13 +1449,17 @@
* @return
*/
private int runTransaction(CaretTransaction.RemoveType removeType, int offset, CaretItem[] addCarets, CaretMoveHandler moveHandler) {
+ return runTransaction(removeType, offset, addCarets, moveHandler, MoveCaretsOrigin.DEFAULT);
+ }
+
+ private int runTransaction(CaretTransaction.RemoveType removeType, int offset, CaretItem[] addCarets, CaretMoveHandler moveHandler, MoveCaretsOrigin org) {
lock();
try {
if (activeTransaction == null) {
JTextComponent c = component;
Document d = activeDoc;
if (c != null && d != null) {
- activeTransaction = new CaretTransaction(this, c, d);
+ activeTransaction = new CaretTransaction(this, c, d, org);
if (LOG.isLoggable(Level.FINE)) {
StringBuilder msgBuilder = new StringBuilder(200);
msgBuilder.append("EditorCaret.runTransaction(): removeType=").append(removeType).
@@ -1276,7 +1539,7 @@
}
if (activeTransaction.isAnyChange()) {
// For now clear the lists and use old way TODO update to selective updating and rendering
- fireStateChanged();
+ fireStateChanged(activeTransaction.getOrigin());
dispatchUpdate(true);
resetBlink();
}
@@ -1329,14 +1592,14 @@
/**
* Notifies listeners that caret position has changed.
*/
- private void fireStateChanged() {
+ private void fireStateChanged(final MoveCaretsOrigin origin) {
Runnable runnable = new Runnable() {
public @Override void run() {
JTextComponent c = component;
if (c == null || c.getCaret() != EditorCaret.this) {
return;
}
- fireEditorCaretChange(new EditorCaretEvent(EditorCaret.this, 0, Integer.MAX_VALUE)); // [TODO] temp firing without detailed info
+ fireEditorCaretChange(new EditorCaretEvent(EditorCaret.this, 0, Integer.MAX_VALUE, origin)); // [TODO] temp firing without detailed info
ChangeEvent evt = new ChangeEvent(EditorCaret.this);
List listeners = changeListenerList.getListeners();
for (ChangeListener l : listeners) {
@@ -1883,7 +2146,7 @@
rsDotRect.x = r.x;
rsDotRect.width = r.width;
updateRectangularSelectionPaintRect();
- fireStateChanged();
+ fireStateChanged(null);
}
}
@@ -2120,7 +2383,7 @@
} else { // No rectangular selection
RectangularSelectionTransferHandler.uninstall(component);
}
- fireStateChanged();
+ fireStateChanged(null);
}
}
}
@@ -2177,7 +2440,7 @@
invalidateCaretBounds(offset);
dispatchUpdate();
resetBlink();
- fireStateChanged();
+ fireStateChanged(null);
modified = false;
}
} else {
diff --git a/editor.lib2/src/org/netbeans/api/editor/caret/EditorCaretEvent.java b/editor.lib2/src/org/netbeans/api/editor/caret/EditorCaretEvent.java
--- a/editor.lib2/src/org/netbeans/api/editor/caret/EditorCaretEvent.java
+++ b/editor.lib2/src/org/netbeans/api/editor/caret/EditorCaretEvent.java
@@ -41,6 +41,7 @@
*/
package org.netbeans.api.editor.caret;
+import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
/**
@@ -55,10 +56,13 @@
private final int affectedEndOffset;
- EditorCaretEvent(EditorCaret source, int affectedStartOffset, int affectedEndOffset) {
+ private final MoveCaretsOrigin origin;
+
+ EditorCaretEvent(EditorCaret source, int affectedStartOffset, int affectedEndOffset, MoveCaretsOrigin origin) {
super(source);
this.affectedStartOffset = affectedStartOffset;
this.affectedEndOffset = affectedEndOffset;
+ this.origin = origin;
}
/**
@@ -91,4 +95,15 @@
public int getAffectedEndOffset() {
return affectedEndOffset;
}
+
+ /**
+ * Describes the origin of the movement command. May not be filled, if the movement
+ * does not originate form an user-triggered editor action.
+ *
+ * @return the original reason for caret movement, or {@code null}.
+ * @since 2.9
+ */
+ public @CheckForNull MoveCaretsOrigin getOrigin() {
+ return origin;
+ }
}
diff --git a/editor.lib2/src/org/netbeans/api/editor/caret/FilterBypassImpl.java b/editor.lib2/src/org/netbeans/api/editor/caret/FilterBypassImpl.java
new file mode 100644
--- /dev/null
+++ b/editor.lib2/src/org/netbeans/api/editor/caret/FilterBypassImpl.java
@@ -0,0 +1,244 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2016 Sun Microsystems, Inc.
+ */
+package org.netbeans.api.editor.caret;
+
+import org.netbeans.spi.editor.caret.NavigationFilterBypass;
+import java.awt.Graphics;
+import java.awt.Point;
+import javax.swing.event.ChangeListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Caret;
+import javax.swing.text.Document;
+import javax.swing.text.JTextComponent;
+import javax.swing.text.Position;
+import org.openide.util.Exceptions;
+
+/**
+ * Implementation of the FilterBypass suitable for multi-caret environment.
+ * Provides a special {@link Caret} implementatio, which works with the current
+ * {@link CaretItem} being manipulated, so that if client calls {@link Caret#setDot}
+ * or the like, the instruction will be executed and bypasses all NavigationFilters.
+ *
+ * @author sdedic
+ */
+class FilterBypassImpl extends NavigationFilterBypass {
+ private final CaretTransaction transaction;
+ private final CaretInfo item;
+ private final Document doc;
+ private Caret itemCaret;
+ private boolean result;
+
+ public FilterBypassImpl(CaretTransaction transaction, CaretInfo item, Document doc) {
+ this.transaction = transaction;
+ this.item = item;
+ this.doc = doc;
+ }
+
+ @Override
+ public CaretInfo getCaretItem() {
+ return item;
+ }
+
+ @Override
+ public EditorCaret getEditorCaret() {
+ return transaction.getCaret();
+ }
+
+ @Override
+ public Caret getCaret() {
+ if (itemCaret == null) {
+ itemCaret = new ItemCaret();
+ }
+ return itemCaret;
+ }
+
+ @Override
+ public MoveCaretsOrigin getOrigin() {
+ return transaction.getOrigin();
+ }
+
+ boolean getResult() {
+ return result;
+ }
+
+ @Override
+ public void setDot(final int dot, Position.Bias bias) {
+ Position dotPos = createPosition(dot, bias);
+ result = transaction.setDotAndMark(item.getCaretItem(),
+ dotPos, dotPos);
+ }
+
+ private Position createPosition(final int dot, Position.Bias bias) {
+ final Position[] p = new Position[1];
+
+ doc.render(new Runnable() {
+ public void run() {
+ p[0] = createPosition0(dot, null);
+ }
+ });
+ return p[0];
+ }
+
+ private Position createPosition0(int dot, Position.Bias bias) {
+ try {
+ // FIXME: bias is not used. LineDocument should be used
+ // in preference to document to create biased positions.
+ // bypass handlers
+ if (dot < 0) {
+ return doc.createPosition(0);
+ } else if (dot > doc.getLength()) {
+ return doc.createPosition(doc.getLength());
+ } else {
+ return doc.createPosition(dot);
+ }
+ } catch (BadLocationException ex) {
+ // should not happen, checked under doc lock
+ Exceptions.printStackTrace(ex);
+ return item.getDotPosition();
+ }
+ }
+
+ @Override
+ public void moveDot(int dot, Position.Bias bias) {
+ result = transaction.setDotAndMark(item.getCaretItem(),
+ createPosition(dot, bias),
+ item.getMarkPosition()
+ );
+ }
+
+ /**
+ * Custom caret implementation, which cleverly delegates to CaretItem
+ * and transaction to carry out most of Caret API tasks against the current
+ * CaretItem.
+ * Unsupported operations throw an exception.
+ */
+ private class ItemCaret implements Caret {
+ private void notPermitted() {
+ throw new UnsupportedOperationException("Disallowed in NavigationFilter"); // NOI18N
+ }
+ @Override
+ public void install(JTextComponent c) {
+ notPermitted();
+ }
+
+ @Override
+ public void deinstall(JTextComponent c) {
+ notPermitted();
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ notPermitted();
+ }
+
+ @Override
+ public void addChangeListener(ChangeListener l) {
+ transaction.getCaret().addChangeListener(l);
+ }
+
+ @Override
+ public void removeChangeListener(ChangeListener l) {
+ transaction.getCaret().removeChangeListener(l);
+ }
+
+ @Override
+ public boolean isVisible() {
+ return transaction.getCaret().isVisible();
+ }
+
+ @Override
+ public void setVisible(boolean v) {
+ transaction.getCaret().setVisible(v);
+ }
+
+ @Override
+ public boolean isSelectionVisible() {
+ return transaction.getCaret().isSelectionVisible();
+ }
+
+ @Override
+ public void setSelectionVisible(boolean v) {
+ transaction.getCaret().setSelectionVisible(v);
+ }
+
+ @Override
+ public void setMagicCaretPosition(Point p) {
+ transaction.setMagicCaretPosition(item.getCaretItem(), p);
+ }
+
+ @Override
+ public Point getMagicCaretPosition() {
+ return item.getMagicCaretPosition();
+ }
+
+ @Override
+ public void setBlinkRate(int rate) {
+ transaction.getCaret().setBlinkRate(rate);
+ }
+
+ @Override
+ public int getBlinkRate() {
+ return transaction.getCaret().getBlinkRate();
+ }
+
+ @Override
+ public int getDot() {
+ return item.getDot();
+ }
+
+ @Override
+ public int getMark() {
+ return item.getMark();
+ }
+
+ @Override
+ public void setDot(int dot) {
+ FilterBypassImpl.this.setDot(dot, Position.Bias.Forward);
+ }
+
+ @Override
+ public void moveDot(int dot) {
+ FilterBypassImpl.this.moveDot(dot, Position.Bias.Forward);
+ }
+
+ }
+}
diff --git a/editor.lib2/src/org/netbeans/api/editor/caret/MoveCaretsOrigin.java b/editor.lib2/src/org/netbeans/api/editor/caret/MoveCaretsOrigin.java
new file mode 100644
--- /dev/null
+++ b/editor.lib2/src/org/netbeans/api/editor/caret/MoveCaretsOrigin.java
@@ -0,0 +1,178 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2016 Sun Microsystems, Inc.
+ */
+package org.netbeans.api.editor.caret;
+
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.spi.editor.caret.NavigationFilterBypass;
+import org.openide.util.Parameters;
+
+/**
+ * Describes the operation which initiated the caret navigation.
+ *
+ * The core implementation
+ * supports operation type (token) as a key and original intended movement direction as a hint
+ * for possible filtering. Future API versions may define more details.
+ *
+ * This class is used in two modes:
+ *
+ *
+ * - When moving a caret ({@link EditorCaret#setDot(int, org.netbeans.api.editor.caret.MoveCaretsOrigin) setDot(pos, origin)},
+ * {@link EditorCaret#moveDot(int, org.netbeans.api.editor.caret.MoveCaretsOrigin) moveDot(pos, origin)},
+ * {@link EditorCaret#moveCarets(org.netbeans.spi.editor.caret.CaretMoveHandler, org.netbeans.api.editor.caret.MoveCaretsOrigin) moveCarets(handler, origin)}),
+ * as a information on the originating operation ({@link #getActionType getActionType()}) and possibly additional
+ * hints that describes the intended caret move ({@link #getDirection() getDirection()}).
+ *
+ * -
+ * When {@link EditorCaret#setNavigationFilter(org.netbeans.api.editor.caret.MoveCaretsOrigin, javax.swing.text.NavigationFilter) registering a NavigationFilter}
+ * as a filtering template.
+ *
+ *
+ *
+ *
+ *
+ * The intended usage in caret moving code (actions) is as follows:
+ *
+ *
+ * // Action perform method
+ * editorCaret.moveCarets(new CaretMoveHandler() {
+ * @Override
+ * public void moveCarets(CaretMoveContext context) {
+ * ...
+ * }
+ * }, new MoveCaretsOrigin(
+ * // The action is a raw movement command
+ * MoveCaretsOrigin.DIRECT_NAVIGATION,
+ * // The approximate direction of the movement; can be 0.
+ * SwingConstants.NORTH)
+ * );
+ *
+ *
+ * If a {@link javax.swing.text.NavigationFilter} only wants to intercept certain type of moevements, it can register as follows:
+ *
+ *
+ * EditorCaret eCaret = .... ; // obtain EditorCaret
+ * eCaret.setNavigationFilter(
+ * new NavigationFilter() {
+ * // navigation filter implementation, not important for the example
+ * },
+ * new MoveCaretsOrigin(MoveCaretsOrigin.DIRECT_NAVIGATION)
+ * );
+ *
+ *
+ * If the NavigationFilter implementation wants to obtain the extended information for the caret movement,
+ * it can downcast the received FilterBypass:
+ *
+ *
+ * public void setDot(FilterBypass fb, int dot, Position.Bias bias) {
+ * if (fb instanceof NavigationFilterBypass) {
+ * NavigationFilterBypass nfb = (NavigationFilterBypass)fb;
+ *
+ * // get the Origin object created by the caret-moving operation, can query the details
+ * MoveCaretsOrigin origin = nfb.getOrigin();
+ *
+ * // get the individual caret in multi-caret scenario
+ * CaretInfo info = nfb.getCaretInfo();
+ *
+ * // get the whole EditorCaret
+ * EditorCaret eCaret = nfb.getEditorCaret();
+ * }
+ * }
+ *
+ *
+ * @see NavigationFilterBypass
+ * @since 2.9
+ */
+public final class MoveCaretsOrigin {
+ /**
+ * Actions, which are defined as moving or setting the caret. Do not user for actions
+ * like search (moves caret to the found string), goto type (moves to the definition) etc.
+ */
+ public static final String DIRECT_NAVIGATION = "navigation.action"; // NOI18N
+ /**
+ * Undefined action type. Use this description when registering for all possible
+ * caret movements.
+ */
+ public static final MoveCaretsOrigin DEFAULT = new MoveCaretsOrigin("default", 0); // NOI18N
+ private final String actionType;
+ private final int direction;
+
+ /**
+ * Describes the origin by just the action type.
+ * @param actionType action type
+ */
+ public MoveCaretsOrigin(String actionType) {
+ this.actionType = actionType;
+ this.direction = 0;
+ }
+
+ /**
+ * Specifies the origin by action type, and the overall moving direction
+ * @param actionType action type
+ * @param direction the intended direction of movement
+ */
+ public MoveCaretsOrigin(@NonNull String actionType, int direction) {
+ Parameters.notNull("actionType", actionType); // NOI18N
+ this.actionType = actionType;
+ this.direction = direction;
+ }
+
+ /**
+ * Returns the type of the action which originated the caret movement.
+ * @return the action type.
+ */
+ @NonNull
+ public String getActionType() {
+ return actionType;
+ }
+
+ /**
+ * Specifies the desired movement direction. Use {@link javax.swing.SwingConstants}
+ * compass constants to specify the direction. 0 means the direction
+ * is unspecified.
+ *
+ * @return The initial direction of movemnet
+ */
+ public int getDirection() {
+ return direction;
+ }
+
+}
diff --git a/editor.lib2/src/org/netbeans/api/editor/caret/package.html b/editor.lib2/src/org/netbeans/api/editor/caret/package.html
--- a/editor.lib2/src/org/netbeans/api/editor/caret/package.html
+++ b/editor.lib2/src/org/netbeans/api/editor/caret/package.html
@@ -131,13 +131,105 @@
concurrently.
+
+
+ It may be desirable (especially for NavigationListeners, but possibly for
+ EditorCaretListeners too) to determine
+ what was the reason leading to caret movement. The caller of Caret API which intends to position the caret
+ may provide an optional MoveCaretsOrigin instance to Caret API methods to indicate
+ type of action leading to the movement.
+ This description is used to select NavigationFilters which receive and can modify the movement, and is available
+ to all invoked NavigationFilters or EditorCaretListeners contacted by the Caret API operation.
+
+
+ Only one action type is currently defined by the
+ API - DIRECT_NAVIGATION. This type identifies operations
+ actions, whose only task is to reposition the caret (i.e. left/right, page-up, document-begin) from actions,
+ which position caret as a part of larger task (i.e. search, goto type, ...).
+
+
+
+
+ The Caret API implements and extends the concept of swing
+ NavigationFilters. The Filter
+ gets notified on CaretInfo
movement; so if more Carets are present,
+ a NavigationFilter
will see all their moves. Navigation Filters are
+ called with a special instance of
+ FilterBypass extending
+ NavigationFilterBypass. The Filter can downcast the FilterBypass to access extended information
+ about the current caret's movement:
+
+
+
+ public void setDot(FilterBypass fb, int dot, Position.Bias bias) {
+ if (fb instanceof NavigationFilterBypass) {
+ NavigationFilterBypass nfb = (NavigationFilterBypass)fb;
+
+ // get the Origin object created by the caret-moving operation, can query the details
+ MoveCaretsOrigin origin = nfb.getOrigin();
+
+ // get the individual caret in multi-caret scenario
+ CaretInfo info = nfb.getCaretInfo();
+
+ // get the whole EditorCaret
+ EditorCaret eCaret = nfb.getEditorCaret();
+ }
+ }
+
+
+
+ Navigation filters can be also selectively registered for only certain type of actions described by
+ MoveCaretsOrigin instance.
+
+
+
+ EditorCaret eCaret = .... ; // obtain EditorCaret
+ eCaret.setNavigationFilter(
+ new NavigationFilter() {
+ // navigation filter implementation, not important for the example
+ },
+ new MoveCaretsOrigin(MoveCaretsOrigin.DIRECT_NAVIGATION)
+ );
+
+
+
+ Such filters are only called if the caller of Caret API provides a suitable MoveCaretsOrigin description of the move operatoion.
+
+
+
+
+ // Action perform method
+ editorCaret.moveCarets(new CaretMoveHandler() {
+ @Override
+ public void moveCarets(CaretMoveContext context) {
+ ...
+ }
+ }, new MoveCaretsOrigin(
+ // The action is a raw movement command
+ MoveCaretsOrigin.DIRECT_NAVIGATION,
+ // The approximate direction of the movement; can be 0.
+ SwingConstants.NORTH)
+ );
+
+
+
+ Swing NavigationFilters, specified by TextComponent.setNavigationFilter()
+ are called for all caret movements.
+
+ Abstract boilerplate for NavigationFilters
is created,
+ CascadingNavigationFilter
+ which allows to chain individual filters. Implementors are encouraged to use it, or otherwise
+ pass the control to previously registered NavigationFilter
in case the movement
+ event is not handled by the custom implementation.
+
+
As a technical limitation, EditorCaret
has to implement Caret
to be able to work with Swings Text API. The Caret
interface is not
aware of multiple carets and a call to setDot(int)
will only retain
a single caret. For multiple carets a call to moveDot(int)
will move the last
- caret only (but it retains other existing carets).
+ caret only (but it retains other existing carets).+
editorTextComponent.getCaret()
will now always return EditorCaret
instance instead of the original caret implementation org.netbeans.editor.BaseCaret
.
diff --git a/editor.lib2/src/org/netbeans/spi/editor/caret/CascadingNavigationFilter.java b/editor.lib2/src/org/netbeans/spi/editor/caret/CascadingNavigationFilter.java
new file mode 100644
--- /dev/null
+++ b/editor.lib2/src/org/netbeans/spi/editor/caret/CascadingNavigationFilter.java
@@ -0,0 +1,176 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2016 Sun Microsystems, Inc.
+ */
+package org.netbeans.spi.editor.caret;
+
+import javax.swing.text.BadLocationException;
+import javax.swing.text.JTextComponent;
+import javax.swing.text.NavigationFilter;
+import javax.swing.text.Position;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.editor.caret.EditorCaret;
+import org.netbeans.api.editor.caret.MoveCaretsOrigin;
+import org.openide.util.Parameters;
+
+/**
+ * Boilerplate {@link NavigationFilter}, which supports chaining of filters
+ * on an JTextComponent.
+ *
+ * The implementation should call super methods to
+ * allow lower-precedence filters to react. If the implementation desires to
+ * disable the filters and take the movement action directly, it can still use
+ * the {@link FilterBypass} instance passed.
+ *
+ * There are helper {@link #register} and {@link #unregister} methods which
+ * ensure the chain of filters is correctly maintained. After registering, methods
+ * implemented by this class will delegate to the remembered formerly-toplevel filter.
+ * Implementor of this class may safely call super.* methods to delegate to filters
+ * further in the chain.
+ *
+ *
+ * @author sdedic
+ * @since 2.9
+ */
+public abstract class CascadingNavigationFilter extends NavigationFilter {
+ private NavigationFilter previous;
+ private EditorCaret owner;
+ private MoveCaretsOrigin regKey;
+
+ /**
+ * Returns the next filter in the chain. This class' implementations of NavigationFilter
+ * API methods delegate to that filter, if non-null. Results after this Filter is
+ * unregistered (removed from the NavigationFilter) chain
+ * are undefined.
+ *
+ * @return next NavigationFilter.
+ */
+ protected final NavigationFilter getNextFilter() {
+ return previous;
+ }
+
+ @Override
+ public int getNextVisualPositionFrom(JTextComponent text, int pos, Position.Bias bias, int direction, Position.Bias[] biasRet) throws BadLocationException {
+ return previous != null ?
+ previous.getNextVisualPositionFrom(text, pos, bias, direction, biasRet) :
+ super.getNextVisualPositionFrom(text, pos, bias, direction, biasRet);
+ }
+
+ @Override
+ public void moveDot(FilterBypass fb, int dot, Position.Bias bias) {
+ if (previous != null) {
+ previous.moveDot(fb, dot, bias);
+ } else {
+ super.moveDot(fb, dot, bias);
+ }
+ }
+
+ @Override
+ public void setDot(FilterBypass fb, int dot, Position.Bias bias) {
+ if (previous != null) {
+ previous.setDot(fb, dot, bias);
+ } else {
+ super.setDot(fb, dot, bias);
+ }
+ }
+
+ /**
+ * Removes this NavigationFilter from the chain; preceding filter will
+ * be connected to the following one, so the chain will not be broken.
+ */
+ public final void unregister() {
+ if (regKey == null) {
+ // not registered
+ return;
+ }
+ NavigationFilter f = owner.getNavigationFilter(regKey);
+ CascadingNavigationFilter next = null;
+
+ while (f instanceof CascadingNavigationFilter && f != this) {
+ next = (CascadingNavigationFilter)f;
+ f = next.getNextFilter();
+ }
+ if (f != this) {
+ return;
+ }
+ if (next == null) {
+ owner.setNavigationFilter(regKey, previous);
+ } else {
+ next.previous = previous;
+ }
+ // reset state
+ this.owner = null;
+ this.previous = null;
+ }
+
+ /**
+ * Registers this Filter into the NavigationFilter chain.
+ *
+ * This filter will
+ * be placed on top of the filter's chain and the formerly-toplevel filter will
+ * be remembered for delegation.
+ *
+ * It is not permitted to register with more carets; make multiple instances of
+ * the filter for that case.
+ *
+ *
+ * @param caret where this Filter should be registered.
+ * @param origin operation specifier
+ */
+ public final void register(
+ @NonNull EditorCaret caret,
+ @NonNull MoveCaretsOrigin origin) {
+ Parameters.notNull("caret", caret);
+ Parameters.notNull("origin", origin);
+ if (owner != null) {
+ throw new IllegalStateException();
+ }
+ caret.setNavigationFilter(origin, this);
+ }
+
+ public void setOwnerAndPrevious(EditorCaret ec, MoveCaretsOrigin orig, NavigationFilter prev) {
+ if (this.owner != null) {
+ throw new IllegalStateException("Can be registered only once");
+ }
+ this.owner = ec;
+ this.previous = prev;
+ this.regKey = orig;
+ }
+}
diff --git a/editor.lib2/src/org/netbeans/spi/editor/caret/NavigationFilterBypass.java b/editor.lib2/src/org/netbeans/spi/editor/caret/NavigationFilterBypass.java
new file mode 100644
--- /dev/null
+++ b/editor.lib2/src/org/netbeans/spi/editor/caret/NavigationFilterBypass.java
@@ -0,0 +1,82 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2016 Sun Microsystems, Inc.
+ */
+package org.netbeans.spi.editor.caret;
+
+import javax.swing.text.NavigationFilter;
+import org.netbeans.api.annotations.common.NonNull;
+import org.netbeans.api.editor.caret.CaretInfo;
+import org.netbeans.api.editor.caret.EditorCaret;
+import org.netbeans.api.editor.caret.MoveCaretsOrigin;
+
+/**
+ * Enhanced FilterBypass which understands multicaret.
+ *
+ * Implementations of
+ * {@link NavigationFilter} may check if the FilterBypass is instanceof this class,
+ * and if so, they can access extended information.
+ *
+ * If the caret move operation is initiated by new caret APIs, the FilterBypass passed
+ * to NavigationFilters always satisfies this interface.
+ *
+ * @author sdedic
+ * @since 2.9
+ */
+public abstract class NavigationFilterBypass extends NavigationFilter.FilterBypass {
+ /**
+ * Returns the currently changing CaretItem.
+ *
+ * @return CaretItem the caret instance being changed
+ */
+ public abstract @NonNull CaretInfo getCaretItem();
+
+ /**
+ * Access to the entire EditorCaret abstraction
+ * @return the editor caret
+ */
+ public abstract @NonNull EditorCaret getEditorCaret();
+
+ /**
+ * Describes the origin / reason of the movement.
+ * @return The origin object provided by the caret movement initiator.
+ */
+ public abstract @NonNull MoveCaretsOrigin getOrigin();
+}
diff --git a/editor.lib2/src/org/netbeans/spi/editor/caret/package.html b/editor.lib2/src/org/netbeans/spi/editor/caret/package.html
--- a/editor.lib2/src/org/netbeans/spi/editor/caret/package.html
+++ b/editor.lib2/src/org/netbeans/spi/editor/caret/package.html
@@ -70,7 +70,23 @@
});
-
+
+
+
+ A boilerplate CascadingNavigationFilter is provided to
+ make implementation of NavigationFilters easier. The boilerplate
+ remembers the preceding filter and will delegate to it. If you create a subclass, you may call super
methods
+ moveDot
and setDot
to delegate to that previous filter and ultimately perform the action. Calling
+ methods of FilterBypass will perform the caret action
+ directly.
+
+
+ The filter can find out the origin of the movement or
+ the info for actual caret being moved. The
+ FilterBypass implementation passed to
+ NavigationFilter can be downcasted to NavigationFilterBypass,
+ which provides this extended information.
+