/** * 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 static void doPutNavigationFilter(JTextComponent component, String type, NavigationFilter n) { if (component == null) { throw new IllegalStateException("Not attached to a Component"); } Map<String, NavigationFilter> m = (Map<String, NavigationFilter>)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); } }
/** * 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 = EditorCaret.getNavigationFilter(owner, regKey); CascadingNavigationFilter next = null; while (f instanceof CascadingNavigationFilter && f != this) { next = (CascadingNavigationFilter)f; f = next.getNextFilter(); } if (f != this) { return; } if (next == null) { EditorCaret.setNavigationFilter(owner, regKey, previous); } else { next.previous = previous; } // reset state this.owner = null; this.previous = null; }
public void testInstallUninstall_Filters() { NavigationFilter navFilter = new NavigationFilter(); formatter.setNavigationFilter(navFilter); DocumentFilter docFilter = new DocumentFilter(); formatter.setDocumentFilter(docFilter); AbstractDocument doc = (AbstractDocument) tf.getDocument(); assertNull(tf.getNavigationFilter()); assertNull(doc.getDocumentFilter()); formatter.install(tf); assertEquals(navFilter, tf.getNavigationFilter()); assertEquals(docFilter, doc.getDocumentFilter()); formatter.uninstall(); assertNull(tf.getNavigationFilter()); assertNull(doc.getDocumentFilter()); formatter.install(tf); assertEquals(navFilter, tf.getNavigationFilter()); assertEquals(docFilter, doc.getDocumentFilter()); formatter.install(null); assertNull(tf.getNavigationFilter()); assertNull(doc.getDocumentFilter()); }
/** * Change dot of the given caret. * * @param caret non-null caret. * @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 setDot(@NonNull CaretInfo caret, @NonNull Position dotPos, @NonNull Position.Bias dotBias) { NavigationFilter naviFilter = transaction.getCaret().getNavigationFilterNoDefault(transaction.getOrigin()); 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, dotBias, dotPos, dotBias); } }
/** * Move dot of the given caret so caret selection gets created or changed. * * @param caret non-null caret. * @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, @NonNull Position.Bias dotBias) { NavigationFilter naviFilter = transaction.getCaret().getNavigationFilterNoDefault(transaction.getOrigin()); 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, dotBias); } }
/** * 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 component the component whose filter should be returned * @param origin the operation description * @return the current navigation filter. * @since 2.10 */ public static @CheckForNull NavigationFilter getNavigationFilter(@NonNull JTextComponent component, @NonNull MoveCaretsOrigin origin) { Parameters.notNull("origin", origin); if (origin == MoveCaretsOrigin.DEFAULT) { return component.getNavigationFilter(); } else if (origin == MoveCaretsOrigin.DISABLE_FILTERS) { return null; } NavigationFilter navi = doGetNavigationFilter(component, 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 : getChainNavigationFilter(component); }
/** * 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(); } else if (origin == MoveCaretsOrigin.DISABLE_FILTERS) { return null; } NavigationFilter navi2 = doGetNavigationFilter(component, origin.getActionType()); return navi2 != null ? navi2 : component.getNavigationFilter(); }
@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}. * <p> * The registered filter will receive <b>only those caret movements</b>, which correspond to the * passed {@link MoveCaretsOrigin}. To receive all caret movements, register for {@link MoveCaretsOrigin#DEFAULT} * or use {@link JTextComponent#setNavigationFilter}. * </p><p> * 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. * </p><p> * The NavigationFilter implementation <b>may downcast</b> the passed {@link NavigationFilter.FilterBypass FilterBypass} * parameter to {@link NavigationFilterBypass} to get full infomration about the movement. * </p> * @param component the component which will use the filter * @param origin the origin * @param naviFilter the installed filter * @see JTextComponent#setNavigationFilter * @see NavigationFilterBypass * @since 2.10 */ public static void setNavigationFilter(JTextComponent component, MoveCaretsOrigin origin, @NullAllowed NavigationFilter naviFilter) { if (origin == null) { origin = MoveCaretsOrigin.DEFAULT; } final NavigationFilter prev = getNavigationFilter(component, 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(component, origin, prev); } if (MoveCaretsOrigin.DEFAULT == origin) { component.setNavigationFilter(naviFilter); } else { doPutNavigationFilter(component, origin.getActionType(), prev); } }
private static NavigationFilter getChainNavigationFilter(JTextComponent component) { NavigationFilter chain = (NavigationFilter)component.getClientProperty(CHAIN_FILTER_PROPERTY); if (chain == null) { component.putClientProperty(CHAIN_FILTER_PROPERTY, chain = new ChainNavigationFilter(component)); } return chain; }
private static NavigationFilter doGetNavigationFilter(JTextComponent component, String n) { if (component == null) { throw new IllegalStateException("Not attached to a Component"); } Map<String, NavigationFilter> m = (Map<String, NavigationFilter>)component.getClientProperty(NAVIGATION_FILTER_PROPERTY); return m == null ? null : m.get(n); }
public void setOwnerAndPrevious(JTextComponent component, MoveCaretsOrigin orig, NavigationFilter prev) { if (this.owner != null) { throw new IllegalStateException("Can be registered only once"); } this.owner = component; this.previous = prev; this.regKey = orig; }
@Override public void setDot(NavigationFilter.FilterBypass bypass, int dot, Position.Bias bias) { if (dot > classNameLength) { bypass.setDot(classNameLength, bias); } else { super.setDot(bypass, dot, bias); } }
@Override public void moveDot(NavigationFilter.FilterBypass bypass, int dot, Position.Bias bias) { if (dot > classNameLength) { bypass.moveDot(classNameLength, bias); } else { super.moveDot(bypass, dot, bias); } }
@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); }
public static void main(String[] args) throws Exception { SwingUtilities.invokeAndWait(new Runnable() { public void run() { jtf = new JFormattedTextField(); nf = new NavigationFilter(); jtf.setText("A text message"); jFrame = new JFrame(); jFrame.getContentPane().add(jtf); jFrame.pack(); jFrame.setVisible(true); } }); Thread.sleep(1000); SwingUtilities.invokeAndWait(new Runnable() { public void run() { Position.Bias[] biasRet = {Position.Bias.Forward}; for (int direction : new int[]{ SwingConstants.EAST, SwingConstants.WEST, // the following constants still will lead to "BadLocationException: Length must be positive" SwingConstants.SOUTH, SwingConstants.NORTH, }) { for (int position : new int[]{-100, Integer.MIN_VALUE}) { for (Position.Bias bias : new Position.Bias[]{Position.Bias.Backward, Position.Bias.Forward}) { try { nf.getNextVisualPositionFrom(jtf, position, bias, direction, biasRet); throw new RuntimeException("BadLocationException was not thrown: position = " + position + ", bias = " + bias + ", direction = " + direction); } catch (BadLocationException e) { // Ok } } } } jFrame.dispose(); } }); }
protected NavigationFilter getNavigationFilter() { return null; }
private void setNavigationFilter(final NavigationFilter filter) { textField.setNavigationFilter(filter); }
@Override protected NavigationFilter getNavigationFilter() { return navigationFilter == null ? super.getNavigationFilter() : navigationFilter; }
final void setNavigationFilter(final NavigationFilter filter) { navigationFilter = filter; }