| 1 | |
|---|
| 2 | package spikes; |
|---|
| 3 | |
|---|
| 4 | |
|---|
| 5 | import java.awt.*; |
|---|
| 6 | import java.awt.event.*; |
|---|
| 7 | import java.util.HashMap; |
|---|
| 8 | import java.util.logging.Level; |
|---|
| 9 | import java.util.logging.Logger; |
|---|
| 10 | |
|---|
| 11 | import javax.swing.*; |
|---|
| 12 | import javax.swing.text.*; |
|---|
| 13 | import javax.swing.event.*; |
|---|
| 14 | import javax.swing.undo.*; |
|---|
| 15 | |
|---|
| 16 | public class TextComponentDemo extends JFrame { |
|---|
| 17 | JTextPane textPane; |
|---|
| 18 | AbstractDocument doc; |
|---|
| 19 | static final int MAX_CHARACTERS = 300; |
|---|
| 20 | JTextArea changeLog; |
|---|
| 21 | String newline = "\n"; |
|---|
| 22 | HashMap<Object, Action> actions; |
|---|
| 23 | |
|---|
| 24 | //undo helpers |
|---|
| 25 | protected UndoAction undoAction; |
|---|
| 26 | protected RedoAction redoAction; |
|---|
| 27 | protected UndoManager undo = new UndoManager(); |
|---|
| 28 | private ClearAction saveAction = new ClearAction(); |
|---|
| 29 | |
|---|
| 30 | public TextComponentDemo() { |
|---|
| 31 | super("TextComponentDemo"); |
|---|
| 32 | |
|---|
| 33 | //Create the text pane and configure it. |
|---|
| 34 | textPane = new JTextPane(); |
|---|
| 35 | textPane.setCaretPosition(0); |
|---|
| 36 | textPane.setMargin(new Insets(5,5,5,5)); |
|---|
| 37 | StyledDocument styledDoc = textPane.getStyledDocument(); |
|---|
| 38 | if (styledDoc instanceof AbstractDocument) { |
|---|
| 39 | doc = (AbstractDocument)styledDoc; |
|---|
| 40 | doc.setDocumentFilter(new DocumentSizeFilter(MAX_CHARACTERS)); |
|---|
| 41 | } else { |
|---|
| 42 | System.err.println("Text pane's document isn't an AbstractDocument!"); |
|---|
| 43 | System.exit(-1); |
|---|
| 44 | } |
|---|
| 45 | JScrollPane scrollPane = new JScrollPane(textPane); |
|---|
| 46 | scrollPane.setPreferredSize(new Dimension(200, 200)); |
|---|
| 47 | |
|---|
| 48 | //Create the text area for the status log and configure it. |
|---|
| 49 | changeLog = new JTextArea(5, 30); |
|---|
| 50 | changeLog.setEditable(false); |
|---|
| 51 | JScrollPane scrollPaneForLog = new JScrollPane(changeLog); |
|---|
| 52 | |
|---|
| 53 | //Create a split pane for the change log and the text area. |
|---|
| 54 | JSplitPane splitPane = new JSplitPane( |
|---|
| 55 | JSplitPane.VERTICAL_SPLIT, |
|---|
| 56 | scrollPane, scrollPaneForLog); |
|---|
| 57 | splitPane.setOneTouchExpandable(true); |
|---|
| 58 | |
|---|
| 59 | //Create the status area. |
|---|
| 60 | JPanel statusPane = new JPanel(new GridLayout(1, 1)); |
|---|
| 61 | CaretListenerLabel caretListenerLabel = |
|---|
| 62 | new CaretListenerLabel("Caret Status"); |
|---|
| 63 | statusPane.add(caretListenerLabel); |
|---|
| 64 | |
|---|
| 65 | //Add the components. |
|---|
| 66 | getContentPane().add(splitPane, BorderLayout.CENTER); |
|---|
| 67 | getContentPane().add(statusPane, BorderLayout.PAGE_END); |
|---|
| 68 | |
|---|
| 69 | //Set up the menu bar. |
|---|
| 70 | actions=createActionTable(textPane); |
|---|
| 71 | JMenu editMenu = createEditMenu(); |
|---|
| 72 | JMenu styleMenu = createStyleMenu(); |
|---|
| 73 | JMenuBar mb = new JMenuBar(); |
|---|
| 74 | mb.add(editMenu); |
|---|
| 75 | mb.add(styleMenu); |
|---|
| 76 | setJMenuBar(mb); |
|---|
| 77 | |
|---|
| 78 | //Add some key bindings. |
|---|
| 79 | addBindings(); |
|---|
| 80 | |
|---|
| 81 | //Put the initial text into the text pane. |
|---|
| 82 | initDocument(); |
|---|
| 83 | textPane.setCaretPosition(0); |
|---|
| 84 | |
|---|
| 85 | //Start watching for undoable edits and caret changes. |
|---|
| 86 | doc.addUndoableEditListener(new MyUndoableEditListener()); |
|---|
| 87 | textPane.addCaretListener(caretListenerLabel); |
|---|
| 88 | doc.addDocumentListener(new MyDocumentListener()); |
|---|
| 89 | } |
|---|
| 90 | |
|---|
| 91 | //This listens for and reports caret movements. |
|---|
| 92 | protected class CaretListenerLabel extends JLabel |
|---|
| 93 | implements CaretListener { |
|---|
| 94 | public CaretListenerLabel(String label) { |
|---|
| 95 | super(label); |
|---|
| 96 | } |
|---|
| 97 | |
|---|
| 98 | //Might not be invoked from the event dispatch thread. |
|---|
| 99 | public void caretUpdate(CaretEvent e) { |
|---|
| 100 | displaySelectionInfo(e.getDot(), e.getMark()); |
|---|
| 101 | } |
|---|
| 102 | |
|---|
| 103 | //This method can be invoked from any thread. It |
|---|
| 104 | //invokes the setText and modelToView methods, which |
|---|
| 105 | //must run on the event dispatch thread. We use |
|---|
| 106 | //invokeLater to schedule the code for execution |
|---|
| 107 | //on the event dispatch thread. |
|---|
| 108 | protected void displaySelectionInfo(final int dot, |
|---|
| 109 | final int mark) { |
|---|
| 110 | SwingUtilities.invokeLater(new Runnable() { |
|---|
| 111 | public void run() { |
|---|
| 112 | if (dot == mark) { // no selection |
|---|
| 113 | try { |
|---|
| 114 | Rectangle caretCoords = textPane.modelToView(dot); |
|---|
| 115 | //Convert it to view coordinates. |
|---|
| 116 | setText("caret: text position: " + dot |
|---|
| 117 | + ", view location = [" |
|---|
| 118 | + caretCoords.x + ", " |
|---|
| 119 | + caretCoords.y + "]" |
|---|
| 120 | + newline); |
|---|
| 121 | } catch (BadLocationException ble) { |
|---|
| 122 | setText("caret: text position: " + dot + newline); |
|---|
| 123 | } |
|---|
| 124 | } else if (dot < mark) { |
|---|
| 125 | setText("selection from: " + dot |
|---|
| 126 | + " to " + mark + newline); |
|---|
| 127 | } else { |
|---|
| 128 | setText("selection from: " + mark |
|---|
| 129 | + " to " + dot + newline); |
|---|
| 130 | } |
|---|
| 131 | } |
|---|
| 132 | }); |
|---|
| 133 | } |
|---|
| 134 | } |
|---|
| 135 | |
|---|
| 136 | //This one listens for edits that can be undone. |
|---|
| 137 | protected class MyUndoableEditListener |
|---|
| 138 | implements UndoableEditListener { |
|---|
| 139 | public void undoableEditHappened(UndoableEditEvent e) { |
|---|
| 140 | //Remember the edit and update the menus. |
|---|
| 141 | undo.addEdit(e.getEdit()); |
|---|
| 142 | undoAction.updateUndoState(); |
|---|
| 143 | redoAction.updateRedoState(); |
|---|
| 144 | } |
|---|
| 145 | } |
|---|
| 146 | |
|---|
| 147 | //And this one listens for any changes to the document. |
|---|
| 148 | protected class MyDocumentListener |
|---|
| 149 | implements DocumentListener { |
|---|
| 150 | public void insertUpdate(DocumentEvent e) { |
|---|
| 151 | displayEditInfo(e); |
|---|
| 152 | } |
|---|
| 153 | public void removeUpdate(DocumentEvent e) { |
|---|
| 154 | displayEditInfo(e); |
|---|
| 155 | } |
|---|
| 156 | public void changedUpdate(DocumentEvent e) { |
|---|
| 157 | displayEditInfo(e); |
|---|
| 158 | } |
|---|
| 159 | private void displayEditInfo(DocumentEvent e) { |
|---|
| 160 | Document document = e.getDocument(); |
|---|
| 161 | int changeLength = e.getLength(); |
|---|
| 162 | changeLog.append(e.getType().toString() + ": " + |
|---|
| 163 | changeLength + " character" + |
|---|
| 164 | ((changeLength == 1) ? ". " : "s. ") + |
|---|
| 165 | " Text length = " + document.getLength() + |
|---|
| 166 | "." + newline); |
|---|
| 167 | } |
|---|
| 168 | } |
|---|
| 169 | |
|---|
| 170 | //Add a couple of emacs key bindings for navigation. |
|---|
| 171 | protected void addBindings() { |
|---|
| 172 | InputMap inputMap = textPane.getInputMap(); |
|---|
| 173 | ActionMap actionMap = textPane.getActionMap(); |
|---|
| 174 | //Ctrl-b to go backward one character |
|---|
| 175 | KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_B, Event.CTRL_MASK); |
|---|
| 176 | //inputMap.put(key, DefaultEditorKit.backwardAction); |
|---|
| 177 | inputMap.put(key, "myAction"); |
|---|
| 178 | actionMap.put("myAction", saveAction); |
|---|
| 179 | |
|---|
| 180 | //Ctrl-f to go forward one character |
|---|
| 181 | key = KeyStroke.getKeyStroke(KeyEvent.VK_F, Event.CTRL_MASK); |
|---|
| 182 | inputMap.put(key, DefaultEditorKit.forwardAction); |
|---|
| 183 | |
|---|
| 184 | //Ctrl-p to go up one line |
|---|
| 185 | key = KeyStroke.getKeyStroke(KeyEvent.VK_P, Event.CTRL_MASK); |
|---|
| 186 | inputMap.put(key, DefaultEditorKit.upAction); |
|---|
| 187 | |
|---|
| 188 | //Ctrl-n to go down one line |
|---|
| 189 | key = KeyStroke.getKeyStroke(KeyEvent.VK_N, Event.CTRL_MASK); |
|---|
| 190 | inputMap.put(key, DefaultEditorKit.downAction); |
|---|
| 191 | } |
|---|
| 192 | |
|---|
| 193 | //Create the edit menu. |
|---|
| 194 | protected JMenu createEditMenu() { |
|---|
| 195 | JMenu menu = new JMenu("Edit"); |
|---|
| 196 | |
|---|
| 197 | //Undo and redo are actions of our own creation. |
|---|
| 198 | undoAction = new UndoAction(); |
|---|
| 199 | menu.add(undoAction); |
|---|
| 200 | |
|---|
| 201 | redoAction = new RedoAction(); |
|---|
| 202 | menu.add(redoAction); |
|---|
| 203 | |
|---|
| 204 | menu.addSeparator(); |
|---|
| 205 | |
|---|
| 206 | //These actions come from the default editor kit. |
|---|
| 207 | //Get the ones we want and stick them in the menu. |
|---|
| 208 | menu.add(getActionByName(DefaultEditorKit.cutAction)); |
|---|
| 209 | menu.add(getActionByName(DefaultEditorKit.copyAction)); |
|---|
| 210 | menu.add(getActionByName(DefaultEditorKit.pasteAction)); |
|---|
| 211 | |
|---|
| 212 | menu.addSeparator(); |
|---|
| 213 | |
|---|
| 214 | menu.add(getActionByName(DefaultEditorKit.selectAllAction)); |
|---|
| 215 | return menu; |
|---|
| 216 | } |
|---|
| 217 | |
|---|
| 218 | //Create the style menu. |
|---|
| 219 | protected JMenu createStyleMenu() { |
|---|
| 220 | JMenu menu = new JMenu("Style"); |
|---|
| 221 | |
|---|
| 222 | Action action = new StyledEditorKit.BoldAction(); |
|---|
| 223 | action.putValue(Action.NAME, "Bold"); |
|---|
| 224 | menu.add(action); |
|---|
| 225 | |
|---|
| 226 | action = new StyledEditorKit.ItalicAction(); |
|---|
| 227 | action.putValue(Action.NAME, "Italic"); |
|---|
| 228 | menu.add(action); |
|---|
| 229 | |
|---|
| 230 | action = new StyledEditorKit.UnderlineAction(); |
|---|
| 231 | action.putValue(Action.NAME, "Underline"); |
|---|
| 232 | menu.add(action); |
|---|
| 233 | |
|---|
| 234 | menu.addSeparator(); |
|---|
| 235 | |
|---|
| 236 | menu.add(new StyledEditorKit.FontSizeAction("12", 12)); |
|---|
| 237 | menu.add(new StyledEditorKit.FontSizeAction("14", 14)); |
|---|
| 238 | menu.add(new StyledEditorKit.FontSizeAction("18", 18)); |
|---|
| 239 | |
|---|
| 240 | menu.addSeparator(); |
|---|
| 241 | |
|---|
| 242 | menu.add(new StyledEditorKit.FontFamilyAction("Serif", |
|---|
| 243 | "Serif")); |
|---|
| 244 | menu.add(new StyledEditorKit.FontFamilyAction("SansSerif", |
|---|
| 245 | "SansSerif")); |
|---|
| 246 | |
|---|
| 247 | menu.addSeparator(); |
|---|
| 248 | |
|---|
| 249 | menu.add(new StyledEditorKit.ForegroundAction("Red", |
|---|
| 250 | Color.red)); |
|---|
| 251 | menu.add(new StyledEditorKit.ForegroundAction("Green", |
|---|
| 252 | Color.green)); |
|---|
| 253 | menu.add(new StyledEditorKit.ForegroundAction("Blue", |
|---|
| 254 | Color.blue)); |
|---|
| 255 | menu.add(new StyledEditorKit.ForegroundAction("Black", |
|---|
| 256 | Color.black)); |
|---|
| 257 | |
|---|
| 258 | return menu; |
|---|
| 259 | } |
|---|
| 260 | |
|---|
| 261 | protected void initDocument() { |
|---|
| 262 | String initString[] = |
|---|
| 263 | { "Use the mouse to place the caret.", |
|---|
| 264 | "Use the edit menu to cut, copy, paste, and select text.", |
|---|
| 265 | "Also to undo and redo changes.", |
|---|
| 266 | "Use the style menu to change the style of the text.", |
|---|
| 267 | "Use the arrow keys on the keyboard or these emacs key bindings to move the caret:", |
|---|
| 268 | "Ctrl-f, Ctrl-b, Ctrl-n, Ctrl-p." }; |
|---|
| 269 | |
|---|
| 270 | SimpleAttributeSet[] attrs = initAttributes(initString.length); |
|---|
| 271 | |
|---|
| 272 | try { |
|---|
| 273 | for (int i = 0; i < initString.length; i ++) { |
|---|
| 274 | doc.insertString(doc.getLength(), initString[i] + newline, |
|---|
| 275 | attrs[i]); |
|---|
| 276 | } |
|---|
| 277 | } catch (BadLocationException ble) { |
|---|
| 278 | System.err.println("Couldn't insert initial text."); |
|---|
| 279 | } |
|---|
| 280 | } |
|---|
| 281 | |
|---|
| 282 | protected SimpleAttributeSet[] initAttributes(int length) { |
|---|
| 283 | //Hard-code some attributes. |
|---|
| 284 | SimpleAttributeSet[] attrs = new SimpleAttributeSet[length]; |
|---|
| 285 | |
|---|
| 286 | attrs[0] = new SimpleAttributeSet(); |
|---|
| 287 | StyleConstants.setFontFamily(attrs[0], "SansSerif"); |
|---|
| 288 | StyleConstants.setFontSize(attrs[0], 16); |
|---|
| 289 | |
|---|
| 290 | attrs[1] = new SimpleAttributeSet(attrs[0]); |
|---|
| 291 | StyleConstants.setBold(attrs[1], true); |
|---|
| 292 | |
|---|
| 293 | attrs[2] = new SimpleAttributeSet(attrs[0]); |
|---|
| 294 | StyleConstants.setItalic(attrs[2], true); |
|---|
| 295 | |
|---|
| 296 | attrs[3] = new SimpleAttributeSet(attrs[0]); |
|---|
| 297 | StyleConstants.setFontSize(attrs[3], 20); |
|---|
| 298 | |
|---|
| 299 | attrs[4] = new SimpleAttributeSet(attrs[0]); |
|---|
| 300 | StyleConstants.setFontSize(attrs[4], 12); |
|---|
| 301 | |
|---|
| 302 | attrs[5] = new SimpleAttributeSet(attrs[0]); |
|---|
| 303 | StyleConstants.setForeground(attrs[5], Color.red); |
|---|
| 304 | |
|---|
| 305 | return attrs; |
|---|
| 306 | } |
|---|
| 307 | |
|---|
| 308 | //The following two methods allow us to find an |
|---|
| 309 | //action provided by the editor kit by its name. |
|---|
| 310 | private HashMap<Object, Action> createActionTable(JTextComponent textComponent) { |
|---|
| 311 | HashMap<Object, Action> actions = new HashMap<Object, Action>(); |
|---|
| 312 | Action[] actionsArray = textComponent.getActions(); |
|---|
| 313 | for (int i = 0; i < actionsArray.length; i++) { |
|---|
| 314 | Action a = actionsArray[i]; |
|---|
| 315 | actions.put(a.getValue(Action.NAME), a); |
|---|
| 316 | } |
|---|
| 317 | return actions; |
|---|
| 318 | } |
|---|
| 319 | |
|---|
| 320 | private Action getActionByName(String name) { |
|---|
| 321 | return actions.get(name); |
|---|
| 322 | } |
|---|
| 323 | |
|---|
| 324 | class ClearAction extends AbstractAction |
|---|
| 325 | { |
|---|
| 326 | public void actionPerformed(ActionEvent evt) |
|---|
| 327 | { |
|---|
| 328 | JTextPane src = (JTextPane) evt.getSource(); |
|---|
| 329 | StyledDocument doc = src.getStyledDocument(); |
|---|
| 330 | int length = doc.getLength(); |
|---|
| 331 | try { |
|---|
| 332 | doc.remove(0,length); |
|---|
| 333 | } catch (BadLocationException ex) { |
|---|
| 334 | Logger.getLogger(TextComponentDemo.class.getName()).log(Level.SEVERE, null, ex); |
|---|
| 335 | } |
|---|
| 336 | } |
|---|
| 337 | } |
|---|
| 338 | |
|---|
| 339 | class UndoAction extends AbstractAction { |
|---|
| 340 | public UndoAction() { |
|---|
| 341 | super("Undo"); |
|---|
| 342 | setEnabled(false); |
|---|
| 343 | } |
|---|
| 344 | |
|---|
| 345 | public void actionPerformed(ActionEvent e) { |
|---|
| 346 | try { |
|---|
| 347 | undo.undo(); |
|---|
| 348 | } catch (CannotUndoException ex) { |
|---|
| 349 | System.out.println("Unable to undo: " + ex); |
|---|
| 350 | ex.printStackTrace(); |
|---|
| 351 | } |
|---|
| 352 | updateUndoState(); |
|---|
| 353 | redoAction.updateRedoState(); |
|---|
| 354 | } |
|---|
| 355 | |
|---|
| 356 | protected void updateUndoState() { |
|---|
| 357 | if (undo.canUndo()) { |
|---|
| 358 | setEnabled(true); |
|---|
| 359 | putValue(Action.NAME, undo.getUndoPresentationName()); |
|---|
| 360 | } else { |
|---|
| 361 | setEnabled(false); |
|---|
| 362 | putValue(Action.NAME, "Undo"); |
|---|
| 363 | } |
|---|
| 364 | } |
|---|
| 365 | } |
|---|
| 366 | |
|---|
| 367 | class RedoAction extends AbstractAction { |
|---|
| 368 | public RedoAction() { |
|---|
| 369 | super("Redo"); |
|---|
| 370 | setEnabled(false); |
|---|
| 371 | } |
|---|
| 372 | |
|---|
| 373 | public void actionPerformed(ActionEvent e) { |
|---|
| 374 | try { |
|---|
| 375 | undo.redo(); |
|---|
| 376 | } catch (CannotRedoException ex) { |
|---|
| 377 | System.out.println("Unable to redo: " + ex); |
|---|
| 378 | ex.printStackTrace(); |
|---|
| 379 | } |
|---|
| 380 | updateRedoState(); |
|---|
| 381 | undoAction.updateUndoState(); |
|---|
| 382 | } |
|---|
| 383 | |
|---|
| 384 | protected void updateRedoState() { |
|---|
| 385 | if (undo.canRedo()) { |
|---|
| 386 | setEnabled(true); |
|---|
| 387 | putValue(Action.NAME, undo.getRedoPresentationName()); |
|---|
| 388 | } else { |
|---|
| 389 | setEnabled(false); |
|---|
| 390 | putValue(Action.NAME, "Redo"); |
|---|
| 391 | } |
|---|
| 392 | } |
|---|
| 393 | } |
|---|
| 394 | |
|---|
| 395 | /** |
|---|
| 396 | * Create the GUI and show it. For thread safety, |
|---|
| 397 | * this method should be invoked from the |
|---|
| 398 | * event dispatch thread. |
|---|
| 399 | */ |
|---|
| 400 | private static void createAndShowGUI() { |
|---|
| 401 | //Create and set up the window. |
|---|
| 402 | final TextComponentDemo frame = new TextComponentDemo(); |
|---|
| 403 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); |
|---|
| 404 | |
|---|
| 405 | //Display the window. |
|---|
| 406 | frame.pack(); |
|---|
| 407 | frame.setVisible(true); |
|---|
| 408 | } |
|---|
| 409 | |
|---|
| 410 | //The standard main method. |
|---|
| 411 | public static void main(String[] args) { |
|---|
| 412 | //Schedule a job for the event dispatch thread: |
|---|
| 413 | //creating and showing this application's GUI. |
|---|
| 414 | SwingUtilities.invokeLater(new Runnable() { |
|---|
| 415 | public void run() { |
|---|
| 416 | //Turn off metal's use of bold fonts |
|---|
| 417 | UIManager.put("swing.boldMetal", Boolean.FALSE); |
|---|
| 418 | createAndShowGUI(); |
|---|
| 419 | } |
|---|
| 420 | }); |
|---|
| 421 | } |
|---|
| 422 | } |
|---|