src/demo/share/jfc/Notepad/Notepad.java
changeset 47216 71c04702a3d5
parent 32869 edd4354e4a09
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jfc/Notepad/Notepad.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,863 @@
+/*
+ * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ *   - Neither the name of Oracle nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This source code is provided to illustrate the usage of a given feature
+ * or technique and has been deliberately simplified. Additional steps
+ * required for a production-quality application, such as security checks,
+ * input validation and proper error handling, might not be present in
+ * this sample code.
+ */
+
+
+
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.*;
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.logging.*;
+import javax.swing.*;
+import javax.swing.undo.*;
+import javax.swing.text.*;
+import javax.swing.event.*;
+import javax.swing.UIManager.LookAndFeelInfo;
+
+
+/**
+ * Sample application using the simple text editor component that
+ * supports only one font.
+ *
+ * @author  Timothy Prinzing
+ */
+@SuppressWarnings("serial")
+class Notepad extends JPanel {
+
+    protected static Properties properties;
+    private static ResourceBundle resources;
+    private static final String EXIT_AFTER_PAINT = "-exit";
+    private static boolean exitAfterFirstPaint;
+
+    private static final String[] MENUBAR_KEYS = {"file", "edit", "debug"};
+    private static final String[] TOOLBAR_KEYS = {"new", "open", "save", "-", "cut", "copy", "paste"};
+    private static final String[] FILE_KEYS = {"new", "open", "save", "-", "exit"};
+    private static final String[] EDIT_KEYS = {"cut", "copy", "paste", "-", "undo", "redo"};
+    private static final String[] DEBUG_KEYS = {"dump", "showElementTree"};
+
+    static {
+        try {
+            properties = new Properties();
+            properties.load(Notepad.class.getResourceAsStream(
+                    "resources/NotepadSystem.properties"));
+            resources = ResourceBundle.getBundle("resources.Notepad",
+                    Locale.getDefault());
+        } catch (MissingResourceException | IOException  e) {
+            System.err.println("resources/Notepad.properties "
+                    + "or resources/NotepadSystem.properties not found");
+            System.exit(1);
+        }
+    }
+
+    @Override
+    public void paintChildren(Graphics g) {
+        super.paintChildren(g);
+        if (exitAfterFirstPaint) {
+            System.exit(0);
+        }
+    }
+
+    @SuppressWarnings("OverridableMethodCallInConstructor")
+    Notepad() {
+        super(true);
+
+        // Trying to set Nimbus look and feel
+        try {
+            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
+                if ("Nimbus".equals(info.getName())) {
+                    UIManager.setLookAndFeel(info.getClassName());
+                    break;
+                }
+            }
+        } catch (Exception ignored) {
+        }
+
+        setBorder(BorderFactory.createEtchedBorder());
+        setLayout(new BorderLayout());
+
+        // create the embedded JTextComponent
+        editor = createEditor();
+        // Add this as a listener for undoable edits.
+        editor.getDocument().addUndoableEditListener(undoHandler);
+
+        // install the command table
+        commands = new HashMap<Object, Action>();
+        Action[] actions = getActions();
+        for (Action a : actions) {
+            commands.put(a.getValue(Action.NAME), a);
+        }
+
+        JScrollPane scroller = new JScrollPane();
+        JViewport port = scroller.getViewport();
+        port.add(editor);
+
+        String vpFlag = getProperty("ViewportBackingStore");
+        if (vpFlag != null) {
+            Boolean bs = Boolean.valueOf(vpFlag);
+            port.setScrollMode(bs
+                    ? JViewport.BACKINGSTORE_SCROLL_MODE
+                    : JViewport.BLIT_SCROLL_MODE);
+        }
+
+        JPanel panel = new JPanel();
+        panel.setLayout(new BorderLayout());
+        panel.add("North", createToolbar());
+        panel.add("Center", scroller);
+        add("Center", panel);
+        add("South", createStatusbar());
+    }
+
+    public static void main(String[] args) throws Exception {
+        if (args.length > 0 && args[0].equals(EXIT_AFTER_PAINT)) {
+            exitAfterFirstPaint = true;
+        }
+        SwingUtilities.invokeAndWait(new Runnable() {
+
+            public void run() {
+                JFrame frame = new JFrame();
+                frame.setTitle(resources.getString("Title"));
+                frame.setBackground(Color.lightGray);
+                frame.getContentPane().setLayout(new BorderLayout());
+                Notepad notepad = new Notepad();
+                frame.getContentPane().add("Center", notepad);
+                frame.setJMenuBar(notepad.createMenubar());
+                frame.addWindowListener(new AppCloser());
+                frame.pack();
+                frame.setSize(500, 600);
+                frame.setVisible(true);
+            }
+        });
+    }
+
+    /**
+     * Fetch the list of actions supported by this
+     * editor.  It is implemented to return the list
+     * of actions supported by the embedded JTextComponent
+     * augmented with the actions defined locally.
+     */
+    public Action[] getActions() {
+        return TextAction.augmentList(editor.getActions(), defaultActions);
+    }
+
+    /**
+     * Create an editor to represent the given document.
+     */
+    protected JTextComponent createEditor() {
+        JTextComponent c = new JTextArea();
+        c.setDragEnabled(true);
+        c.setFont(new Font("monospaced", Font.PLAIN, 12));
+        return c;
+    }
+
+    /**
+     * Fetch the editor contained in this panel
+     */
+    protected JTextComponent getEditor() {
+        return editor;
+    }
+
+
+    /**
+     * To shutdown when run as an application.  This is a
+     * fairly lame implementation.   A more self-respecting
+     * implementation would at least check to see if a save
+     * was needed.
+     */
+    protected static final class AppCloser extends WindowAdapter {
+
+        @Override
+        public void windowClosing(WindowEvent e) {
+            System.exit(0);
+        }
+    }
+
+    /**
+     * Find the hosting frame, for the file-chooser dialog.
+     */
+    protected Frame getFrame() {
+        for (Container p = getParent(); p != null; p = p.getParent()) {
+            if (p instanceof Frame) {
+                return (Frame) p;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * This is the hook through which all menu items are
+     * created.
+     */
+    protected JMenuItem createMenuItem(String cmd) {
+        JMenuItem mi = new JMenuItem(getResourceString(cmd + labelSuffix));
+        URL url = getResource(cmd + imageSuffix);
+        if (url != null) {
+            mi.setHorizontalTextPosition(JButton.RIGHT);
+            mi.setIcon(new ImageIcon(url));
+        }
+        String astr = getProperty(cmd + actionSuffix);
+        if (astr == null) {
+            astr = cmd;
+        }
+        mi.setActionCommand(astr);
+        Action a = getAction(astr);
+        if (a != null) {
+            mi.addActionListener(a);
+            a.addPropertyChangeListener(createActionChangeListener(mi));
+            mi.setEnabled(a.isEnabled());
+        } else {
+            mi.setEnabled(false);
+        }
+        return mi;
+    }
+
+    protected Action getAction(String cmd) {
+        return commands.get(cmd);
+    }
+
+    protected String getProperty(String key) {
+        return properties.getProperty(key);
+    }
+
+    protected String getResourceString(String nm) {
+        String str;
+        try {
+            str = resources.getString(nm);
+        } catch (MissingResourceException mre) {
+            str = null;
+        }
+        return str;
+    }
+
+    protected URL getResource(String key) {
+        String name = getResourceString(key);
+        if (name != null) {
+            return this.getClass().getResource(name);
+        }
+        return null;
+    }
+
+    /**
+     * Create a status bar
+     */
+    protected Component createStatusbar() {
+        // need to do something reasonable here
+        status = new StatusBar();
+        return status;
+    }
+
+    /**
+     * Resets the undo manager.
+     */
+    protected void resetUndoManager() {
+        undo.discardAllEdits();
+        undoAction.update();
+        redoAction.update();
+    }
+
+    /**
+     * Create the toolbar.  By default this reads the
+     * resource file for the definition of the toolbar.
+     */
+    private Component createToolbar() {
+        toolbar = new JToolBar();
+        for (String toolKey: getToolBarKeys()) {
+            if (toolKey.equals("-")) {
+                toolbar.add(Box.createHorizontalStrut(5));
+            } else {
+                toolbar.add(createTool(toolKey));
+            }
+        }
+        toolbar.add(Box.createHorizontalGlue());
+        return toolbar;
+    }
+
+    /**
+     * Hook through which every toolbar item is created.
+     */
+    protected Component createTool(String key) {
+        return createToolbarButton(key);
+    }
+
+    /**
+     * Create a button to go inside of the toolbar.  By default this
+     * will load an image resource.  The image filename is relative to
+     * the classpath (including the '.' directory if its a part of the
+     * classpath), and may either be in a JAR file or a separate file.
+     *
+     * @param key The key in the resource file to serve as the basis
+     *  of lookups.
+     */
+    protected JButton createToolbarButton(String key) {
+        URL url = getResource(key + imageSuffix);
+        JButton b = new JButton(new ImageIcon(url)) {
+
+            @Override
+            public float getAlignmentY() {
+                return 0.5f;
+            }
+        };
+        b.setRequestFocusEnabled(false);
+        b.setMargin(new Insets(1, 1, 1, 1));
+
+        String astr = getProperty(key + actionSuffix);
+        if (astr == null) {
+            astr = key;
+        }
+        Action a = getAction(astr);
+        if (a != null) {
+            b.setActionCommand(astr);
+            b.addActionListener(a);
+        } else {
+            b.setEnabled(false);
+        }
+
+        String tip = getResourceString(key + tipSuffix);
+        if (tip != null) {
+            b.setToolTipText(tip);
+        }
+
+        return b;
+    }
+
+    /**
+     * Create the menubar for the app.  By default this pulls the
+     * definition of the menu from the associated resource file.
+     */
+    protected JMenuBar createMenubar() {
+        JMenuBar mb = new JMenuBar();
+        for(String menuKey: getMenuBarKeys()){
+            JMenu m = createMenu(menuKey);
+            if (m != null) {
+                mb.add(m);
+            }
+        }
+        return mb;
+    }
+
+    /**
+     * Create a menu for the app.  By default this pulls the
+     * definition of the menu from the associated resource file.
+     */
+    protected JMenu createMenu(String key) {
+        JMenu menu = new JMenu(getResourceString(key + labelSuffix));
+        for (String itemKey: getItemKeys(key)) {
+            if (itemKey.equals("-")) {
+                menu.addSeparator();
+            } else {
+                JMenuItem mi = createMenuItem(itemKey);
+                menu.add(mi);
+            }
+        }
+        return menu;
+    }
+
+    /**
+     *  Get keys for menus
+     */
+    protected String[] getItemKeys(String key) {
+        switch (key) {
+            case "file":
+                return FILE_KEYS;
+            case "edit":
+                return EDIT_KEYS;
+            case "debug":
+                return DEBUG_KEYS;
+            default:
+                return null;
+        }
+    }
+
+    protected String[] getMenuBarKeys() {
+        return MENUBAR_KEYS;
+    }
+
+    protected String[] getToolBarKeys() {
+        return TOOLBAR_KEYS;
+    }
+
+    // Yarked from JMenu, ideally this would be public.
+    protected PropertyChangeListener createActionChangeListener(JMenuItem b) {
+        return new ActionChangedListener(b);
+    }
+
+    // Yarked from JMenu, ideally this would be public.
+
+    private class ActionChangedListener implements PropertyChangeListener {
+
+        JMenuItem menuItem;
+
+        ActionChangedListener(JMenuItem mi) {
+            super();
+            this.menuItem = mi;
+        }
+
+        public void propertyChange(PropertyChangeEvent e) {
+            String propertyName = e.getPropertyName();
+            if (e.getPropertyName().equals(Action.NAME)) {
+                String text = (String) e.getNewValue();
+                menuItem.setText(text);
+            } else if (propertyName.equals("enabled")) {
+                Boolean enabledState = (Boolean) e.getNewValue();
+                menuItem.setEnabled(enabledState.booleanValue());
+            }
+        }
+    }
+    private JTextComponent editor;
+    private Map<Object, Action> commands;
+    private JToolBar toolbar;
+    private JComponent status;
+    private JFrame elementTreeFrame;
+    protected ElementTreePanel elementTreePanel;
+
+    /**
+     * Listener for the edits on the current document.
+     */
+    protected UndoableEditListener undoHandler = new UndoHandler();
+    /** UndoManager that we add edits to. */
+    protected UndoManager undo = new UndoManager();
+    /**
+     * Suffix applied to the key used in resource file
+     * lookups for an image.
+     */
+    public static final String imageSuffix = "Image";
+    /**
+     * Suffix applied to the key used in resource file
+     * lookups for a label.
+     */
+    public static final String labelSuffix = "Label";
+    /**
+     * Suffix applied to the key used in resource file
+     * lookups for an action.
+     */
+    public static final String actionSuffix = "Action";
+    /**
+     * Suffix applied to the key used in resource file
+     * lookups for tooltip text.
+     */
+    public static final String tipSuffix = "Tooltip";
+    public static final String openAction = "open";
+    public static final String newAction = "new";
+    public static final String saveAction = "save";
+    public static final String exitAction = "exit";
+    public static final String showElementTreeAction = "showElementTree";
+
+
+    class UndoHandler implements UndoableEditListener {
+
+        /**
+         * Messaged when the Document has created an edit, the edit is
+         * added to <code>undo</code>, an instance of UndoManager.
+         */
+        public void undoableEditHappened(UndoableEditEvent e) {
+            undo.addEdit(e.getEdit());
+            undoAction.update();
+            redoAction.update();
+        }
+    }
+
+
+    /**
+     * FIXME - I'm not very useful yet
+     */
+    class StatusBar extends JComponent {
+
+        public StatusBar() {
+            super();
+            setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+        }
+
+        @Override
+        public void paint(Graphics g) {
+            super.paint(g);
+        }
+    }
+    // --- action implementations -----------------------------------
+    private UndoAction undoAction = new UndoAction();
+    private RedoAction redoAction = new RedoAction();
+    /**
+     * Actions defined by the Notepad class
+     */
+    private Action[] defaultActions = {
+        new NewAction(),
+        new OpenAction(),
+        new SaveAction(),
+        new ExitAction(),
+        new ShowElementTreeAction(),
+        undoAction,
+        redoAction
+    };
+
+
+    class UndoAction extends AbstractAction {
+
+        public UndoAction() {
+            super("Undo");
+            setEnabled(false);
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            try {
+                undo.undo();
+            } catch (CannotUndoException ex) {
+                Logger.getLogger(UndoAction.class.getName()).log(Level.SEVERE,
+                        "Unable to undo", ex);
+            }
+            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) {
+                Logger.getLogger(RedoAction.class.getName()).log(Level.SEVERE,
+                        "Unable to redo", ex);
+            }
+            update();
+            undoAction.update();
+        }
+
+        protected void update() {
+            if (undo.canRedo()) {
+                setEnabled(true);
+                putValue(Action.NAME, undo.getRedoPresentationName());
+            } else {
+                setEnabled(false);
+                putValue(Action.NAME, "Redo");
+            }
+        }
+    }
+
+
+    class OpenAction extends NewAction {
+
+        OpenAction() {
+            super(openAction);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            Frame frame = getFrame();
+            JFileChooser chooser = new JFileChooser();
+            int ret = chooser.showOpenDialog(frame);
+
+            if (ret != JFileChooser.APPROVE_OPTION) {
+                return;
+            }
+
+            File f = chooser.getSelectedFile();
+            if (f.isFile() && f.canRead()) {
+                Document oldDoc = getEditor().getDocument();
+                if (oldDoc != null) {
+                    oldDoc.removeUndoableEditListener(undoHandler);
+                }
+                if (elementTreePanel != null) {
+                    elementTreePanel.setEditor(null);
+                }
+                getEditor().setDocument(new PlainDocument());
+                frame.setTitle(f.getName());
+                Thread loader = new FileLoader(f, editor.getDocument());
+                loader.start();
+            } else {
+                JOptionPane.showMessageDialog(getFrame(),
+                        "Could not open file: " + f,
+                        "Error opening file",
+                        JOptionPane.ERROR_MESSAGE);
+            }
+        }
+    }
+
+
+    class SaveAction extends AbstractAction {
+
+        SaveAction() {
+            super(saveAction);
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            Frame frame = getFrame();
+            JFileChooser chooser = new JFileChooser();
+            int ret = chooser.showSaveDialog(frame);
+
+            if (ret != JFileChooser.APPROVE_OPTION) {
+                return;
+            }
+
+            File f = chooser.getSelectedFile();
+            frame.setTitle(f.getName());
+            Thread saver = new FileSaver(f, editor.getDocument());
+            saver.start();
+        }
+    }
+
+
+    class NewAction extends AbstractAction {
+
+        NewAction() {
+            super(newAction);
+        }
+
+        NewAction(String nm) {
+            super(nm);
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            Document oldDoc = getEditor().getDocument();
+            if (oldDoc != null) {
+                oldDoc.removeUndoableEditListener(undoHandler);
+            }
+            getEditor().setDocument(new PlainDocument());
+            getEditor().getDocument().addUndoableEditListener(undoHandler);
+            resetUndoManager();
+            getFrame().setTitle(resources.getString("Title"));
+            revalidate();
+        }
+    }
+
+
+    /**
+     * Really lame implementation of an exit command
+     */
+    class ExitAction extends AbstractAction {
+
+        ExitAction() {
+            super(exitAction);
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            System.exit(0);
+        }
+    }
+
+
+    /**
+     * Action that brings up a JFrame with a JTree showing the structure
+     * of the document.
+     */
+    class ShowElementTreeAction extends AbstractAction {
+
+        ShowElementTreeAction() {
+            super(showElementTreeAction);
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            if (elementTreeFrame == null) {
+                // Create a frame containing an instance of
+                // ElementTreePanel.
+                try {
+                    String title = resources.getString("ElementTreeFrameTitle");
+                    elementTreeFrame = new JFrame(title);
+                } catch (MissingResourceException mre) {
+                    elementTreeFrame = new JFrame();
+                }
+
+                elementTreeFrame.addWindowListener(new WindowAdapter() {
+
+                    @Override
+                    public void windowClosing(WindowEvent weeee) {
+                        elementTreeFrame.setVisible(false);
+                    }
+                });
+                Container fContentPane = elementTreeFrame.getContentPane();
+
+                fContentPane.setLayout(new BorderLayout());
+                elementTreePanel = new ElementTreePanel(getEditor());
+                fContentPane.add(elementTreePanel);
+                elementTreeFrame.pack();
+            }
+            elementTreeFrame.setVisible(true);
+        }
+    }
+
+
+    /**
+     * Thread to load a file into the text storage model
+     */
+    class FileLoader extends Thread {
+
+        FileLoader(File f, Document doc) {
+            setPriority(4);
+            this.f = f;
+            this.doc = doc;
+        }
+
+        @Override
+        public void run() {
+            try {
+                // initialize the statusbar
+                status.removeAll();
+                JProgressBar progress = new JProgressBar();
+                progress.setMinimum(0);
+                progress.setMaximum((int) f.length());
+                status.add(progress);
+                status.revalidate();
+
+                // try to start reading
+                Reader in = new FileReader(f);
+                char[] buff = new char[4096];
+                int nch;
+                while ((nch = in.read(buff, 0, buff.length)) != -1) {
+                    doc.insertString(doc.getLength(), new String(buff, 0, nch),
+                            null);
+                    progress.setValue(progress.getValue() + nch);
+                }
+            } catch (IOException e) {
+                final String msg = e.getMessage();
+                SwingUtilities.invokeLater(new Runnable() {
+
+                    public void run() {
+                        JOptionPane.showMessageDialog(getFrame(),
+                                "Could not open file: " + msg,
+                                "Error opening file",
+                                JOptionPane.ERROR_MESSAGE);
+                    }
+                });
+            } catch (BadLocationException e) {
+                System.err.println(e.getMessage());
+            }
+            doc.addUndoableEditListener(undoHandler);
+            // we are done... get rid of progressbar
+            status.removeAll();
+            status.revalidate();
+
+            resetUndoManager();
+
+            if (elementTreePanel != null) {
+                SwingUtilities.invokeLater(new Runnable() {
+
+                    public void run() {
+                        elementTreePanel.setEditor(getEditor());
+                    }
+                });
+            }
+        }
+        Document doc;
+        File f;
+    }
+
+
+    /**
+     * Thread to save a document to file
+     */
+    class FileSaver extends Thread {
+
+        Document doc;
+        File f;
+
+        FileSaver(File f, Document doc) {
+            setPriority(4);
+            this.f = f;
+            this.doc = doc;
+        }
+
+        @Override
+        @SuppressWarnings("SleepWhileHoldingLock")
+        public void run() {
+            try {
+                // initialize the statusbar
+                status.removeAll();
+                JProgressBar progress = new JProgressBar();
+                progress.setMinimum(0);
+                progress.setMaximum(doc.getLength());
+                status.add(progress);
+                status.revalidate();
+
+                // start writing
+                Writer out = new FileWriter(f);
+                Segment text = new Segment();
+                text.setPartialReturn(true);
+                int charsLeft = doc.getLength();
+                int offset = 0;
+                while (charsLeft > 0) {
+                    doc.getText(offset, Math.min(4096, charsLeft), text);
+                    out.write(text.array, text.offset, text.count);
+                    charsLeft -= text.count;
+                    offset += text.count;
+                    progress.setValue(offset);
+                    try {
+                        Thread.sleep(10);
+                    } catch (InterruptedException e) {
+                        Logger.getLogger(FileSaver.class.getName()).log(
+                                Level.SEVERE,
+                                null, e);
+                    }
+                }
+                out.flush();
+                out.close();
+            } catch (IOException e) {
+                final String msg = e.getMessage();
+                SwingUtilities.invokeLater(new Runnable() {
+
+                    public void run() {
+                        JOptionPane.showMessageDialog(getFrame(),
+                                "Could not save file: " + msg,
+                                "Error saving file",
+                                JOptionPane.ERROR_MESSAGE);
+                    }
+                });
+            } catch (BadLocationException e) {
+                System.err.println(e.getMessage());
+            }
+            // we are done... get rid of progressbar
+            status.removeAll();
+            status.revalidate();
+        }
+    }
+}