import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.Hashtable; import java.net.*; import javax.swing.*; import javax.swing.text.*; import javax.swing.event.*; import javax.swing.undo.*; import javax.swing.border.*; public class TextComponentDemo extends JFrame { JTextPane textPane; JTextArea changeLog; String newline; static final int MAX_CHARACTERS = 200; Hashtable actions; //undo helpers protected UndoAction undoAction; protected RedoAction redoAction; protected UndoManager undo = new UndoManager(); public TextComponentDemo () { //Some initial setup super("TextComponentDemo"); newline = System.getProperty("line.separator"); //Create the document for the text area LimitedStyledDocument lpd = new LimitedStyledDocument(MAX_CHARACTERS); lpd.addDocumentListener(new MyDocumentListener()); //Create the text area and configure it textPane = new JTextPane(); textPane.setPreferredSize(new Dimension(200, 200)); textPane.setDocument(lpd); textPane.setCaretPosition(0); textPane.setMargin(new Insets(5,5,5,5)); JScrollPane scrollPane = new JScrollPane(textPane); //Create the text area for the status log and configure it changeLog = new JTextArea(5, 30); changeLog.setEditable(false); JScrollPane scrollPaneForLog = new JScrollPane(changeLog); //Create a split pane for the change log and the text area JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, scrollPane, scrollPaneForLog); splitPane.setOneTouchExpandable(true); //Create the status area JPanel statusPane = new JPanel(new GridLayout(1, 1)); CaretListenerLabel caretListenerLabel = new CaretListenerLabel("Caret Status"); statusPane.add(caretListenerLabel); //Add the components to the frame BorderLayout borderLayout = new BorderLayout(); JPanel contentPane = new JPanel(); contentPane.setLayout(borderLayout); contentPane.add(splitPane, BorderLayout.CENTER); contentPane.add(statusPane, BorderLayout.SOUTH); setContentPane(contentPane); //Set up the menu bar actions = createActionTable(textPane); JMenu editMenu = createEditMenu(); JMenu styleMenu = createStyleMenu(); JMenuBar mb = new JMenuBar(); mb.add(editMenu); mb.add(styleMenu); setJMenuBar(mb); //Add some key bindings to the keymap addKeymapBindings(); //Load the text area with text try { URL url = new URL("http://java.sun.com/docs/books/tutorial/ui/" + "swing/example-swing/TextComponentDemo.html"); textPane.setPage(url); } catch (Exception e ) { System.err.println("Can't open instructions"); } //Start watching for undoable edits and caret changes lpd.addUndoableEditListener(new MyUndoableEditListener()); textPane.addCaretListener(caretListenerLabel); } //This listens for and reports caret movements. protected class CaretListenerLabel extends JLabel implements CaretListener { public CaretListenerLabel (String label) { super(label); } public void caretUpdate(CaretEvent e) { //Get the location in the text int dot = e.getDot(); int mark = e.getMark(); if (dot == mark) { // no selection try { Rectangle caretCoords = textPane.modelToView(dot); //Convert it to view coordinates setText("caret: text position: " + dot + ", view location = [" + caretCoords.x + ", " + caretCoords.y + "]" + newline); } catch (BadLocationException ble) { setText("caret: text position: " + dot + newline); } } else if (dot < mark) { setText("selection from: " + dot + " to " + mark + newline); } else { setText("selection from: " + mark + " to " + dot + newline); } } } //This one listens for edits that can be undone. protected class MyUndoableEditListener implements UndoableEditListener { public void undoableEditHappened(UndoableEditEvent e) { //Remember the edit and update the menus undo.addEdit(e.getEdit()); undoAction.update(); redoAction.update(); } } //And this one listens for any changes to the document. protected class MyDocumentListener implements DocumentListener { public void insertUpdate(DocumentEvent e) { update(e); } public void removeUpdate(DocumentEvent e) { update(e); } public void changedUpdate(DocumentEvent e) { AbstractDocument.DefaultDocumentEvent de = (AbstractDocument.DefaultDocumentEvent)e; //Display the type of edit that occurred changeLog.append(de.getPresentationName() + newline); } private void update(DocumentEvent e) { AbstractDocument.DefaultDocumentEvent de = (AbstractDocument.DefaultDocumentEvent)e; //Display the type of edit that occurred and //the resulting text length changeLog.append(de.getPresentationName() + ": text length = " + e.getDocument().getLength() + newline); } } //Add a couple of emacs key bindings to the key map for navigation protected void addKeymapBindings() { //Get the current, default map Keymap keymap = textPane.getKeymap(); //Ctrl-b to go backward one character Action action = getActionByName(DefaultEditorKit.backwardAction); KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_B, Event.CTRL_MASK); keymap.addActionForKeyStroke(key, action); //Ctrl-f to go forward one character action = getActionByName(DefaultEditorKit.forwardAction); key = KeyStroke.getKeyStroke(KeyEvent.VK_F, Event.CTRL_MASK); keymap.addActionForKeyStroke(key, action); //Ctrl-p to go up one line action = getActionByName(DefaultEditorKit.upAction); key = KeyStroke.getKeyStroke(KeyEvent.VK_P, Event.CTRL_MASK); keymap.addActionForKeyStroke(key, action); //Ctrl-n to go down one line action = getActionByName(DefaultEditorKit.downAction); key = KeyStroke.getKeyStroke(KeyEvent.VK_N, Event.CTRL_MASK); keymap.addActionForKeyStroke(key, action); } //Create the edit menu protected JMenu createEditMenu() { JMenu menu = new JMenu("Edit"); //Undo and redo are actions of our own creation undoAction = new UndoAction(); menu.add(undoAction); redoAction = new RedoAction(); menu.add(redoAction); menu.addSeparator(); //These actions come from the default editor kit. //We just get the ones we want and stick them in the menu. Action action = getActionByName(DefaultEditorKit.cutAction); action.putValue(Action.NAME, "Cut"); menu.add(action); action = getActionByName(DefaultEditorKit.copyAction); action.putValue(Action.NAME, "Copy"); menu.add(action); action = getActionByName(DefaultEditorKit.pasteAction); action.putValue(Action.NAME, "Paste"); menu.add(action); menu.addSeparator(); action = getActionByName(DefaultEditorKit.selectAllAction); action.putValue(Action.NAME, "Select All"); menu.add(action); return menu; } //Create the style menu protected JMenu createStyleMenu() { JMenu menu = new JMenu("Style"); Action action = new StyledEditorKit.BoldAction(); action.putValue(Action.NAME, "Bold"); menu.add(action); action = new StyledEditorKit.ItalicAction(); action.putValue(Action.NAME, "Italic"); menu.add(action); action = new StyledEditorKit.UnderlineAction(); action.putValue(Action.NAME, "Underline"); menu.add(action); menu.addSeparator(); action = new StyledEditorKit.FontSizeAction("12", 12); menu.add(action); action = new StyledEditorKit.FontSizeAction("14", 14); menu.add(action); action = new StyledEditorKit.FontSizeAction("18", 18); menu.add(action); menu.addSeparator(); action = new StyledEditorKit.FontFamilyAction("Serif", "Serif"); menu.add(action); action = new StyledEditorKit.FontFamilyAction("SansSerif", "SansSerif"); menu.add(action); menu.addSeparator(); action = new StyledEditorKit.ForegroundAction("Red", Color.red); menu.add(action); action = new StyledEditorKit.ForegroundAction("Green", Color.green); menu.add(action); action = new StyledEditorKit.ForegroundAction("Blue", Color.blue); menu.add(action); action = new StyledEditorKit.ForegroundAction("Black", Color.black); menu.add(action); return menu; } //The following two methods allow us to find an //action provided by the editor kit by its name. private Hashtable createActionTable(JTextComponent textComponent) { Hashtable actions = new Hashtable(); Action[] actionsArray = textComponent.getActions(); for (int i = 0; i < actionsArray.length; i++) { Action a = actionsArray[i]; actions.put(a.getValue(Action.NAME), a); } return actions; } private Action getActionByName(String name) { return (Action)(actions.get(name)); } class UndoAction extends AbstractAction { public UndoAction() { super("Undo"); setEnabled(false); } public void actionPerformed(ActionEvent e) { try { undo.undo(); } catch (CannotUndoException ex) { System.out.println("Unable to undo: " + ex); ex.printStackTrace(); } update(); redoAction.update(); } protected void update() { if (undo.canUndo()) { setEnabled(true); putValue(Action.NAME, undo.getUndoPresentationName()); } else { setEnabled(false); putValue(Action.NAME, "Undo"); } } } class RedoAction extends AbstractAction { public RedoAction() { super("Redo"); setEnabled(false); } public void actionPerformed(ActionEvent e) { try { undo.redo(); } catch (CannotRedoException ex) { System.out.println("Unable to redo: " + ex); ex.printStackTrace(); } update(); undoAction.update(); } protected void update() { if (undo.canRedo()) { setEnabled(true); putValue(Action.NAME, undo.getRedoPresentationName()); } else { setEnabled(false); putValue(Action.NAME, "Redo"); } } } //The standard main method. public static void main(String[] args) { final TextComponentDemo frame = new TextComponentDemo(); WindowListener l = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } public void windowActivated(WindowEvent e) { frame.textPane.requestFocus(); } }; frame.addWindowListener(l); frame.pack(); frame.setVisible(true); } }