package toy.dialog; import java.awt.*; import java.awt.event.*; /** * * @author Brian Tsang * @version 7.0 */ public class ToyTextArea extends TextArea implements KeyListener { private ActionListener actionListener; private int lastEndStringPosition = -1; private boolean netscapeBug; //netscape reports its caret and selection positions as though newlines //were made of "\r\n", but when we call the getText() function, it //translates all "\r\n"s to "\n"s WITHOUT CORRECTING THE POSITIONS!!! //we can test for this problem using the selectAll() method //unfortunately this does not seem to work in the contructor, so we //create a separate test function that EditPanel calls when its //workspace is set private int netscapeFooterLength; public ToyTextArea() { this("", 0, 0, SCROLLBARS_BOTH); } public ToyTextArea(int rows, int columns) { this("", rows, columns, SCROLLBARS_BOTH); } public ToyTextArea(String text) { this(text, 0, 0, SCROLLBARS_BOTH); } public ToyTextArea(String text, int rows, int columns) { this(text, rows, columns, SCROLLBARS_BOTH); } public ToyTextArea(String text, int rows, int columns, int scrollbars) { super(text, rows, columns, scrollbars); netscapeFooterLength = 0; //intercept key events AFTER they are processed by the peer enableEvents(AWTEvent.KEY_EVENT_MASK); addKeyListener(this); } //////////////////////////////////////////////////////////////////////////// // Allow ActionListeners to work public void addActionListener(ActionListener newActionListener) { actionListener = AWTEventMulticaster.add(actionListener, newActionListener); } public void removeActionListener(ActionListener newActionListener) { actionListener = AWTEventMulticaster.remove(actionListener, newActionListener); } //////////////////////////////////////////////////////////////////////////// // unique ToyTextArea functions public void testForNetscapeBug() { //test for netscapeBug super.setText("\n\n\n"); super.requestFocus(); super.selectAll(); netscapeBug = (super.getSelectionEnd() - super.getSelectionStart() != 3); super.select(0, 0); } private int getPeerPosition(int stringPosition) { int peerPosition = stringPosition; String text = super.getText(); for (int ctr = 0; ctr < stringPosition && netscapeBug; ctr++) if (text.charAt(ctr) == '\n') peerPosition++; return peerPosition; } private int getStringPosition(int peerPosition) { int stringPosition = peerPosition; String text = super.getText(); for (int ctr = 0; ctr < stringPosition && netscapeBug; ctr++) if (text.charAt(ctr) == '\n') stringPosition--; return stringPosition; } public String getText(boolean cropFooter) { if (netscapeFooterLength > 0 && cropFooter) { String text = super.getText(); return text.substring(0, text.length() - netscapeFooterLength); } else return super.getText(); } //////////////////////////////////////////////////////////////////////////// // intercept set functions public void setEditable(boolean editable) { super.setEditable(editable); if (actionListener != null) actionListener.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, "repaint()" ) ); } public void setBackground(Color newBackground) { super.setBackground(newBackground); if (actionListener != null) actionListener.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, "repaint()" ) ); } public void setBounds(Rectangle r) { setBounds(r.x, r.y, r.width, r.height); } public void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, width, height); //redo footer String newText = getText(true); netscapeFooterLength = 0; if (netscapeBug && getBounds().width == 0 && getBounds().height == 0) { for (int ctr = 0; ctr < newText.length(); ctr++) if (newText.charAt(ctr) == '\n') netscapeFooterLength++; for (int ctr = 0; ctr < netscapeFooterLength; ctr++) newText += '*'; } super.setText(newText); } public void setText(String newText) { setText(newText, true); } public void setText(String newText, boolean generateEvent) { //check for special characters //replace tabs with four spaces int tabIndex; while ((tabIndex = newText.indexOf('\t')) >= 0) newText = newText.substring(0, tabIndex) + " " + newText.substring(tabIndex + 1); //replace carriageReturn-newline pairs with just a newline int rnIndex; while ((rnIndex = newText.indexOf("\r\n")) >= 0) newText = newText.substring(0, rnIndex) + "\n" + newText.substring(rnIndex + 2); //replace newline-carriageReturn pairs with just a newline int nrIndex; while ((nrIndex = newText.indexOf("\n\r")) >= 0) newText = newText.substring(0, nrIndex) + "\n" + newText.substring(nrIndex + 2); //replace all orphan carriage returns with newlines newText.replace('\r', '\n'); netscapeFooterLength = 0; if (netscapeBug && getBounds().width == 0 && getBounds().height == 0) { for (int ctr = 0; ctr < newText.length(); ctr++) if (newText.charAt(ctr) == '\n') netscapeFooterLength++; for (int ctr = 0; ctr < netscapeFooterLength; ctr++) newText += '*'; } super.setText(newText); if (actionListener != null && generateEvent) actionListener.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, "refresh()" ) ); } public int getCaretPosition() { if (super.getSelectionStart() != super.getSelectionEnd() && getStringPosition(super.getSelectionStart()) == lastEndStringPosition || getStringPosition(super.getSelectionEnd()) == lastEndStringPosition) return lastEndStringPosition; else return getStringPosition(super.getCaretPosition()); } public void setCaretPosition(int position) { super.setCaretPosition(getPeerPosition(position)); if (actionListener != null) actionListener.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, "refresh()" ) ); } public int getSelectionStart() { return getStringPosition(super.getSelectionStart()); } public int getSelectionEnd() { return getStringPosition(super.getSelectionEnd()); } public void select(int start, int end) { //ensure that the user can't highlight the footer if (netscapeFooterLength > 0) { int max = super.getText().length() - netscapeFooterLength; if (end > max) end = max; } //well, netscape does compensate for the bug in their select method //except when start == end if (start == end) { setCaretPosition(start); return; } super.select( Math.min(start, end), Math.max(start, end) ); lastEndStringPosition = end; if (actionListener != null) actionListener.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, "refresh()" ) ); } public void selectAll() { super.selectAll(); //ensure that this command doesn't highlight the footer if (netscapeFooterLength > 0) { int start = getSelectionStart(); int end = getSelectionEnd(); int max = super.getText().length() - netscapeFooterLength; if (end > max) { if (start == end) super.setCaretPosition(getPeerPosition(max)); else super.select(start, max); } } if (actionListener != null) actionListener.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, "refresh()" ) ); } //////////////////////////////////////////////////////////////////////////// // intercept key events public void processKeyEvent(KeyEvent e) { if (e.isConsumed()) return; //check for special characters String text = super.getText(); int selectionStart = getSelectionStart(); int selectionEnd = getSelectionEnd(); //replace tabs with four spaces if (text.indexOf('\t') >= 0) { int tabIndex; while ((tabIndex = text.indexOf('\t')) >= 0) { text = text.substring(0, tabIndex) + " " + text.substring(tabIndex + 1); if (lastEndStringPosition > tabIndex) lastEndStringPosition += 3; if (selectionStart > tabIndex) selectionStart += 3; if (selectionEnd > tabIndex) selectionEnd += 3; } super.setText(text); super.select(selectionStart, selectionEnd); } //replace carriageReturn-newline pairs with just a newline if (text.indexOf("\r\n") >= 0) { int rnIndex; while ((rnIndex = text.indexOf("\r\n")) >= 0) { text = text.substring(0, rnIndex) + "\n" + text.substring(rnIndex + 2); if (lastEndStringPosition > rnIndex) lastEndStringPosition--; if (selectionStart > rnIndex) selectionStart--; if (selectionEnd > rnIndex) selectionEnd--; } super.setText(text); super.select(selectionStart, selectionEnd); } //replace newline-carriageReturn pairs with just a newline if (text.indexOf("\n\r") >= 0) { int nrIndex; while ((nrIndex = text.indexOf("\n\r")) >= 0) { text = text.substring(0, nrIndex) + "\n" + text.substring(nrIndex + 2); if (lastEndStringPosition > nrIndex) lastEndStringPosition--; if (selectionStart > nrIndex) selectionStart--; if (selectionEnd > nrIndex) selectionEnd--; } super.setText(text); super.select(selectionStart, selectionEnd); } //replace all orphan carriage returns with newlines if (text.indexOf('\r') >= 0) { text.replace('\r', '\n'); super.setText(text); super.select(selectionStart, selectionEnd); } //intercept all events involving no modifier or just a shift key, //and only intercept if we're in a minimized state if (getBounds().width == 0 && getBounds().height == 0 && (e.getModifiers() & KeyEvent.ALT_MASK) == 0 && (e.getModifiers() & KeyEvent.CTRL_MASK) == 0 && (e.getModifiers() & KeyEvent.META_MASK) == 0) { //System.out.println("(" + e.getID() + ", " + e.getKeyCode() + ", " + (int)e.getKeyChar() + ")"); //we can only hear the released event for pageUp and pageDown if (e.getID() == KeyEvent.KEY_RELEASED) { switch (e.getKeyCode()) { case KeyEvent.VK_PAGE_UP: if (actionListener != null) { if ((e.getModifiers() & KeyEvent.SHIFT_MASK) != 0) actionListener.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, "pageUpSelect()" ) ); else actionListener.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, "pageUp()" ) ); } break; case KeyEvent.VK_PAGE_DOWN: if (actionListener != null) { if ((e.getModifiers() & KeyEvent.SHIFT_MASK) != 0) actionListener.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, "pageDownSelect()" ) ); else actionListener.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, "pageDown()" ) ); } break; } } //we can hear the key pressed event for non-printable keys if (e.getID() == KeyEvent.KEY_PRESSED) { int position; int charCtr; switch (e.getKeyCode()) { case KeyEvent.VK_RIGHT: if ((e.getModifiers() & KeyEvent.SHIFT_MASK) == 0) setCaretPosition(getCaretPosition() + 1); else { int start = getSelectionStart(); int end = getSelectionEnd(); if (getCaretPosition() == end) select(start, end + 1); else select(end, start + 1); } break; case KeyEvent.VK_LEFT: if ((e.getModifiers() & KeyEvent.SHIFT_MASK) == 0) setCaretPosition(getCaretPosition() - 1); else { int start = getSelectionStart(); int end = getSelectionEnd(); if (getCaretPosition() == end) select(start, end - 1); else select(end, start - 1); } break; case KeyEvent.VK_UP: text = getText(true); position = 0; charCtr = getCaretPosition(); while (charCtr > 0 && text.charAt(charCtr - 1) != '\n') { charCtr--; position++; } if (charCtr > 0) { charCtr--; while (charCtr > 0 && text.charAt(charCtr - 1) != '\n') charCtr--; while (text.charAt(charCtr) != '\n' && position > 0) { charCtr++; position--; } } else charCtr = getCaretPosition(); if ((e.getModifiers() & KeyEvent.SHIFT_MASK) == 0) setCaretPosition(charCtr); else { int start = getSelectionStart(); int end = getSelectionEnd(); if (getCaretPosition() == end) select(start, charCtr); else select(end, charCtr); } break; case KeyEvent.VK_DOWN: text = getText(true); position = 0; charCtr = getCaretPosition(); while (charCtr > 0 && text.charAt(charCtr - 1) != '\n') { charCtr--; position++; } while (charCtr < text.length() && text.charAt(charCtr) != '\n') charCtr++; if (charCtr < text.length()) { charCtr++; while (charCtr < text.length() && text.charAt(charCtr) != '\n' && position > 0) { charCtr++; position--; } } else charCtr = getCaretPosition(); if ((e.getModifiers() & KeyEvent.SHIFT_MASK) == 0) setCaretPosition(charCtr); else { int start = getSelectionStart(); int end = getSelectionEnd(); if (getCaretPosition() == end) select(start, charCtr); else select(end, charCtr); } break; case KeyEvent.VK_DELETE: if (isEditable()) { text = getText(true); int start = getSelectionStart(); int end = getSelectionEnd(); if (start == end) { setText( text.substring(0, start) + text.substring(start + 1), false ); setCaretPosition(start); } else { setText( text.substring(0, start) + text.substring(end), false ); setCaretPosition(start); } } break; case KeyEvent.VK_HOME: text = getText(true); charCtr = getCaretPosition(); while (charCtr > 0 && text.charAt(charCtr - 1) != '\n') charCtr--; if ((e.getModifiers() & KeyEvent.SHIFT_MASK) == 0) setCaretPosition(charCtr); else { int start = getSelectionStart(); int end = getSelectionEnd(); if (getCaretPosition() == end) select(start, charCtr); else select(end, charCtr); } break; case KeyEvent.VK_END: text = getText(true); charCtr = getCaretPosition(); while (charCtr < text.length() && text.charAt(charCtr) != '\n') charCtr++; if ((e.getModifiers() & KeyEvent.SHIFT_MASK) == 0) setCaretPosition(charCtr); else { int start = getSelectionStart(); int end = getSelectionEnd(); if (getCaretPosition() == end) select(start, charCtr); else select(end, charCtr); } break; } } //127 is the delete character we took care of it in the previous block if (e.getID() == KeyEvent.KEY_TYPED && !(e.getKeyChar() == KeyEvent.CHAR_UNDEFINED || e.getKeyChar() == 0) && isEditable() && e.getKeyChar() != 127) { text = getText(true); String newText = String.valueOf(e.getKeyChar()); int start = getSelectionStart(); int end = getSelectionEnd(); if (e.getKeyChar() != 8) { if (e.getKeyChar() == '\r') newText = "\n"; if (e.getKeyCode() == '\t') newText = " "; //a beep or escape cahracter if (e.getKeyChar() == '\b' || e.getKeyChar() == 27) newText = ""; if (start == end) { setText( text.substring(0, start) + newText + text.substring(start), false ); setCaretPosition(start + newText.length()); } else { setText( text.substring(0, start) + newText + text.substring(end), false ); setCaretPosition(start + newText.length()); } } else { //backspace if (start == end && start > 0) { setText( text.substring(0, start - 1) + text.substring(start), false ); setCaretPosition(start - 1); } else { setText( text.substring(0, start) + text.substring(end), false ); setCaretPosition(start); } } } //and consume it e.consume(); } //pass it on to the key listeners super.processKeyEvent(e); } //////////////////////////////////////////////////////////////////////////// // intercept key events public void keyTyped(KeyEvent e) { } public void keyPressed(KeyEvent e) { } public void keyReleased(KeyEvent e) { if ((e.getModifiers() & KeyEvent.ALT_MASK) != 0 || (e.getModifiers() & KeyEvent.CTRL_MASK) != 0 || (e.getModifiers() & KeyEvent.META_MASK) != 0) { if (e.isConsumed()) return; //check for special characters String text = super.getText(); int selectionStart = getSelectionStart(); int selectionEnd = getSelectionEnd(); //replace tabs with four spaces if (text.indexOf('\t') >= 0) { int tabIndex; while ((tabIndex = text.indexOf('\t')) >= 0) { text = text.substring(0, tabIndex) + " " + text.substring(tabIndex + 1); if (lastEndStringPosition > tabIndex) lastEndStringPosition += 3; if (selectionStart > tabIndex) selectionStart += 3; if (selectionEnd > tabIndex) selectionEnd += 3; } super.setText(text); super.select(selectionStart, selectionEnd); } //replace carriageReturn-newline pairs with just a newline if (text.indexOf("\r\n") >= 0) { int rnIndex; while ((rnIndex = text.indexOf("\r\n")) >= 0) { text = text.substring(0, rnIndex) + "\n" + text.substring(rnIndex + 2); if (lastEndStringPosition > rnIndex) lastEndStringPosition--; if (selectionStart > rnIndex) selectionStart--; if (selectionEnd > rnIndex) selectionEnd--; } super.setText(text); super.select(selectionStart, selectionEnd); } //replace newline-carriageReturn pairs with just a newline if (text.indexOf("\n\r") >= 0) { int nrIndex; while ((nrIndex = text.indexOf("\n\r")) >= 0) { text = text.substring(0, nrIndex) + "\n" + text.substring(nrIndex + 2); if (lastEndStringPosition > nrIndex) lastEndStringPosition--; if (selectionStart > nrIndex) selectionStart--; if (selectionEnd > nrIndex) selectionEnd--; } super.setText(text); super.select(selectionStart, selectionEnd); } //replace all orphan carriage returns with newlines if (text.indexOf('\r') >= 0) { text.replace('\r', '\n'); super.setText(text); super.select(selectionStart, selectionEnd); } if (actionListener != null) actionListener.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, "refresh()" ) ); } } }