Path: blob/master/src/demo/share/jfc/Notepad/Notepad.java
41152 views
/*1* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.2*3* Redistribution and use in source and binary forms, with or without4* modification, are permitted provided that the following conditions5* are met:6*7* - Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer.9*10* - Redistributions in binary form must reproduce the above copyright11* notice, this list of conditions and the following disclaimer in the12* documentation and/or other materials provided with the distribution.13*14* - Neither the name of Oracle nor the names of its15* contributors may be used to endorse or promote products derived16* from this software without specific prior written permission.17*18* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS19* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,20* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR21* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR22* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,23* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,24* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR25* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF26* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING27* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS28* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.29*/3031/*32* This source code is provided to illustrate the usage of a given feature33* or technique and has been deliberately simplified. Additional steps34* required for a production-quality application, such as security checks,35* input validation and proper error handling, might not be present in36* this sample code.37*/38394041import java.awt.*;42import java.awt.event.*;43import java.beans.*;44import java.io.*;45import java.net.*;46import java.util.*;47import java.util.logging.*;48import javax.swing.*;49import javax.swing.undo.*;50import javax.swing.text.*;51import javax.swing.event.*;52import javax.swing.UIManager.LookAndFeelInfo;535455/**56* Sample application using the simple text editor component that57* supports only one font.58*59* @author Timothy Prinzing60*/61@SuppressWarnings("serial")62public class Notepad extends JPanel {6364protected static Properties properties;65private static ResourceBundle resources;66private static final String EXIT_AFTER_PAINT = "-exit";67private static boolean exitAfterFirstPaint;6869private static final String[] MENUBAR_KEYS = {"file", "edit", "debug"};70private static final String[] TOOLBAR_KEYS = {"new", "open", "save", "-", "cut", "copy", "paste"};71private static final String[] FILE_KEYS = {"new", "open", "save", "-", "exit"};72private static final String[] EDIT_KEYS = {"cut", "copy", "paste", "-", "undo", "redo"};73private static final String[] DEBUG_KEYS = {"dump", "showElementTree"};7475static {76try {77properties = new Properties();78properties.load(Notepad.class.getResourceAsStream(79"resources/NotepadSystem.properties"));80resources = ResourceBundle.getBundle("resources.Notepad",81Locale.getDefault());82} catch (MissingResourceException | IOException e) {83System.err.println("resources/Notepad.properties "84+ "or resources/NotepadSystem.properties not found");85System.exit(1);86}87}8889@Override90public void paintChildren(Graphics g) {91super.paintChildren(g);92if (exitAfterFirstPaint) {93System.exit(0);94}95}9697@SuppressWarnings("OverridableMethodCallInConstructor")98Notepad() {99super(true);100101// Trying to set Nimbus look and feel102try {103for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {104if ("Nimbus".equals(info.getName())) {105UIManager.setLookAndFeel(info.getClassName());106break;107}108}109} catch (Exception ignored) {110}111112setBorder(BorderFactory.createEtchedBorder());113setLayout(new BorderLayout());114115// create the embedded JTextComponent116editor = createEditor();117// Add this as a listener for undoable edits.118editor.getDocument().addUndoableEditListener(undoHandler);119120// install the command table121commands = new HashMap<Object, Action>();122Action[] actions = getActions();123for (Action a : actions) {124commands.put(a.getValue(Action.NAME), a);125}126127JScrollPane scroller = new JScrollPane();128JViewport port = scroller.getViewport();129port.add(editor);130131String vpFlag = getProperty("ViewportBackingStore");132if (vpFlag != null) {133Boolean bs = Boolean.valueOf(vpFlag);134port.setScrollMode(bs135? JViewport.BACKINGSTORE_SCROLL_MODE136: JViewport.BLIT_SCROLL_MODE);137}138139JPanel panel = new JPanel();140panel.setLayout(new BorderLayout());141panel.add("North", createToolbar());142panel.add("Center", scroller);143add("Center", panel);144add("South", createStatusbar());145}146147public static void main(String[] args) throws Exception {148if (args.length > 0 && args[0].equals(EXIT_AFTER_PAINT)) {149exitAfterFirstPaint = true;150}151SwingUtilities.invokeAndWait(new Runnable() {152153public void run() {154JFrame frame = new JFrame();155frame.setTitle(resources.getString("Title"));156frame.setBackground(Color.lightGray);157frame.getContentPane().setLayout(new BorderLayout());158Notepad notepad = new Notepad();159frame.getContentPane().add("Center", notepad);160frame.setJMenuBar(notepad.createMenubar());161frame.addWindowListener(new AppCloser());162frame.pack();163frame.setSize(500, 600);164frame.setVisible(true);165}166});167}168169/**170* Fetch the list of actions supported by this171* editor. It is implemented to return the list172* of actions supported by the embedded JTextComponent173* augmented with the actions defined locally.174*/175public Action[] getActions() {176return TextAction.augmentList(editor.getActions(), defaultActions);177}178179/**180* Create an editor to represent the given document.181*/182protected JTextComponent createEditor() {183JTextComponent c = new JTextArea();184c.setDragEnabled(true);185c.setFont(new Font("monospaced", Font.PLAIN, 12));186return c;187}188189/**190* Fetch the editor contained in this panel191*/192protected JTextComponent getEditor() {193return editor;194}195196197/**198* To shutdown when run as an application. This is a199* fairly lame implementation. A more self-respecting200* implementation would at least check to see if a save201* was needed.202*/203protected static final class AppCloser extends WindowAdapter {204205@Override206public void windowClosing(WindowEvent e) {207System.exit(0);208}209}210211/**212* Find the hosting frame, for the file-chooser dialog.213*/214protected Frame getFrame() {215for (Container p = getParent(); p != null; p = p.getParent()) {216if (p instanceof Frame) {217return (Frame) p;218}219}220return null;221}222223/**224* This is the hook through which all menu items are225* created.226*/227protected JMenuItem createMenuItem(String cmd) {228JMenuItem mi = new JMenuItem(getResourceString(cmd + labelSuffix));229URL url = getResource(cmd + imageSuffix);230if (url != null) {231mi.setHorizontalTextPosition(JButton.RIGHT);232mi.setIcon(new ImageIcon(url));233}234String astr = getProperty(cmd + actionSuffix);235if (astr == null) {236astr = cmd;237}238mi.setActionCommand(astr);239Action a = getAction(astr);240if (a != null) {241mi.addActionListener(a);242a.addPropertyChangeListener(createActionChangeListener(mi));243mi.setEnabled(a.isEnabled());244} else {245mi.setEnabled(false);246}247return mi;248}249250protected Action getAction(String cmd) {251return commands.get(cmd);252}253254protected String getProperty(String key) {255return properties.getProperty(key);256}257258protected String getResourceString(String nm) {259String str;260try {261str = resources.getString(nm);262} catch (MissingResourceException mre) {263str = null;264}265return str;266}267268protected URL getResource(String key) {269String name = getResourceString(key);270if (name != null) {271return this.getClass().getResource(name);272}273return null;274}275276/**277* Create a status bar278*/279protected Component createStatusbar() {280// need to do something reasonable here281status = new StatusBar();282return status;283}284285/**286* Resets the undo manager.287*/288protected void resetUndoManager() {289undo.discardAllEdits();290undoAction.update();291redoAction.update();292}293294/**295* Create the toolbar. By default this reads the296* resource file for the definition of the toolbar.297*/298private Component createToolbar() {299toolbar = new JToolBar();300for (String toolKey: getToolBarKeys()) {301if (toolKey.equals("-")) {302toolbar.add(Box.createHorizontalStrut(5));303} else {304toolbar.add(createTool(toolKey));305}306}307toolbar.add(Box.createHorizontalGlue());308return toolbar;309}310311/**312* Hook through which every toolbar item is created.313*/314protected Component createTool(String key) {315return createToolbarButton(key);316}317318/**319* Create a button to go inside of the toolbar. By default this320* will load an image resource. The image filename is relative to321* the classpath (including the '.' directory if its a part of the322* classpath), and may either be in a JAR file or a separate file.323*324* @param key The key in the resource file to serve as the basis325* of lookups.326*/327protected JButton createToolbarButton(String key) {328URL url = getResource(key + imageSuffix);329JButton b = new JButton(new ImageIcon(url)) {330331@Override332public float getAlignmentY() {333return 0.5f;334}335};336b.setRequestFocusEnabled(false);337b.setMargin(new Insets(1, 1, 1, 1));338339String astr = getProperty(key + actionSuffix);340if (astr == null) {341astr = key;342}343Action a = getAction(astr);344if (a != null) {345b.setActionCommand(astr);346b.addActionListener(a);347} else {348b.setEnabled(false);349}350351String tip = getResourceString(key + tipSuffix);352if (tip != null) {353b.setToolTipText(tip);354}355356return b;357}358359/**360* Create the menubar for the app. By default this pulls the361* definition of the menu from the associated resource file.362*/363protected JMenuBar createMenubar() {364JMenuBar mb = new JMenuBar();365for(String menuKey: getMenuBarKeys()){366JMenu m = createMenu(menuKey);367if (m != null) {368mb.add(m);369}370}371return mb;372}373374/**375* Create a menu for the app. By default this pulls the376* definition of the menu from the associated resource file.377*/378protected JMenu createMenu(String key) {379JMenu menu = new JMenu(getResourceString(key + labelSuffix));380for (String itemKey: getItemKeys(key)) {381if (itemKey.equals("-")) {382menu.addSeparator();383} else {384JMenuItem mi = createMenuItem(itemKey);385menu.add(mi);386}387}388return menu;389}390391/**392* Get keys for menus393*/394protected String[] getItemKeys(String key) {395switch (key) {396case "file":397return FILE_KEYS;398case "edit":399return EDIT_KEYS;400case "debug":401return DEBUG_KEYS;402default:403return null;404}405}406407protected String[] getMenuBarKeys() {408return MENUBAR_KEYS;409}410411protected String[] getToolBarKeys() {412return TOOLBAR_KEYS;413}414415// Yarked from JMenu, ideally this would be public.416protected PropertyChangeListener createActionChangeListener(JMenuItem b) {417return new ActionChangedListener(b);418}419420// Yarked from JMenu, ideally this would be public.421422private class ActionChangedListener implements PropertyChangeListener {423424JMenuItem menuItem;425426ActionChangedListener(JMenuItem mi) {427super();428this.menuItem = mi;429}430431public void propertyChange(PropertyChangeEvent e) {432String propertyName = e.getPropertyName();433if (e.getPropertyName().equals(Action.NAME)) {434String text = (String) e.getNewValue();435menuItem.setText(text);436} else if (propertyName.equals("enabled")) {437Boolean enabledState = (Boolean) e.getNewValue();438menuItem.setEnabled(enabledState.booleanValue());439}440}441}442private JTextComponent editor;443private Map<Object, Action> commands;444private JToolBar toolbar;445private JComponent status;446private JFrame elementTreeFrame;447protected ElementTreePanel elementTreePanel;448449/**450* Listener for the edits on the current document.451*/452protected UndoableEditListener undoHandler = new UndoHandler();453/** UndoManager that we add edits to. */454protected UndoManager undo = new UndoManager();455/**456* Suffix applied to the key used in resource file457* lookups for an image.458*/459public static final String imageSuffix = "Image";460/**461* Suffix applied to the key used in resource file462* lookups for a label.463*/464public static final String labelSuffix = "Label";465/**466* Suffix applied to the key used in resource file467* lookups for an action.468*/469public static final String actionSuffix = "Action";470/**471* Suffix applied to the key used in resource file472* lookups for tooltip text.473*/474public static final String tipSuffix = "Tooltip";475public static final String openAction = "open";476public static final String newAction = "new";477public static final String saveAction = "save";478public static final String exitAction = "exit";479public static final String showElementTreeAction = "showElementTree";480481482class UndoHandler implements UndoableEditListener {483484/**485* Messaged when the Document has created an edit, the edit is486* added to <code>undo</code>, an instance of UndoManager.487*/488public void undoableEditHappened(UndoableEditEvent e) {489undo.addEdit(e.getEdit());490undoAction.update();491redoAction.update();492}493}494495496/**497* FIXME - I'm not very useful yet498*/499class StatusBar extends JComponent {500501public StatusBar() {502super();503setLayout(new BoxLayout(this, BoxLayout.X_AXIS));504}505506@Override507public void paint(Graphics g) {508super.paint(g);509}510}511// --- action implementations -----------------------------------512private UndoAction undoAction = new UndoAction();513private RedoAction redoAction = new RedoAction();514/**515* Actions defined by the Notepad class516*/517private Action[] defaultActions = {518new NewAction(),519new OpenAction(),520new SaveAction(),521new ExitAction(),522new ShowElementTreeAction(),523undoAction,524redoAction525};526527528class UndoAction extends AbstractAction {529530public UndoAction() {531super("Undo");532setEnabled(false);533}534535public void actionPerformed(ActionEvent e) {536try {537undo.undo();538} catch (CannotUndoException ex) {539Logger.getLogger(UndoAction.class.getName()).log(Level.SEVERE,540"Unable to undo", ex);541}542update();543redoAction.update();544}545546protected void update() {547if (undo.canUndo()) {548setEnabled(true);549putValue(Action.NAME, undo.getUndoPresentationName());550} else {551setEnabled(false);552putValue(Action.NAME, "Undo");553}554}555}556557558class RedoAction extends AbstractAction {559560public RedoAction() {561super("Redo");562setEnabled(false);563}564565public void actionPerformed(ActionEvent e) {566try {567undo.redo();568} catch (CannotRedoException ex) {569Logger.getLogger(RedoAction.class.getName()).log(Level.SEVERE,570"Unable to redo", ex);571}572update();573undoAction.update();574}575576protected void update() {577if (undo.canRedo()) {578setEnabled(true);579putValue(Action.NAME, undo.getRedoPresentationName());580} else {581setEnabled(false);582putValue(Action.NAME, "Redo");583}584}585}586587588class OpenAction extends NewAction {589590OpenAction() {591super(openAction);592}593594@Override595public void actionPerformed(ActionEvent e) {596Frame frame = getFrame();597JFileChooser chooser = new JFileChooser();598int ret = chooser.showOpenDialog(frame);599600if (ret != JFileChooser.APPROVE_OPTION) {601return;602}603604File f = chooser.getSelectedFile();605if (f.isFile() && f.canRead()) {606Document oldDoc = getEditor().getDocument();607if (oldDoc != null) {608oldDoc.removeUndoableEditListener(undoHandler);609}610if (elementTreePanel != null) {611elementTreePanel.setEditor(null);612}613getEditor().setDocument(new PlainDocument());614frame.setTitle(f.getName());615Thread loader = new FileLoader(f, editor.getDocument());616loader.start();617} else {618JOptionPane.showMessageDialog(getFrame(),619"Could not open file: " + f,620"Error opening file",621JOptionPane.ERROR_MESSAGE);622}623}624}625626627class SaveAction extends AbstractAction {628629SaveAction() {630super(saveAction);631}632633public void actionPerformed(ActionEvent e) {634Frame frame = getFrame();635JFileChooser chooser = new JFileChooser();636int ret = chooser.showSaveDialog(frame);637638if (ret != JFileChooser.APPROVE_OPTION) {639return;640}641642File f = chooser.getSelectedFile();643frame.setTitle(f.getName());644Thread saver = new FileSaver(f, editor.getDocument());645saver.start();646}647}648649650class NewAction extends AbstractAction {651652NewAction() {653super(newAction);654}655656NewAction(String nm) {657super(nm);658}659660public void actionPerformed(ActionEvent e) {661Document oldDoc = getEditor().getDocument();662if (oldDoc != null) {663oldDoc.removeUndoableEditListener(undoHandler);664}665getEditor().setDocument(new PlainDocument());666getEditor().getDocument().addUndoableEditListener(undoHandler);667resetUndoManager();668getFrame().setTitle(resources.getString("Title"));669revalidate();670}671}672673674/**675* Really lame implementation of an exit command676*/677class ExitAction extends AbstractAction {678679ExitAction() {680super(exitAction);681}682683public void actionPerformed(ActionEvent e) {684System.exit(0);685}686}687688689/**690* Action that brings up a JFrame with a JTree showing the structure691* of the document.692*/693class ShowElementTreeAction extends AbstractAction {694695ShowElementTreeAction() {696super(showElementTreeAction);697}698699public void actionPerformed(ActionEvent e) {700if (elementTreeFrame == null) {701// Create a frame containing an instance of702// ElementTreePanel.703try {704String title = resources.getString("ElementTreeFrameTitle");705elementTreeFrame = new JFrame(title);706} catch (MissingResourceException mre) {707elementTreeFrame = new JFrame();708}709710elementTreeFrame.addWindowListener(new WindowAdapter() {711712@Override713public void windowClosing(WindowEvent weeee) {714elementTreeFrame.setVisible(false);715}716});717Container fContentPane = elementTreeFrame.getContentPane();718719fContentPane.setLayout(new BorderLayout());720elementTreePanel = new ElementTreePanel(getEditor());721fContentPane.add(elementTreePanel);722elementTreeFrame.pack();723}724elementTreeFrame.setVisible(true);725}726}727728729/**730* Thread to load a file into the text storage model731*/732class FileLoader extends Thread {733734FileLoader(File f, Document doc) {735setPriority(4);736this.f = f;737this.doc = doc;738}739740@Override741public void run() {742try {743// initialize the statusbar744status.removeAll();745JProgressBar progress = new JProgressBar();746progress.setMinimum(0);747progress.setMaximum((int) f.length());748status.add(progress);749status.revalidate();750751// try to start reading752Reader in = new FileReader(f);753char[] buff = new char[4096];754int nch;755while ((nch = in.read(buff, 0, buff.length)) != -1) {756doc.insertString(doc.getLength(), new String(buff, 0, nch),757null);758progress.setValue(progress.getValue() + nch);759}760} catch (IOException e) {761final String msg = e.getMessage();762SwingUtilities.invokeLater(new Runnable() {763764public void run() {765JOptionPane.showMessageDialog(getFrame(),766"Could not open file: " + msg,767"Error opening file",768JOptionPane.ERROR_MESSAGE);769}770});771} catch (BadLocationException e) {772System.err.println(e.getMessage());773}774doc.addUndoableEditListener(undoHandler);775// we are done... get rid of progressbar776status.removeAll();777status.revalidate();778779resetUndoManager();780781if (elementTreePanel != null) {782SwingUtilities.invokeLater(new Runnable() {783784public void run() {785elementTreePanel.setEditor(getEditor());786}787});788}789}790Document doc;791File f;792}793794795/**796* Thread to save a document to file797*/798class FileSaver extends Thread {799800Document doc;801File f;802803FileSaver(File f, Document doc) {804setPriority(4);805this.f = f;806this.doc = doc;807}808809@Override810@SuppressWarnings("SleepWhileHoldingLock")811public void run() {812try {813// initialize the statusbar814status.removeAll();815JProgressBar progress = new JProgressBar();816progress.setMinimum(0);817progress.setMaximum(doc.getLength());818status.add(progress);819status.revalidate();820821// start writing822Writer out = new FileWriter(f);823Segment text = new Segment();824text.setPartialReturn(true);825int charsLeft = doc.getLength();826int offset = 0;827while (charsLeft > 0) {828doc.getText(offset, Math.min(4096, charsLeft), text);829out.write(text.array, text.offset, text.count);830charsLeft -= text.count;831offset += text.count;832progress.setValue(offset);833try {834Thread.sleep(10);835} catch (InterruptedException e) {836Logger.getLogger(FileSaver.class.getName()).log(837Level.SEVERE,838null, e);839}840}841out.flush();842out.close();843} catch (IOException e) {844final String msg = e.getMessage();845SwingUtilities.invokeLater(new Runnable() {846847public void run() {848JOptionPane.showMessageDialog(getFrame(),849"Could not save file: " + msg,850"Error saving file",851JOptionPane.ERROR_MESSAGE);852}853});854} catch (BadLocationException e) {855System.err.println(e.getMessage());856}857// we are done... get rid of progressbar858status.removeAll();859status.revalidate();860}861}862}863864865