import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.text.*; /* * Class to manage protected text within a Document. There are two different * but related functions provided by this class: * * a) protect the text in the Document from being changed or deleted * b) control caret navigation so that the caret is only ever positioned on * text that can be changed. * * Note: this class was designed to be used the the ProtectedTextComponent * class, although it can be used on its own when highlighting of the * protected text is not a requirement. */ public class ProtectedDocument { private AbstractDocument doc; private Map positions = new HashMap(); public ProtectedDocument(JTextComponent component) { doc = (AbstractDocument)component.getDocument(); doc.setDocumentFilter( new ProtectedDocumentFilter() ); component.setNavigationFilter( new ProtectedNavigationFilter(component) ); } /* * Specify a portion of text to be protected * * Note: when protecting and entire line the start offset should be the line * start - 1. This prevents a character from being inserted at the * start of the line. */ public void protect(int start, int end) { try { positions.put(doc.createPosition(start), doc.createPosition(end)); } catch(BadLocationException ble) { System.out.println(ble); } } /* * Class to prevent the removal or changing of text in protected areas * of the Document. */ class ProtectedDocumentFilter extends DocumentFilter { /* * Prevent inserts by the program */ public void insertString(FilterBypass fb, int offset, String str, AttributeSet a) throws BadLocationException { if (isInsertProtected(offset)) { Toolkit.getDefaultToolkit().beep(); } else { super.insertString(fb, offset, str, a); } } /* * Prevent removal and insertions by the GUI components */ public void replace(FilterBypass fb, int offset, int length, String str, AttributeSet a) throws BadLocationException { if (length != 0 && isRemoveProtected(offset, length)) { Toolkit.getDefaultToolkit().beep(); } else if (isInsertProtected(offset)) { Toolkit.getDefaultToolkit().beep(); } else { super.replace(fb, offset, length, str, a); } } /* * Prevent removal of text */ public void remove(DocumentFilter.FilterBypass fb, int offset, int length) throws BadLocationException { if (length == 0) length++; if (isRemoveProtected(offset, length)) Toolkit.getDefaultToolkit().beep(); else super.remove(fb, offset, length); } /* * Check if we are attempting to remove protected text. Don't * remove when: * * a) the starting offset is contained within a protected block * b) the ending offset is contained within a protected block * c) the start and end offsets span a contained block */ private boolean isRemoveProtected(int start, int length) { int end = start + length - 1; for (Map.Entry me: positions.entrySet()) { int positionStart = me.getKey().getOffset(); int positionEnd = me.getValue().getOffset(); if (start >= positionStart && start <= positionEnd) return true; if (end >= positionStart && end <= positionEnd) return true; if (start < positionStart && end > positionEnd) return true; } return false; } /* * Check if we are attempting to insert text in the middle of a * protected block. This should never happen since the navigation * filter should prevent the caret from being positioned here. * However, the program could invoke an insertString(...) method. * * Offset 0, is a special case because the Position object managed by * the Document will never update its start position, so the size of * protected area would continue to grow as text is inserted. */ private boolean isInsertProtected(int start) { for (Map.Entry me: positions.entrySet()) { int positionStart = me.getKey().getOffset(); int positionEnd = me.getValue().getOffset(); if (start == 0 && positionStart == 0) return true; if (start > positionStart && start <= positionEnd) return true; } return false; } } // end ProtectedDocumentFilter /* * This class will control the navigation of the caret. The caret will * skip over protected pieces of text and position itself at the * next unprotected text. */ class ProtectedNavigationFilter extends NavigationFilter implements MouseListener { private JTextComponent component; private boolean isMousePressed = false; private int mouseDot = -1; private int lastDot = -1; public ProtectedNavigationFilter(JTextComponent component) { this.component = component; this.component.addMouseListener( this ); } /* * Override for normal caret movement */ public void setDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias) { // Moving forwards in the Document if (dot > lastDot) { dot = getForwardDot(dot); super.setDot(fb, dot, bias); } else // Moving backwards in the Document { dot = getBackwardDot(dot); super.setDot(fb, dot, bias); } lastDot = dot; } /* * Override for text selection as the caret is moved */ public void moveDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias) { // The mouse dot is used when dragging the mouse to prevent flickering lastDot = isMousePressed ? mouseDot : lastDot; // Moving forwards in the Document if (dot > lastDot) { lastDot = dot; dot = getForwardDot(dot); super.moveDot(fb, dot, bias); } else // Moving backwards in the Document { lastDot = dot; dot = getBackwardDot(dot); super.moveDot(fb, dot, bias); } lastDot = dot; } /* * Attempting to move the caret forward in the Document. Skip forward * when we attempt to position the caret at a protected offset. */ private int getForwardDot(int dot) { for (Map.Entry me: positions.entrySet()) { int positionStart = me.getKey().getOffset(); int positionEnd = me.getValue().getOffset(); if (dot > positionStart && dot <= positionEnd) return positionEnd + 1; } return dot; } /* * Attempting to move the caret forward in the Document. Skip forward * when we attempt to position the caret at a protected offset. */ private int getBackwardDot(int dot) { for (Map.Entry me: positions.entrySet()) { int positionStart = me.getKey().getOffset(); int positionEnd = me.getValue().getOffset(); if (dot <= positionEnd && dot >= positionStart) return positionStart; } return dot; } // Implement the MouseListener public void mousePressed( MouseEvent e ) { // Track the caret position so it is easier to determine whether // we are moving forwards/backwards when dragging the mouse. isMousePressed = true; mouseDot = component.getCaretPosition(); } public void mouseReleased( MouseEvent e ) { isMousePressed = false; } public void mouseEntered( MouseEvent e ) {} public void mouseExited( MouseEvent e ) {} public void mouseClicked( MouseEvent e ) {} } // end ProtectedNavigationFilter } // end ProtectedDocument