001/* Top-level window containing a simple text editor or viewer. 002 003 Copyright (c) 1998-2016 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.actor.gui; 029 030import java.awt.BorderLayout; 031import java.awt.Color; 032import java.awt.Dimension; 033import java.awt.Graphics; 034import java.awt.Graphics2D; 035import java.awt.Point; 036import java.awt.Rectangle; 037import java.awt.RenderingHints; 038import java.awt.Toolkit; 039import java.awt.event.ActionEvent; 040import java.awt.event.KeyEvent; 041import java.awt.image.BufferedImage; 042import java.awt.print.PageFormat; 043import java.awt.print.Printable; 044import java.awt.print.PrinterException; 045import java.io.File; 046import java.io.FileOutputStream; 047import java.io.IOException; 048import java.io.OutputStream; 049import java.util.LinkedList; 050import java.util.Locale; 051 052import javax.imageio.ImageIO; 053import javax.swing.AbstractAction; 054import javax.swing.Action; 055import javax.swing.JFileChooser; 056import javax.swing.JMenu; 057import javax.swing.JMenuItem; 058import javax.swing.JOptionPane; 059import javax.swing.JScrollPane; 060import javax.swing.JTextArea; 061import javax.swing.KeyStroke; 062import javax.swing.event.DocumentEvent; 063import javax.swing.event.DocumentListener; 064import javax.swing.text.BadLocationException; 065import javax.swing.text.DefaultHighlighter; 066import javax.swing.text.Document; 067import javax.swing.text.Highlighter; 068import javax.swing.text.Highlighter.Highlight; 069import javax.swing.undo.CannotRedoException; 070import javax.swing.undo.CannotUndoException; 071 072import diva.gui.GUIUtilities; 073import ptolemy.actor.injection.PortablePlaceable; 074import ptolemy.gui.ComponentDialog; 075import ptolemy.gui.ExtensionFilenameFilter; 076import ptolemy.gui.ImageExportable; 077import ptolemy.gui.JFileChooserBugFix; 078import ptolemy.gui.Query; 079import ptolemy.gui.QueryListener; 080import ptolemy.gui.UndoListener; 081import ptolemy.util.MessageHandler; 082import ptolemy.util.StringUtilities; 083 084/////////////////////////////////////////////////////////////////// 085//// TextEditor 086 087/** 088 089 A top-level window containing a simple text editor or viewer. 090 You can access the public member text to set the text, get the text, 091 or set the number of rows or columns. 092 After creating this, it is necessary to call show() for it to appear. 093 094 @author Edward A. Lee, contributors: Christopher Brooks, Ben Leinfelder 095 @version $Id$ 096 @since Ptolemy II 1.0 097 @Pt.ProposedRating Yellow (eal) 098 @Pt.AcceptedRating Red (eal) 099 */ 100@SuppressWarnings("serial") 101public class TextEditor extends TableauFrame 102 implements DocumentListener, ImageExportable, Printable, QueryListener { 103 104 /** Construct an empty text editor with no name. 105 * After constructing this, it is necessary 106 * to call setVisible(true) to make the frame appear. 107 */ 108 public TextEditor() { 109 this("Unnamed"); 110 } 111 112 /** Construct an empty text editor with the specified title. 113 * After constructing this, it is necessary 114 * to call setVisible(true) to make the frame appear. 115 * @param title The title to put in the title bar. 116 */ 117 public TextEditor(String title) { 118 this(title, null); 119 } 120 121 /** Construct an empty text editor with the specified title and 122 * document. After constructing this, it is necessary 123 * to call setVisible(true) to make the frame appear. 124 * @param title The title to put in the title bar. 125 * @param document The document containing text, or null if none. 126 */ 127 public TextEditor(String title, Document document) { 128 this(title, document, (Placeable) null); 129 } 130 131 /** Construct an empty text editor with the specified title and 132 * document and associated placeable. After constructing this, 133 * it is necessary to call setVisible(true) to make the frame 134 * appear. 135 * @param title The title to put in the title bar. 136 * @param document The document containing text, or null if none. 137 * @param placeable The associated placeable. 138 */ 139 public TextEditor(String title, Document document, Placeable placeable) { 140 // NOTE: Create with no status bar, since we have no use for it now. 141 super(null, null, placeable); 142 _init(title, document); 143 } 144 145 /** Construct an empty text editor with the specified title and 146 * document and associated poratalbeplaceable. After constructing this, 147 * it is necessary to call setVisible(true) to make the frame 148 * appear. 149 * @param title The title to put in the title bar. 150 * @param document The document containing text, or null if none. 151 * @param portablePlaceable The associated PortablePlaceable. 152 */ 153 public TextEditor(String title, Document document, 154 PortablePlaceable portablePlaceable) { 155 // NOTE: Create with no status bar, since we have no use for it now. 156 super(null, null, portablePlaceable); 157 _init(title, document); 158 } 159 160 /////////////////////////////////////////////////////////////////// 161 //// public variables //// 162 163 /** The text area. */ 164 public JTextArea text; 165 166 /////////////////////////////////////////////////////////////////// 167 //// public methods //// 168 169 /** Allow subclasses to adjust the file menu after packing. 170 * This has to be called after pack(). 171 * This base class does nothing. 172 */ 173 public void adjustFileMenu() { 174 } 175 176 /** React to a change in the find-and-replace query. 177 * @param name The field that changed. 178 */ 179 @Override 180 public void changed(String name) { 181 if (_query != null) { 182 switch (name) { 183 case "Find": 184 Highlighter highlighter = text.getHighlighter(); 185 highlighter.removeAllHighlights(); 186 187 String textValue = text.getText(); 188 String search = _query.getStringValue("Find"); 189 _previousSearch = search; 190 int start = text.getCaretPosition(); 191 int location = textValue.indexOf(search, start); 192 // If nothing is found from the start position, search again from the top. 193 if (location < 0) { 194 location = textValue.indexOf(search); 195 } 196 if (location >= 0) { 197 int firstMatch = location; 198 // Highlight the first match. 199 int end = location + search.length(); 200 Highlighter.HighlightPainter painter = new DefaultHighlighter.DefaultHighlightPainter( 201 Color.YELLOW); 202 try { 203 highlighter.addHighlight(location, end, painter); 204 } catch (BadLocationException e1) { 205 // Ignore. Should not occur. 206 } 207 208 // Select the first match. 209 text.setCaretPosition(location); 210 text.moveCaretPosition(end); 211 // Highlight the remaining matches. 212 painter = new DefaultHighlighter.DefaultHighlightPainter( 213 Color.CYAN); 214 int count = 1; 215 while (location >= 0 && end < textValue.length()) { 216 location = textValue.indexOf(search, end); 217 if (location >= 0) { 218 count++; 219 end = location + search.length(); 220 try { 221 highlighter.addHighlight(location, end, 222 painter); 223 } catch (BadLocationException e) { 224 // Ignore. Should not occur. 225 } 226 } 227 } 228 // Reached the end. Search again from the top. 229 location = textValue.indexOf(search); 230 while (location >= 0 && location < firstMatch) { 231 count++; 232 end = location + search.length(); 233 try { 234 highlighter.addHighlight(location, end, painter); 235 } catch (BadLocationException e) { 236 // Ignore. Should not occur. 237 } 238 location = textValue.indexOf(search, end); 239 } 240 _query.set("Result", "Found " + count 241 + ((count > 1) ? " matches" : " match")); 242 } else { 243 _query.set("Result", "Not found"); 244 } 245 return; 246 case "Replacement": 247 _previousReplacement = _query.getStringValue("Replacement"); 248 return; 249 } 250 } 251 } 252 253 /** React to notification that an attribute or set of attributes 254 * changed. 255 */ 256 @Override 257 public void changedUpdate(DocumentEvent e) { 258 // Do nothing... We don't care about attributes. 259 } 260 261 /** Dispose of this frame. 262 * Override this dispose() method to unattach any listeners that may keep 263 * this model from getting garbage collected. This method invokes the 264 * dispose() method of the superclass, 265 * {@link ptolemy.actor.gui.TableauFrame}. 266 */ 267 @Override 268 public void dispose() { 269 if (_debugClosing) { 270 System.out.println("TextEditor.dispose() : " + this.getName()); 271 } 272 273 super.dispose(); 274 } 275 276 /** Get the background color. 277 * @return The background color of the scroll pane. 278 * If _scrollPane is null, then null is returned. 279 * @see #setBackground(Color) 280 */ 281 @Override 282 public Color getBackground() { 283 // Under Java 1.7 on the Mac, the _scrollbar is sometimes null. 284 // See http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5574 285 if (_scrollPane != null) { 286 return _scrollPane.getBackground(); 287 } else { 288 return null; 289 } 290 } 291 292 /** Return the scroll pane, if there is one, and null if not. 293 * @return The scroll pane. 294 */ 295 public JScrollPane getScrollPane() { 296 return _scrollPane; 297 } 298 299 // CONTRIBUTED CODE. The exportImage() methods are from PlotBox, 300 // which says: 301 302 // I wanted the ability to use the Plot object in a servlet and to 303 // write out the resultant images. The following routines, 304 // particularly exportImage(), permit this. I also had to make some 305 // minor changes elsewhere. Rob Kroeger, May 2001. 306 307 // NOTE: This code has been modified by EAL to conform with Ptolemy II 308 // coding style. 309 310 /** Create a BufferedImage and draw this plot to it. 311 * The size of the returned image matches the current size of the plot. 312 * This method can be used, for 313 * example, by a servlet to produce an image, rather than 314 * requiring an applet to instantiate a PlotBox. 315 * @return An image filled by the plot. 316 */ 317 public synchronized BufferedImage exportImage() { 318 Dimension dimension = getSize(); 319 Rectangle rectangle = new Rectangle(dimension.height, dimension.width); 320 return exportImage( 321 new BufferedImage(rectangle.width, rectangle.height, 322 BufferedImage.TYPE_INT_ARGB), 323 rectangle, _defaultImageRenderingHints(), false); 324 } 325 326 /** Draw this plot onto the specified image at the position of the 327 * specified rectangle with the size of the specified rectangle. 328 * The plot is rendered using anti-aliasing. 329 * This can be used to paint a number of different 330 * plots onto a single buffered image. This method can be used, for 331 * example, by a servlet to produce an image, rather than 332 * requiring an applet to instantiate a PlotBox. 333 * @param bufferedImage Image onto which the plot is drawn. 334 * @param rectangle The size and position of the plot in the image. 335 * @param hints Rendering hints for this plot. 336 * @param transparent Indicator that the background of the plot 337 * should not be painted. 338 * @return The modified bufferedImage. 339 */ 340 public synchronized BufferedImage exportImage(BufferedImage bufferedImage, 341 Rectangle rectangle, RenderingHints hints, boolean transparent) { 342 Graphics2D graphics = bufferedImage.createGraphics(); 343 graphics.addRenderingHints(_defaultImageRenderingHints()); 344 345 if (!transparent) { 346 graphics.setColor(Color.white); // set the background color 347 graphics.fill(rectangle); 348 } 349 350 print(graphics, rectangle); 351 return bufferedImage; 352 } 353 354 /** Export an image of the plot in the specified format. 355 * If the specified format is not supported, then pop up a message 356 * window apologizing. 357 * @param out An output stream to which to send the description. 358 * @param formatName A format name, such as "gif" or "png". 359 */ 360 public synchronized void exportImage(OutputStream out, String formatName) { 361 try { 362 boolean match = false; 363 String[] supportedFormats = ImageIO.getWriterFormatNames(); 364 for (String supportedFormat : supportedFormats) { 365 if (formatName.equalsIgnoreCase(supportedFormat)) { 366 match = true; 367 break; 368 } 369 } 370 if (!match) { 371 // This exception is caught and reported below. 372 throw new Exception("Format " + formatName + " not supported."); 373 } 374 BufferedImage image = exportImage(); 375 if (out == null) { 376 // FIXME: Write image to the clipboard. 377 // final Clipboard clipboard = getToolkit().getSystemClipboard(); 378 String message = "Copy to the clipboard is not implemented yet."; 379 JOptionPane.showMessageDialog(this, message, 380 "Ptolemy Plot Message", JOptionPane.ERROR_MESSAGE); 381 return; 382 } 383 ImageIO.write(image, formatName, out); 384 } catch (Exception ex) { 385 String message = "Export failed: " + ex.getMessage(); 386 JOptionPane.showMessageDialog(this, message, "Ptolemy Plot Message", 387 JOptionPane.ERROR_MESSAGE); 388 389 // Rethrow the exception so that we don't report success, 390 // and so the stack trace is displayed on standard out. 391 throw (RuntimeException) ex.fillInStackTrace(); 392 } 393 } 394 395 /** React to notification that there was an insert into the document. 396 */ 397 @Override 398 public void insertUpdate(DocumentEvent e) { 399 setModified(true); 400 } 401 402 /** Print the text to a printer, which is represented by the 403 * specified graphics object. 404 * @param graphics The context into which the page is drawn. 405 * @param format The size and orientation of the page being drawn. 406 * @param index The zero based index of the page to be drawn. 407 * @return PAGE_EXISTS if the page is rendered successfully, or 408 * NO_SUCH_PAGE if pageIndex specifies a non-existent page. 409 * @exception PrinterException If the print job is terminated. 410 */ 411 @Override 412 public int print(Graphics graphics, PageFormat format, int index) 413 throws PrinterException { 414 if (graphics == null) { 415 return Printable.NO_SUCH_PAGE; 416 } 417 418 Graphics2D graphics2D = (Graphics2D) graphics; 419 420 double bottomMargin = format.getHeight() - format.getImageableHeight() 421 - format.getImageableY(); 422 423 double lineHeight = graphics2D.getFontMetrics().getHeight() 424 - graphics2D.getFontMetrics().getLeading() / 2; 425 426 int linesPerPage = (int) Math.floor( 427 (format.getHeight() - format.getImageableY() - bottomMargin) 428 / lineHeight); 429 430 int lineYPosition = (int) Math 431 .ceil(format.getImageableY() + lineHeight); 432 433 return _print(graphics2D, index, linesPerPage, lineHeight, 434 (int) format.getImageableX(), lineYPosition, 435 format.getHeight() - bottomMargin); 436 437 } 438 439 /** Print the text to a printer, which is represented by the 440 * specified graphics object. 441 * @param graphics The context into which the page is drawn. 442 * @param drawRect specification of the size. 443 * @return PAGE_EXISTS if the page is rendered successfully, or 444 * NO_SUCH_PAGE if pageIndex specifies a non-existent page. 445 */ 446 public int print(Graphics graphics, Rectangle drawRect) { 447 if (graphics == null) { 448 return Printable.NO_SUCH_PAGE; 449 } 450 451 graphics.setPaintMode(); 452 453 Graphics2D graphics2D = (Graphics2D) graphics; 454 455 // Loosely based on 456 // http://forum.java.sun.com/thread.jspa?threadID=217823&messageID=2361189 457 // Found it unwise to use the TextArea font's size, 458 // We area just printing text so use a a font size that will 459 // be generally useful. 460 graphics2D.setFont(getFont().deriveFont(9.0f)); 461 462 // FIXME: we should probably get the color somehow. Probably exportImage() is being 463 // called with transparent set to false? 464 graphics2D.setColor(java.awt.Color.BLACK); 465 466 // FIXME: Magic Number 5, similar to what is in PlotBox. 467 double bottomMargin = 5; 468 469 double lineHeight = graphics2D.getFontMetrics().getHeight() 470 - graphics2D.getFontMetrics().getLeading() / 2; 471 472 int linesPerPage = (int) Math 473 .floor((drawRect.height - bottomMargin) / lineHeight); 474 475 int lineYPosition = (int) Math.ceil(lineHeight); 476 477 return _print(graphics2D, 0 /* page */, linesPerPage, lineHeight, 0, 478 lineYPosition, drawRect.height - bottomMargin); 479 } 480 481 /** React to notification that there was a removal from the document. 482 */ 483 @Override 484 public void removeUpdate(DocumentEvent e) { 485 setModified(true); 486 } 487 488 /** Scroll as necessary so that the last line is visible. 489 */ 490 public void scrollToEnd() { 491 // Song and dance to scroll to the new line. 492 text.scrollRectToVisible(new Rectangle(new Point(0, text.getHeight()))); 493 } 494 495 /** Set background color. This overrides the base class to set the 496 * background of contained scroll pane and text area. 497 * @param background The background color. 498 * @see #getBackground() 499 */ 500 @Override 501 public void setBackground(Color background) { 502 super.setBackground(background); 503 504 // This seems to be called in a base class constructor, before 505 // this variable has been set. Hence the test against null. 506 if (_scrollPane != null) { 507 _scrollPane.setBackground(background); 508 } 509 510 if (text != null) { 511 // NOTE: Should the background always be white? 512 text.setBackground(background); 513 } 514 } 515 516 /** Write an image to the specified output stream in the specified 517 * format. Supported formats include at least "gif" and "png", 518 * standard image file formats. The image is a rendition of the 519 * current view of the model. 520 * @param stream The output stream to write to. 521 * @param format The image format to generate. 522 * @exception IOException If writing to the stream fails. 523 * @exception PrinterException If the specified format is not supported. 524 */ 525 @Override 526 public void writeImage(OutputStream stream, String format) 527 throws PrinterException, IOException { 528 exportImage(stream, format); 529 } 530 531 /////////////////////////////////////////////////////////////////// 532 //// protected methods //// 533 534 /** Create an edit menu. 535 */ 536 @Override 537 protected void _addMenus() { 538 super._addMenus(); 539 540 _editMenu = new JMenu("Edit"); 541 _editMenu.setMnemonic(KeyEvent.VK_E); 542 _menubar.add(_editMenu); 543 544 GUIUtilities.addMenuItem(_editMenu, new UndoAction()); 545 GUIUtilities.addMenuItem(_editMenu, new RedoAction()); 546 547 _editMenu.addSeparator(); 548 549 GUIUtilities.addMenuItem(_editMenu, new CutAction()); 550 GUIUtilities.addMenuItem(_editMenu, new CopyAction()); 551 GUIUtilities.addMenuItem(_editMenu, new PasteAction()); 552 553 _editMenu.addSeparator(); 554 555 GUIUtilities.addMenuItem(_editMenu, _findAction); 556 557 } 558 559 /** Clear the current contents. First, check to see whether 560 * the contents have been modified, and if so, then prompt the user 561 * to save them. A return value of false 562 * indicates that the user has canceled the action. 563 * @return False if the user cancels the clear. 564 */ 565 @Override 566 protected boolean _clear() { 567 if (super._clear()) { 568 text.setText(""); 569 return true; 570 } else { 571 return false; 572 } 573 } 574 575 /** Create the items in the File menu's Export section 576 * This method adds a menu items to export images of the plot 577 * in GIF, PNG, and possibly PDF. 578 * @return The items in the File menu. 579 */ 580 @Override 581 protected JMenuItem[] _createFileMenuItems() { 582 // This method is similar to ptolemy/actor/gui/PlotTableauFrame.java, but we don't 583 // handle pdfs. 584 585 JMenuItem[] fileMenuItems = super._createFileMenuItems(); 586 587 JMenu exportMenu = (JMenu) fileMenuItems[_EXPORT_MENU_INDEX]; 588 exportMenu.setEnabled(true); 589 590 // Next do the export GIF action. 591 if (_exportGIFAction == null) { 592 _exportGIFAction = new ExportImageAction("GIF"); 593 } 594 JMenuItem exportItem = new JMenuItem(_exportGIFAction); 595 exportMenu.add(exportItem); 596 597 // Next do the export PNG action. 598 if (_exportPNGAction == null) { 599 _exportPNGAction = new ExportImageAction("PNG"); 600 } 601 exportItem = new JMenuItem(_exportPNGAction); 602 exportMenu.add(exportItem); 603 604 return fileMenuItems; 605 } 606 607 /** Find and replace. */ 608 protected void _find() { 609 _query = new Query(); 610 _query.addLine("Find", "Find", _previousSearch); 611 _query.addLine("Replacement", "Replacement", _previousReplacement); 612 _query.addDisplay("Result", "", ""); 613 614 // If there was a previous search, perform that search now. 615 if (_previousSearch != null && _previousSearch.length() > 0) { 616 changed("Find"); 617 } 618 619 _query.addQueryListener(this); 620 String[] buttons = { "Next", "Close", "Replace", "Replace and Find", 621 "Replace All" }; 622 new ComponentDialog(this, "Find and Replace", _query, buttons) { 623 /** If the contents of this dialog implements the CloseListener 624 * interface, then notify it that the window has closed, unless 625 * notification has already been done (it is guaranteed to be done 626 * only once). 627 */ 628 @Override 629 protected void _handleClosing() { 630 switch (_buttonPressed) { 631 case "Close": 632 super._handleClosing(); 633 return; 634 case "Next": 635 changed("Find"); 636 return; 637 case "Replace": 638 _undo.startCompoundEdit(); 639 text.replaceSelection(_previousReplacement); 640 _undo.endCompoundEdit(); 641 return; 642 case "Replace and Find": 643 _undo.startCompoundEdit(); 644 text.replaceSelection(_previousReplacement); 645 changed("Find"); 646 _undo.endCompoundEdit(); 647 return; 648 case "Replace All": 649 _undo.startCompoundEdit(); 650 Highlighter highlighter = text.getHighlighter(); 651 Highlight[] highlights = highlighter.getHighlights(); 652 // No idea why, but first highlight is listed twice. 653 // Perhaps because it is the selection as well? 654 boolean first = true; 655 for (Highlight highlight : highlights) { 656 if (first) { 657 first = false; 658 continue; 659 } 660 text.replaceRange(_previousReplacement, 661 highlight.getStartOffset(), 662 highlight.getEndOffset()); 663 } 664 _undo.endCompoundEdit(); 665 return; 666 default: 667 // Some other closing event, like Esc. 668 super._handleClosing(); 669 } 670 } 671 }; 672 text.getHighlighter().removeAllHighlights(); 673 _query = null; 674 } 675 676 /** Display more detailed information than given by _about(). 677 */ 678 @Override 679 protected void _help() { 680 // FIXME: Give instructions for the editor here. 681 _about(); 682 } 683 684 /** Initializes an empty text editor with the specified title and 685 * document and associated placeable. After constructing this, 686 * it is necessary to call setVisible(true) to make the frame 687 * appear. 688 * 689 * @param title The title to put in the title bar. 690 * @param document The document containing text. 691 */ 692 protected void _init(String title, Document document) { 693 setTitle(title); 694 695 text = new JTextArea(document); 696 697 // Since the document may have been null, request it... 698 document = text.getDocument(); 699 document.addDocumentListener(this); 700 _scrollPane = new JScrollPane(text); 701 702 getContentPane().add(_scrollPane, BorderLayout.CENTER); 703 _initialSaveAsFileName = "data.txt"; 704 705 // Set the undo listener, with default key mappings. 706 _undo = new UndoListener(text); 707 text.getDocument().addUndoableEditListener(_undo); 708 } 709 710 /** Print the contents. 711 */ 712 @Override 713 protected void _print() { 714 // FIXME: What should we print? 715 super._print(); 716 } 717 718 /** Redo the last undo action. 719 */ 720 protected void _redo() { 721 _undo.redo(); 722 } 723 724 /** Query the user for a filename, save the model to that file, 725 * and open a new window to view the model. 726 * This overrides the base class to use the ".txt" extension. 727 * @return True if the save succeeds. 728 */ 729 @Override 730 protected boolean _saveAs() { 731 return _saveAs(".txt"); 732 } 733 734 /** Undo the last action. 735 */ 736 protected void _undo() { 737 _undo.undo(); 738 } 739 740 // FIXME: Listen for window closing. 741 742 /////////////////////////////////////////////////////////////////// 743 //// protected variables //// 744 745 /** The edit menu. */ 746 protected JMenu _editMenu; 747 748 /** The export to GIF action. */ 749 protected Action _exportGIFAction; 750 751 /** The export to PNG action. */ 752 protected Action _exportPNGAction; 753 754 /** The scroll pane containing the text area. */ 755 protected JScrollPane _scrollPane; 756 757 /** The undo listener. */ 758 protected UndoListener _undo; 759 760 /////////////////////////////////////////////////////////////////// 761 //// private methods //// 762 763 /** Return a default set of rendering hints for image export, which 764 * specifies the use of anti-aliasing. 765 */ 766 private RenderingHints _defaultImageRenderingHints() { 767 // From PlotBox 768 RenderingHints hints = new RenderingHints(null); 769 hints.put(RenderingHints.KEY_ANTIALIASING, 770 RenderingHints.VALUE_ANTIALIAS_ON); 771 return hints; 772 } 773 774 /** Print the contents of the editor to a Graphics. 775 * This used both by the print facility and the exportImage facility. 776 * @param graphics2D The context into which the page is drawn. 777 * @return PAGE_EXISTS if the page is rendered successfully, or 778 * NO_SUCH_PAGE if pageIndex specifies a non-existent page. 779 */ 780 private int _print(Graphics2D graphics2D, int index, int linesPerPage, 781 double lineHeight, int lineXPosition, int linePosition, 782 double bottomLinePosition) { 783 784 int startLine = linesPerPage * index; 785 786 if (startLine > text.getLineCount()) { 787 return NO_SUCH_PAGE; 788 } 789 790 int endLine = startLine + linesPerPage; 791 for (int line = startLine; line < endLine; line++) { 792 try { 793 String linetext = text.getText(text.getLineStartOffset(line), 794 text.getLineEndOffset(line) 795 - text.getLineStartOffset(line)); 796 graphics2D.drawString(linetext, lineXPosition, linePosition); 797 } catch (BadLocationException e) { 798 // Ignore. Never a bad location. 799 } 800 801 linePosition += lineHeight; 802 if (linePosition > bottomLinePosition) { 803 break; 804 } 805 } 806 return PAGE_EXISTS; 807 } 808 809 /////////////////////////////////////////////////////////////////// 810 //// private variables //// 811 812 /** Action to find and replace. */ 813 private Action _findAction = new FindAction(); 814 815 /** Find and replace query, or null if there is none. */ 816 private Query _query; 817 818 /** Previous replacement string, if any. */ 819 private String _previousReplacement; 820 821 /** Previous search string, if any. */ 822 private String _previousSearch; 823 824 /////////////////////////////////////////////////////////////////// 825 //// inner classes //// 826 827 /////////////////////////////////////////////////////////////////// 828 //// CopyAction 829 830 /** Copy the contents of the selection and put on clipboard. */ 831 private class CopyAction extends AbstractAction { 832 public CopyAction() { 833 super("Copy"); 834 putValue("tooltip", "Copy to clipboard."); 835 putValue(diva.gui.GUIUtilities.ACCELERATOR_KEY, 836 KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit 837 .getDefaultToolkit().getMenuShortcutKeyMask())); 838 putValue(diva.gui.GUIUtilities.MNEMONIC_KEY, 839 Integer.valueOf(KeyEvent.VK_O)); 840 } 841 842 @Override 843 public void actionPerformed(ActionEvent e) { 844 text.copy(); 845 } 846 } 847 848 /////////////////////////////////////////////////////////////////// 849 //// CutAction 850 851 /** Cut the contents of the selection and put on clipboard. */ 852 private class CutAction extends AbstractAction { 853 public CutAction() { 854 super("Cut"); 855 putValue("tooltip", "Cut to clipboard."); 856 putValue(diva.gui.GUIUtilities.ACCELERATOR_KEY, 857 KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit 858 .getDefaultToolkit().getMenuShortcutKeyMask())); 859 putValue(diva.gui.GUIUtilities.MNEMONIC_KEY, 860 Integer.valueOf(KeyEvent.VK_C)); 861 } 862 863 @Override 864 public void actionPerformed(ActionEvent e) { 865 text.cut(); 866 } 867 } 868 869 /////////////////////////////////////////////////////////////////// 870 //// ExportImageAction 871 872 /** Export an image. */ 873 public class ExportImageAction extends AbstractAction { 874 // FIXME: this is very similar to PlotTableaFrame.ExportImageAction. 875 876 /** Create a new action to export an image. 877 * @param formatName The name of the format, currently PNG and 878 * GIF are supported. 879 */ 880 public ExportImageAction(String formatName) { 881 super("Export " + formatName); 882 _formatName = formatName.toLowerCase(Locale.getDefault()); 883 putValue("tooltip", "Export " + formatName + " image to a file."); 884 // putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_G)); 885 } 886 887 /** Export an image. 888 * @param e The ActionEvent that invoked this action. 889 */ 890 @Override 891 public void actionPerformed(ActionEvent e) { 892 JFileChooserBugFix jFileChooserBugFix = new JFileChooserBugFix(); 893 Color background = null; 894 try { 895 background = jFileChooserBugFix.saveBackground(); 896 897 JFileChooser fileDialog = new JFileChooser(); 898 fileDialog.setDialogTitle("Specify a file to write to."); 899 LinkedList extensions = new LinkedList(); 900 extensions.add(_formatName); 901 fileDialog.addChoosableFileFilter( 902 new ExtensionFilenameFilter(extensions)); 903 904 if (_directory != null) { 905 fileDialog.setCurrentDirectory(_directory); 906 } else { 907 // The default on Windows is to open at user.home, which is 908 // typically an absurd directory inside the O/S installation. 909 // So we use the current directory instead. 910 // This will throw a security exception in an applet. 911 // FIXME: we should support users under applets opening files 912 // on the server. 913 String currentWorkingDirectory = StringUtilities 914 .getProperty("user.dir"); 915 if (currentWorkingDirectory != null) { 916 fileDialog.setCurrentDirectory( 917 new File(currentWorkingDirectory)); 918 } 919 } 920 921 // Here, we differ from PlotTableauFrame: 922 int returnVal = fileDialog.showDialog(TextEditor.this, "Export " 923 + _formatName.toUpperCase(Locale.getDefault())); 924 925 if (returnVal == JFileChooser.APPROVE_OPTION) { 926 _directory = fileDialog.getCurrentDirectory(); 927 File file = fileDialog.getSelectedFile().getCanonicalFile(); 928 929 if (file.getName().indexOf(".") == -1) { 930 // If the user has not given the file an extension, add it 931 file = new File( 932 file.getAbsolutePath() + "." + _formatName); 933 } 934 if (file.exists()) { 935 if (!MessageHandler.yesNoQuestion( 936 "Overwrite " + file.getName() + "?")) { 937 return; 938 } 939 } 940 OutputStream out = null; 941 try { 942 out = new FileOutputStream(file); 943 exportImage(out, _formatName); 944 } finally { 945 if (out != null) { 946 out.close(); 947 } 948 } 949 950 // Open the PNG file. 951 // FIXME: We don't do the right thing with PNG files. 952 // It just opens in a text editor. 953 // _read(file.toURI().toURL()); 954 MessageHandler.message( 955 "Image file exported to " + file.getName()); 956 } 957 } catch (Exception ex) { 958 MessageHandler.error("Export to " 959 + _formatName.toUpperCase(Locale.getDefault()) 960 + " failed", ex); 961 } finally { 962 jFileChooserBugFix.restoreBackground(background); 963 } 964 } 965 966 private String _formatName; 967 } 968 969 /////////////////////////////////////////////////////////////////// 970 //// FindAction 971 972 /** Find and replace. */ 973 private class FindAction extends AbstractAction { 974 public FindAction() { 975 super("Find"); 976 putValue("tooltip", "Find and replace."); 977 putValue(diva.gui.GUIUtilities.ACCELERATOR_KEY, 978 KeyStroke.getKeyStroke(KeyEvent.VK_F, Toolkit 979 .getDefaultToolkit().getMenuShortcutKeyMask())); 980 putValue(diva.gui.GUIUtilities.MNEMONIC_KEY, 981 Integer.valueOf(KeyEvent.VK_F)); 982 } 983 984 @Override 985 public void actionPerformed(ActionEvent e) { 986 try { 987 _find(); 988 } catch (CannotRedoException ex) { 989 // Ignore. 990 } 991 } 992 } 993 994 /////////////////////////////////////////////////////////////////// 995 //// PasteAction 996 997 /** Copy the contents of the selection and put on clipboard. */ 998 private class PasteAction extends AbstractAction { 999 public PasteAction() { 1000 super("Paste"); 1001 putValue("tooltip", "Paste from clipboard."); 1002 putValue(diva.gui.GUIUtilities.ACCELERATOR_KEY, 1003 KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit 1004 .getDefaultToolkit().getMenuShortcutKeyMask())); 1005 putValue(diva.gui.GUIUtilities.MNEMONIC_KEY, 1006 Integer.valueOf(KeyEvent.VK_P)); 1007 } 1008 1009 @Override 1010 public void actionPerformed(ActionEvent e) { 1011 text.paste(); 1012 } 1013 } 1014 1015 /////////////////////////////////////////////////////////////////// 1016 //// RedoAction 1017 1018 /** Redo the last undo change. */ 1019 private class RedoAction extends AbstractAction { 1020 public RedoAction() { 1021 super("Redo"); 1022 putValue("tooltip", "Redo the last undo."); 1023 putValue(diva.gui.GUIUtilities.ACCELERATOR_KEY, 1024 KeyStroke.getKeyStroke(KeyEvent.VK_Y, Toolkit 1025 .getDefaultToolkit().getMenuShortcutKeyMask())); 1026 putValue(diva.gui.GUIUtilities.MNEMONIC_KEY, 1027 Integer.valueOf(KeyEvent.VK_R)); 1028 } 1029 1030 @Override 1031 public void actionPerformed(ActionEvent e) { 1032 try { 1033 _redo(); 1034 } catch (CannotRedoException ex) { 1035 // Ignore. 1036 } 1037 } 1038 } 1039 1040 /////////////////////////////////////////////////////////////////// 1041 //// UndoAction 1042 1043 /** Undo the last change to the text. */ 1044 private class UndoAction extends AbstractAction { 1045 public UndoAction() { 1046 super("Undo"); 1047 putValue("tooltip", "Undo the last change."); 1048 putValue(diva.gui.GUIUtilities.ACCELERATOR_KEY, 1049 KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit 1050 .getDefaultToolkit().getMenuShortcutKeyMask())); 1051 putValue(diva.gui.GUIUtilities.MNEMONIC_KEY, 1052 Integer.valueOf(KeyEvent.VK_U)); 1053 } 1054 1055 @Override 1056 public void actionPerformed(ActionEvent e) { 1057 try { 1058 _undo(); 1059 } catch (CannotUndoException ex) { 1060 // Ignore. 1061 } 1062 } 1063 } 1064}