/* * Copyright 1997-2003 Sun Microsystems, Inc. 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 Sun Microsystems 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. */ import java.awt.*; import java.awt.event.*; import java.beans.*; import java.io.*; import java.net.URL; import java.util.*; import javax.swing.text.*; import javax.swing.undo.*; import javax.swing.event.*; import javax.swing.*; /** * Sample application using the simple text editor component that * supports only one font. * * @author Timothy Prinzing */ class Notepad extends JPanel { private static ResourceBundle resources; private final static String EXIT_AFTER_PAINT = new String("-exit"); private static boolean exitAfterFirstPaint; static { try { resources = ResourceBundle.getBundle("resources.Notepad", Locale.getDefault()); } catch (MissingResourceException mre) { System.err.println("resources/Notepad.properties not found"); System.exit(1); } } public void paintChildren(Graphics g) { super.paintChildren(g); if (exitAfterFirstPaint) { System.exit(0); } } Notepad() { super(true); // Force SwingSet to come up in the Cross Platform L&F try { UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); // If you want the System L&F instead, comment out the above line and // uncomment the following: // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception exc) { System.err.println("Error loading L&F: " + exc); } 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 Hashtable(); Action[] actions = getActions(); for (int i = 0; i < actions.length; i++) { Action a = actions[i]; //commands.put(a.getText(Action.NAME), a); commands.put(a.getValue(Action.NAME), a); } JScrollPane scroller = new JScrollPane(); JViewport port = scroller.getViewport(); port.add(editor); try { String vpFlag = resources.getString("ViewportBackingStore"); Boolean bs = Boolean.valueOf(vpFlag); port.setBackingStoreEnabled(bs.booleanValue()); } catch (MissingResourceException mre) { // just use the viewport default } menuItems = new Hashtable(); 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) { try { String vers = System.getProperty("java.version"); if (vers.compareTo("1.1.2") < 0) { System.out.println("!!!WARNING: Swing must be run with a " + "1.1.2 or higher version VM!!!"); } if (args.length > 0 && args[0].equals(EXIT_AFTER_PAINT)) { exitAfterFirstPaint = true; } 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.show(); } catch (Throwable t) { System.out.println("uncaught exception: " + t); t.printStackTrace(); } } /** * 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 { 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. It registers the result with the menuitem * hashtable so that it can be fetched with getMenuItem(). * @see #getMenuItem */ 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 = getResourceString(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); } menuItems.put(cmd, mi); return mi; } /** * Fetch the menu item that was created for the given * command. * @param cmd Name of the action. * @returns item created for the given command or null * if one wasn't created. */ protected JMenuItem getMenuItem(String cmd) { return (JMenuItem) menuItems.get(cmd); } protected Action getAction(String cmd) { return (Action) commands.get(cmd); } 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) { URL url = this.getClass().getResource(name); return url; } return null; } protected Container getToolbar() { return toolbar; } protected JMenuBar getMenubar() { return menubar; } /** * 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(); String[] toolKeys = tokenize(getResourceString("toolbar")); for (int i = 0; i < toolKeys.length; i++) { if (toolKeys[i].equals("-")) { toolbar.add(Box.createHorizontalStrut(5)); } else { toolbar.add(createTool(toolKeys[i])); } } 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)) { public float getAlignmentY() { return 0.5f; } }; b.setRequestFocusEnabled(false); b.setMargin(new Insets(1,1,1,1)); String astr = getResourceString(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; } /** * Take the given string and chop it up into a series * of strings on whitespace boundaries. This is useful * for trying to get an array of strings out of the * resource file. */ protected String[] tokenize(String input) { Vector v = new Vector(); StringTokenizer t = new StringTokenizer(input); String cmd[]; while (t.hasMoreTokens()) v.addElement(t.nextToken()); cmd = new String[v.size()]; for (int i = 0; i < cmd.length; i++) cmd[i] = (String) v.elementAt(i); return cmd; } /** * Create the menubar for the app. By default this pulls the * definition of the menu from the associated resource file. */ protected JMenuBar createMenubar() { JMenuItem mi; JMenuBar mb = new JMenuBar(); String[] menuKeys = tokenize(getResourceString("menubar")); for (int i = 0; i < menuKeys.length; i++) { JMenu m = createMenu(menuKeys[i]); if (m != null) { mb.add(m); } } this.menubar = mb; 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) { String[] itemKeys = tokenize(getResourceString(key)); JMenu menu = new JMenu(getResourceString(key + "Label")); for (int i = 0; i < itemKeys.length; i++) { if (itemKeys[i].equals("-")) { menu.addSeparator(); } else { JMenuItem mi = createMenuItem(itemKeys[i]); menu.add(mi); } } return menu; } // 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 Hashtable commands; private Hashtable menuItems; private JMenuBar menubar; private JToolBar toolbar; private JComponent status; private JFrame elementTreeFrame; protected ElementTreePanel elementTreePanel; protected FileDialog fileDialog; /** * 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)); } 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) { System.out.println("Unable to undo: " + ex); ex.printStackTrace(); } update(); redoAction.update(); } protected void update() { if(undo.canUndo()) { setEnabled(true); putValue(Action.NAME, undo.getUndoPresentationName()); } else { setEnabled(false); putValue(Action.NAME, "Undo"); } } } class RedoAction extends AbstractAction { public RedoAction() { super("Redo"); setEnabled(false); } public void actionPerformed(ActionEvent e) { try { undo.redo(); } catch (CannotRedoException ex) { System.out.println("Unable to redo: " + ex); ex.printStackTrace(); } update(); undoAction.update(); } protected void update() { if(undo.canRedo()) { setEnabled(true); putValue(Action.NAME, undo.getRedoPresentationName()); } else { setEnabled(false); putValue(Action.NAME, "Redo"); } } } class OpenAction extends NewAction { OpenAction() { super(openAction); } 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); } ShowElementTreeAction(String nm) { super(nm); } 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() { 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.show(); } } /** * 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; } 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; } public void run() { try { // initialize the statusbar status.removeAll(); JProgressBar progress = new JProgressBar(); progress.setMinimum(0); progress.setMaximum((int) 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) { e.printStackTrace(); } } 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(); } } }