1 /*
   2  * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
   3  *
   4  * Redistribution and use in source and binary forms, with or without
   5  * modification, are permitted provided that the following conditions
   6  * are met:
   7  *
   8  *   - Redistributions of source code must retain the above copyright
   9  *     notice, this list of conditions and the following disclaimer.
  10  *
  11  *   - Redistributions in binary form must reproduce the above copyright
  12  *     notice, this list of conditions and the following disclaimer in the
  13  *     documentation and/or other materials provided with the distribution.
  14  *
  15  *   - Neither the name of Oracle nor the names of its
  16  *     contributors may be used to endorse or promote products derived
  17  *     from this software without specific prior written permission.
  18  *
  19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30  */
  31 
  32 /*
  33  * This source code is provided to illustrate the usage of a given feature
  34  * or technique and has been deliberately simplified. Additional steps
  35  * required for a production-quality application, such as security checks,
  36  * input validation and proper error handling, might not be present in
  37  * this sample code.
  38  */
  39 
  40 
  41 
  42 import java.awt.*;
  43 import java.awt.event.*;
  44 import java.beans.*;
  45 import java.io.*;
  46 import java.net.*;
  47 import java.util.*;
  48 import java.util.logging.*;
  49 import javax.swing.*;
  50 import javax.swing.undo.*;
  51 import javax.swing.text.*;
  52 import javax.swing.event.*;
  53 import javax.swing.UIManager.LookAndFeelInfo;
  54 
  55 
  56 /**
  57  * Sample application using the simple text editor component that
  58  * supports only one font.
  59  *
  60  * @author  Timothy Prinzing
  61  */
  62 @SuppressWarnings("serial")
  63 class Notepad extends JPanel {
  64 
  65     protected static Properties properties;
  66     private static ResourceBundle resources;
  67     private final static String EXIT_AFTER_PAINT = "-exit";
  68     private static boolean exitAfterFirstPaint;
  69 
  70     private static final String[] MENUBAR_KEYS = {"file", "edit", "debug"};
  71     private static final String[] TOOLBAR_KEYS = {"new", "open", "save", "-", "cut", "copy", "paste"};
  72     private static final String[] FILE_KEYS = {"new", "open", "save", "-", "exit"};
  73     private static final String[] EDIT_KEYS = {"cut", "copy", "paste", "-", "undo", "redo"};
  74     private static final String[] DEBUG_KEYS = {"dump", "showElementTree"};
  75 
  76     static {
  77         try {
  78             properties = new Properties();
  79             properties.load(Notepad.class.getResourceAsStream(
  80                     "resources/NotepadSystem.properties"));
  81             resources = ResourceBundle.getBundle("resources.Notepad",
  82                     Locale.getDefault());
  83         } catch (MissingResourceException | IOException  e) {
  84             System.err.println("resources/Notepad.properties "
  85                     + "or resources/NotepadSystem.properties not found");
  86             System.exit(1);
  87         }
  88     }
  89 
  90     @Override
  91     public void paintChildren(Graphics g) {
  92         super.paintChildren(g);
  93         if (exitAfterFirstPaint) {
  94             System.exit(0);
  95         }
  96     }
  97 
  98     @SuppressWarnings("OverridableMethodCallInConstructor")
  99     Notepad() {
 100         super(true);
 101 
 102         // Trying to set Nimbus look and feel
 103         try {
 104             for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
 105                 if ("Nimbus".equals(info.getName())) {
 106                     UIManager.setLookAndFeel(info.getClassName());
 107                     break;
 108                 }
 109             }
 110         } catch (Exception ignored) {
 111         }
 112 
 113         setBorder(BorderFactory.createEtchedBorder());
 114         setLayout(new BorderLayout());
 115 
 116         // create the embedded JTextComponent
 117         editor = createEditor();
 118         // Add this as a listener for undoable edits.
 119         editor.getDocument().addUndoableEditListener(undoHandler);
 120 
 121         // install the command table
 122         commands = new HashMap<Object, Action>();
 123         Action[] actions = getActions();
 124         for (Action a : actions) {
 125             commands.put(a.getValue(Action.NAME), a);
 126         }
 127 
 128         JScrollPane scroller = new JScrollPane();
 129         JViewport port = scroller.getViewport();
 130         port.add(editor);
 131 
 132         String vpFlag = getProperty("ViewportBackingStore");
 133         if (vpFlag != null) {
 134             Boolean bs = Boolean.valueOf(vpFlag);
 135             port.setScrollMode(bs
 136                     ? JViewport.BACKINGSTORE_SCROLL_MODE
 137                     : JViewport.BLIT_SCROLL_MODE);
 138         }
 139 
 140         JPanel panel = new JPanel();
 141         panel.setLayout(new BorderLayout());
 142         panel.add("North", createToolbar());
 143         panel.add("Center", scroller);
 144         add("Center", panel);
 145         add("South", createStatusbar());
 146     }
 147 
 148     public static void main(String[] args) throws Exception {
 149         if (args.length > 0 && args[0].equals(EXIT_AFTER_PAINT)) {
 150             exitAfterFirstPaint = true;
 151         }
 152         SwingUtilities.invokeAndWait(new Runnable() {
 153 
 154             public void run() {
 155                 JFrame frame = new JFrame();
 156                 frame.setTitle(resources.getString("Title"));
 157                 frame.setBackground(Color.lightGray);
 158                 frame.getContentPane().setLayout(new BorderLayout());
 159                 Notepad notepad = new Notepad();
 160                 frame.getContentPane().add("Center", notepad);
 161                 frame.setJMenuBar(notepad.createMenubar());
 162                 frame.addWindowListener(new AppCloser());
 163                 frame.pack();
 164                 frame.setSize(500, 600);
 165                 frame.setVisible(true);
 166             }
 167         });
 168     }
 169 
 170     /**
 171      * Fetch the list of actions supported by this
 172      * editor.  It is implemented to return the list
 173      * of actions supported by the embedded JTextComponent
 174      * augmented with the actions defined locally.
 175      */
 176     public Action[] getActions() {
 177         return TextAction.augmentList(editor.getActions(), defaultActions);
 178     }
 179 
 180     /**
 181      * Create an editor to represent the given document.
 182      */
 183     protected JTextComponent createEditor() {
 184         JTextComponent c = new JTextArea();
 185         c.setDragEnabled(true);
 186         c.setFont(new Font("monospaced", Font.PLAIN, 12));
 187         return c;
 188     }
 189 
 190     /**
 191      * Fetch the editor contained in this panel
 192      */
 193     protected JTextComponent getEditor() {
 194         return editor;
 195     }
 196 
 197 
 198     /**
 199      * To shutdown when run as an application.  This is a
 200      * fairly lame implementation.   A more self-respecting
 201      * implementation would at least check to see if a save
 202      * was needed.
 203      */
 204     protected static final class AppCloser extends WindowAdapter {
 205 
 206         @Override
 207         public void windowClosing(WindowEvent e) {
 208             System.exit(0);
 209         }
 210     }
 211 
 212     /**
 213      * Find the hosting frame, for the file-chooser dialog.
 214      */
 215     protected Frame getFrame() {
 216         for (Container p = getParent(); p != null; p = p.getParent()) {
 217             if (p instanceof Frame) {
 218                 return (Frame) p;
 219             }
 220         }
 221         return null;
 222     }
 223 
 224     /**
 225      * This is the hook through which all menu items are
 226      * created.
 227      */
 228     protected JMenuItem createMenuItem(String cmd) {
 229         JMenuItem mi = new JMenuItem(getResourceString(cmd + labelSuffix));
 230         URL url = getResource(cmd + imageSuffix);
 231         if (url != null) {
 232             mi.setHorizontalTextPosition(JButton.RIGHT);
 233             mi.setIcon(new ImageIcon(url));
 234         }
 235         String astr = getProperty(cmd + actionSuffix);
 236         if (astr == null) {
 237             astr = cmd;
 238         }
 239         mi.setActionCommand(astr);
 240         Action a = getAction(astr);
 241         if (a != null) {
 242             mi.addActionListener(a);
 243             a.addPropertyChangeListener(createActionChangeListener(mi));
 244             mi.setEnabled(a.isEnabled());
 245         } else {
 246             mi.setEnabled(false);
 247         }
 248         return mi;
 249     }
 250 
 251     protected Action getAction(String cmd) {
 252         return commands.get(cmd);
 253     }
 254 
 255     protected String getProperty(String key) {
 256         return properties.getProperty(key);
 257     }
 258 
 259     protected String getResourceString(String nm) {
 260         String str;
 261         try {
 262             str = resources.getString(nm);
 263         } catch (MissingResourceException mre) {
 264             str = null;
 265         }
 266         return str;
 267     }
 268 
 269     protected URL getResource(String key) {
 270         String name = getResourceString(key);
 271         if (name != null) {
 272             return this.getClass().getResource(name);
 273         }
 274         return null;
 275     }
 276 
 277     /**
 278      * Create a status bar
 279      */
 280     protected Component createStatusbar() {
 281         // need to do something reasonable here
 282         status = new StatusBar();
 283         return status;
 284     }
 285 
 286     /**
 287      * Resets the undo manager.
 288      */
 289     protected void resetUndoManager() {
 290         undo.discardAllEdits();
 291         undoAction.update();
 292         redoAction.update();
 293     }
 294 
 295     /**
 296      * Create the toolbar.  By default this reads the
 297      * resource file for the definition of the toolbar.
 298      */
 299     private Component createToolbar() {
 300         toolbar = new JToolBar();
 301         for (String toolKey: getToolBarKeys()) {
 302             if (toolKey.equals("-")) {
 303                 toolbar.add(Box.createHorizontalStrut(5));
 304             } else {
 305                 toolbar.add(createTool(toolKey));
 306             }
 307         }
 308         toolbar.add(Box.createHorizontalGlue());
 309         return toolbar;
 310     }
 311 
 312     /**
 313      * Hook through which every toolbar item is created.
 314      */
 315     protected Component createTool(String key) {
 316         return createToolbarButton(key);
 317     }
 318 
 319     /**
 320      * Create a button to go inside of the toolbar.  By default this
 321      * will load an image resource.  The image filename is relative to
 322      * the classpath (including the '.' directory if its a part of the
 323      * classpath), and may either be in a JAR file or a separate file.
 324      *
 325      * @param key The key in the resource file to serve as the basis
 326      *  of lookups.
 327      */
 328     protected JButton createToolbarButton(String key) {
 329         URL url = getResource(key + imageSuffix);
 330         JButton b = new JButton(new ImageIcon(url)) {
 331 
 332             @Override
 333             public float getAlignmentY() {
 334                 return 0.5f;
 335             }
 336         };
 337         b.setRequestFocusEnabled(false);
 338         b.setMargin(new Insets(1, 1, 1, 1));
 339 
 340         String astr = getProperty(key + actionSuffix);
 341         if (astr == null) {
 342             astr = key;
 343         }
 344         Action a = getAction(astr);
 345         if (a != null) {
 346             b.setActionCommand(astr);
 347             b.addActionListener(a);
 348         } else {
 349             b.setEnabled(false);
 350         }
 351 
 352         String tip = getResourceString(key + tipSuffix);
 353         if (tip != null) {
 354             b.setToolTipText(tip);
 355         }
 356 
 357         return b;
 358     }
 359 
 360     /**
 361      * Create the menubar for the app.  By default this pulls the
 362      * definition of the menu from the associated resource file.
 363      */
 364     protected JMenuBar createMenubar() {
 365         JMenuBar mb = new JMenuBar();
 366         for(String menuKey: getMenuBarKeys()){
 367             JMenu m = createMenu(menuKey);
 368             if (m != null) {
 369                 mb.add(m);
 370             }
 371         }
 372         return mb;
 373     }
 374 
 375     /**
 376      * Create a menu for the app.  By default this pulls the
 377      * definition of the menu from the associated resource file.
 378      */
 379     protected JMenu createMenu(String key) {
 380         JMenu menu = new JMenu(getResourceString(key + labelSuffix));
 381         for (String itemKey: getItemKeys(key)) {
 382             if (itemKey.equals("-")) {
 383                 menu.addSeparator();
 384             } else {
 385                 JMenuItem mi = createMenuItem(itemKey);
 386                 menu.add(mi);
 387             }
 388         }
 389         return menu;
 390     }
 391 
 392     /**
 393      *  Get keys for menus
 394      */
 395     protected String[] getItemKeys(String key) {
 396         switch (key) {
 397             case "file":
 398                 return FILE_KEYS;
 399             case "edit":
 400                 return EDIT_KEYS;
 401             case "debug":
 402                 return DEBUG_KEYS;
 403             default:
 404                 return null;
 405         }
 406     }
 407 
 408     protected String[] getMenuBarKeys() {
 409         return MENUBAR_KEYS;
 410     }
 411 
 412     protected String[] getToolBarKeys() {
 413         return TOOLBAR_KEYS;
 414     }
 415 
 416     // Yarked from JMenu, ideally this would be public.
 417     protected PropertyChangeListener createActionChangeListener(JMenuItem b) {
 418         return new ActionChangedListener(b);
 419     }
 420 
 421     // Yarked from JMenu, ideally this would be public.
 422 
 423     private class ActionChangedListener implements PropertyChangeListener {
 424 
 425         JMenuItem menuItem;
 426 
 427         ActionChangedListener(JMenuItem mi) {
 428             super();
 429             this.menuItem = mi;
 430         }
 431 
 432         public void propertyChange(PropertyChangeEvent e) {
 433             String propertyName = e.getPropertyName();
 434             if (e.getPropertyName().equals(Action.NAME)) {
 435                 String text = (String) e.getNewValue();
 436                 menuItem.setText(text);
 437             } else if (propertyName.equals("enabled")) {
 438                 Boolean enabledState = (Boolean) e.getNewValue();
 439                 menuItem.setEnabled(enabledState.booleanValue());
 440             }
 441         }
 442     }
 443     private JTextComponent editor;
 444     private Map<Object, Action> commands;
 445     private JToolBar toolbar;
 446     private JComponent status;
 447     private JFrame elementTreeFrame;
 448     protected ElementTreePanel elementTreePanel;
 449 
 450     /**
 451      * Listener for the edits on the current document.
 452      */
 453     protected UndoableEditListener undoHandler = new UndoHandler();
 454     /** UndoManager that we add edits to. */
 455     protected UndoManager undo = new UndoManager();
 456     /**
 457      * Suffix applied to the key used in resource file
 458      * lookups for an image.
 459      */
 460     public static final String imageSuffix = "Image";
 461     /**
 462      * Suffix applied to the key used in resource file
 463      * lookups for a label.
 464      */
 465     public static final String labelSuffix = "Label";
 466     /**
 467      * Suffix applied to the key used in resource file
 468      * lookups for an action.
 469      */
 470     public static final String actionSuffix = "Action";
 471     /**
 472      * Suffix applied to the key used in resource file
 473      * lookups for tooltip text.
 474      */
 475     public static final String tipSuffix = "Tooltip";
 476     public static final String openAction = "open";
 477     public static final String newAction = "new";
 478     public static final String saveAction = "save";
 479     public static final String exitAction = "exit";
 480     public static final String showElementTreeAction = "showElementTree";
 481 
 482 
 483     class UndoHandler implements UndoableEditListener {
 484 
 485         /**
 486          * Messaged when the Document has created an edit, the edit is
 487          * added to <code>undo</code>, an instance of UndoManager.
 488          */
 489         public void undoableEditHappened(UndoableEditEvent e) {
 490             undo.addEdit(e.getEdit());
 491             undoAction.update();
 492             redoAction.update();
 493         }
 494     }
 495 
 496 
 497     /**
 498      * FIXME - I'm not very useful yet
 499      */
 500     class StatusBar extends JComponent {
 501 
 502         public StatusBar() {
 503             super();
 504             setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
 505         }
 506 
 507         @Override
 508         public void paint(Graphics g) {
 509             super.paint(g);
 510         }
 511     }
 512     // --- action implementations -----------------------------------
 513     private UndoAction undoAction = new UndoAction();
 514     private RedoAction redoAction = new RedoAction();
 515     /**
 516      * Actions defined by the Notepad class
 517      */
 518     private Action[] defaultActions = {
 519         new NewAction(),
 520         new OpenAction(),
 521         new SaveAction(),
 522         new ExitAction(),
 523         new ShowElementTreeAction(),
 524         undoAction,
 525         redoAction
 526     };
 527 
 528 
 529     class UndoAction extends AbstractAction {
 530 
 531         public UndoAction() {
 532             super("Undo");
 533             setEnabled(false);
 534         }
 535 
 536         public void actionPerformed(ActionEvent e) {
 537             try {
 538                 undo.undo();
 539             } catch (CannotUndoException ex) {
 540                 Logger.getLogger(UndoAction.class.getName()).log(Level.SEVERE,
 541                         "Unable to undo", ex);
 542             }
 543             update();
 544             redoAction.update();
 545         }
 546 
 547         protected void update() {
 548             if (undo.canUndo()) {
 549                 setEnabled(true);
 550                 putValue(Action.NAME, undo.getUndoPresentationName());
 551             } else {
 552                 setEnabled(false);
 553                 putValue(Action.NAME, "Undo");
 554             }
 555         }
 556     }
 557 
 558 
 559     class RedoAction extends AbstractAction {
 560 
 561         public RedoAction() {
 562             super("Redo");
 563             setEnabled(false);
 564         }
 565 
 566         public void actionPerformed(ActionEvent e) {
 567             try {
 568                 undo.redo();
 569             } catch (CannotRedoException ex) {
 570                 Logger.getLogger(RedoAction.class.getName()).log(Level.SEVERE,
 571                         "Unable to redo", ex);
 572             }
 573             update();
 574             undoAction.update();
 575         }
 576 
 577         protected void update() {
 578             if (undo.canRedo()) {
 579                 setEnabled(true);
 580                 putValue(Action.NAME, undo.getRedoPresentationName());
 581             } else {
 582                 setEnabled(false);
 583                 putValue(Action.NAME, "Redo");
 584             }
 585         }
 586     }
 587 
 588 
 589     class OpenAction extends NewAction {
 590 
 591         OpenAction() {
 592             super(openAction);
 593         }
 594 
 595         @Override
 596         public void actionPerformed(ActionEvent e) {
 597             Frame frame = getFrame();
 598             JFileChooser chooser = new JFileChooser();
 599             int ret = chooser.showOpenDialog(frame);
 600 
 601             if (ret != JFileChooser.APPROVE_OPTION) {
 602                 return;
 603             }
 604 
 605             File f = chooser.getSelectedFile();
 606             if (f.isFile() && f.canRead()) {
 607                 Document oldDoc = getEditor().getDocument();
 608                 if (oldDoc != null) {
 609                     oldDoc.removeUndoableEditListener(undoHandler);
 610                 }
 611                 if (elementTreePanel != null) {
 612                     elementTreePanel.setEditor(null);
 613                 }
 614                 getEditor().setDocument(new PlainDocument());
 615                 frame.setTitle(f.getName());
 616                 Thread loader = new FileLoader(f, editor.getDocument());
 617                 loader.start();
 618             } else {
 619                 JOptionPane.showMessageDialog(getFrame(),
 620                         "Could not open file: " + f,
 621                         "Error opening file",
 622                         JOptionPane.ERROR_MESSAGE);
 623             }
 624         }
 625     }
 626 
 627 
 628     class SaveAction extends AbstractAction {
 629 
 630         SaveAction() {
 631             super(saveAction);
 632         }
 633 
 634         public void actionPerformed(ActionEvent e) {
 635             Frame frame = getFrame();
 636             JFileChooser chooser = new JFileChooser();
 637             int ret = chooser.showSaveDialog(frame);
 638 
 639             if (ret != JFileChooser.APPROVE_OPTION) {
 640                 return;
 641             }
 642 
 643             File f = chooser.getSelectedFile();
 644             frame.setTitle(f.getName());
 645             Thread saver = new FileSaver(f, editor.getDocument());
 646             saver.start();
 647         }
 648     }
 649 
 650 
 651     class NewAction extends AbstractAction {
 652 
 653         NewAction() {
 654             super(newAction);
 655         }
 656 
 657         NewAction(String nm) {
 658             super(nm);
 659         }
 660 
 661         public void actionPerformed(ActionEvent e) {
 662             Document oldDoc = getEditor().getDocument();
 663             if (oldDoc != null) {
 664                 oldDoc.removeUndoableEditListener(undoHandler);
 665             }
 666             getEditor().setDocument(new PlainDocument());
 667             getEditor().getDocument().addUndoableEditListener(undoHandler);
 668             resetUndoManager();
 669             getFrame().setTitle(resources.getString("Title"));
 670             revalidate();
 671         }
 672     }
 673 
 674 
 675     /**
 676      * Really lame implementation of an exit command
 677      */
 678     class ExitAction extends AbstractAction {
 679 
 680         ExitAction() {
 681             super(exitAction);
 682         }
 683 
 684         public void actionPerformed(ActionEvent e) {
 685             System.exit(0);
 686         }
 687     }
 688 
 689 
 690     /**
 691      * Action that brings up a JFrame with a JTree showing the structure
 692      * of the document.
 693      */
 694     class ShowElementTreeAction extends AbstractAction {
 695 
 696         ShowElementTreeAction() {
 697             super(showElementTreeAction);
 698         }
 699 
 700         public void actionPerformed(ActionEvent e) {
 701             if (elementTreeFrame == null) {
 702                 // Create a frame containing an instance of
 703                 // ElementTreePanel.
 704                 try {
 705                     String title = resources.getString("ElementTreeFrameTitle");
 706                     elementTreeFrame = new JFrame(title);
 707                 } catch (MissingResourceException mre) {
 708                     elementTreeFrame = new JFrame();
 709                 }
 710 
 711                 elementTreeFrame.addWindowListener(new WindowAdapter() {
 712 
 713                     @Override
 714                     public void windowClosing(WindowEvent weeee) {
 715                         elementTreeFrame.setVisible(false);
 716                     }
 717                 });
 718                 Container fContentPane = elementTreeFrame.getContentPane();
 719 
 720                 fContentPane.setLayout(new BorderLayout());
 721                 elementTreePanel = new ElementTreePanel(getEditor());
 722                 fContentPane.add(elementTreePanel);
 723                 elementTreeFrame.pack();
 724             }
 725             elementTreeFrame.setVisible(true);
 726         }
 727     }
 728 
 729 
 730     /**
 731      * Thread to load a file into the text storage model
 732      */
 733     class FileLoader extends Thread {
 734 
 735         FileLoader(File f, Document doc) {
 736             setPriority(4);
 737             this.f = f;
 738             this.doc = doc;
 739         }
 740 
 741         @Override
 742         public void run() {
 743             try {
 744                 // initialize the statusbar
 745                 status.removeAll();
 746                 JProgressBar progress = new JProgressBar();
 747                 progress.setMinimum(0);
 748                 progress.setMaximum((int) f.length());
 749                 status.add(progress);
 750                 status.revalidate();
 751 
 752                 // try to start reading
 753                 Reader in = new FileReader(f);
 754                 char[] buff = new char[4096];
 755                 int nch;
 756                 while ((nch = in.read(buff, 0, buff.length)) != -1) {
 757                     doc.insertString(doc.getLength(), new String(buff, 0, nch),
 758                             null);
 759                     progress.setValue(progress.getValue() + nch);
 760                 }
 761             } catch (IOException e) {
 762                 final String msg = e.getMessage();
 763                 SwingUtilities.invokeLater(new Runnable() {
 764 
 765                     public void run() {
 766                         JOptionPane.showMessageDialog(getFrame(),
 767                                 "Could not open file: " + msg,
 768                                 "Error opening file",
 769                                 JOptionPane.ERROR_MESSAGE);
 770                     }
 771                 });
 772             } catch (BadLocationException e) {
 773                 System.err.println(e.getMessage());
 774             }
 775             doc.addUndoableEditListener(undoHandler);
 776             // we are done... get rid of progressbar
 777             status.removeAll();
 778             status.revalidate();
 779 
 780             resetUndoManager();
 781 
 782             if (elementTreePanel != null) {
 783                 SwingUtilities.invokeLater(new Runnable() {
 784 
 785                     public void run() {
 786                         elementTreePanel.setEditor(getEditor());
 787                     }
 788                 });
 789             }
 790         }
 791         Document doc;
 792         File f;
 793     }
 794 
 795 
 796     /**
 797      * Thread to save a document to file
 798      */
 799     class FileSaver extends Thread {
 800 
 801         Document doc;
 802         File f;
 803 
 804         FileSaver(File f, Document doc) {
 805             setPriority(4);
 806             this.f = f;
 807             this.doc = doc;
 808         }
 809 
 810         @Override
 811         @SuppressWarnings("SleepWhileHoldingLock")
 812         public void run() {
 813             try {
 814                 // initialize the statusbar
 815                 status.removeAll();
 816                 JProgressBar progress = new JProgressBar();
 817                 progress.setMinimum(0);
 818                 progress.setMaximum(doc.getLength());
 819                 status.add(progress);
 820                 status.revalidate();
 821 
 822                 // start writing
 823                 Writer out = new FileWriter(f);
 824                 Segment text = new Segment();
 825                 text.setPartialReturn(true);
 826                 int charsLeft = doc.getLength();
 827                 int offset = 0;
 828                 while (charsLeft > 0) {
 829                     doc.getText(offset, Math.min(4096, charsLeft), text);
 830                     out.write(text.array, text.offset, text.count);
 831                     charsLeft -= text.count;
 832                     offset += text.count;
 833                     progress.setValue(offset);
 834                     try {
 835                         Thread.sleep(10);
 836                     } catch (InterruptedException e) {
 837                         Logger.getLogger(FileSaver.class.getName()).log(
 838                                 Level.SEVERE,
 839                                 null, e);
 840                     }
 841                 }
 842                 out.flush();
 843                 out.close();
 844             } catch (IOException e) {
 845                 final String msg = e.getMessage();
 846                 SwingUtilities.invokeLater(new Runnable() {
 847 
 848                     public void run() {
 849                         JOptionPane.showMessageDialog(getFrame(),
 850                                 "Could not save file: " + msg,
 851                                 "Error saving file",
 852                                 JOptionPane.ERROR_MESSAGE);
 853                     }
 854                 });
 855             } catch (BadLocationException e) {
 856                 System.err.println(e.getMessage());
 857             }
 858             // we are done... get rid of progressbar
 859             status.removeAll();
 860             status.revalidate();
 861         }
 862     }
 863 }