/* * 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 final static 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(); } } }