Tiny Application Java

/*
 * @(#)Notepad.java  1.31 05/11/17
 * 
 * Copyright (c) 2006 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:
 * 
 * -Redistribution of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 * 
 * -Redistribution 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, Inc. or the names of contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL 
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST 
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, 
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY 
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, 
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * You acknowledge that this software is not designed, licensed or intended
 * for use in the design, construction, operation or maintenance of any
 * nuclear facility.
 */
/*
 * @(#)Notepad.java  1.31 05/11/17
 */
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
 * @version 1.31 11/17/05 
 */
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 undo, 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();
  }
    }
}
/*
 * @(#)ElementTreePanel.java  1.17 05/11/17
 * 
 * Copyright (c) 2006 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:
 * 
 * -Redistribution of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 * 
 * -Redistribution 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, Inc. or the names of contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL 
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST 
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, 
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY 
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, 
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * You acknowledge that this software is not designed, licensed or intended
 * for use in the design, construction, operation or maintenance of any
 * nuclear facility.
 */
/*
 * @(#)ElementTreePanel.java  1.17 05/11/17
 */
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.tree.*;
import javax.swing.undo.*;
import java.awt.*;
import java.beans.*;
import java.util.*;
/**
 * Displays a tree showing all the elements in a text Document. Selecting
 * a node will result in reseting the selection of the JTextComponent.
 * This also becomes a CaretListener to know when the selection has changed
 * in the text to update the selected item in the tree.
 *
 * @author Scott Violet
 * @version 1.17 11/17/05
 */
public class ElementTreePanel extends JPanel implements CaretListener, DocumentListener, PropertyChangeListener, TreeSelectionListener {
    /** Tree showing the documents element structure. */
    protected JTree             tree;
    /** Text component showing elemenst for. */
    protected JTextComponent    editor;
    /** Model for the tree. */
    protected ElementTreeModel  treeModel;
    /** Set to true when updatin the selection. */
    protected boolean           updatingSelection;
    public ElementTreePanel(JTextComponent editor) {
  this.editor = editor;
  Document document = editor.getDocument();
  // Create the tree.
  treeModel = new ElementTreeModel(document);
  tree = new JTree(treeModel) {
      public String convertValueToText(Object value, boolean selected,
               boolean expanded, boolean leaf,
               int row, boolean hasFocus) {
    // Should only happen for the root
    if(!(value instanceof Element))
        return value.toString();
    Element        e = (Element)value;
    AttributeSet   as = e.getAttributes().copyAttributes();
    String         asString;
    if(as != null) {
        StringBuffer       retBuffer = new StringBuffer("[");
        Enumeration        names = as.getAttributeNames();
        while(names.hasMoreElements()) {
      Object        nextName = names.nextElement();
      if(nextName != StyleConstants.ResolveAttribute) {
          retBuffer.append(" ");
          retBuffer.append(nextName);
          retBuffer.append("=");
          retBuffer.append(as.getAttribute(nextName));
      }
        }
        retBuffer.append(" ]");
        asString = retBuffer.toString();
    }
    else
        asString = "[ ]";
    if(e.isLeaf())
        return e.getName() + " [" + e.getStartOffset() +
      ", " + e.getEndOffset() +"] Attributes: " + asString;
    return e.getName() + " [" + e.getStartOffset() +
        ", " + e.getEndOffset() + "] Attributes: " +
             asString;
      }
  };
  tree.addTreeSelectionListener(this);
  tree.setDragEnabled(true);
  // Don't show the root, it is fake.
  tree.setRootVisible(false);
  // Since the display value of every node after the insertion point
  // changes every time the text changes and we don't generate a change
  // event for all those nodes the display value can become off.
  // This can be seen as '...' instead of the complete string value.
  // This is a temporary workaround, increase the needed size by 15,
  // hoping that will be enough.
  tree.setCellRenderer(new DefaultTreeCellRenderer() {
      public Dimension getPreferredSize() {
    Dimension retValue = super.getPreferredSize();
    if(retValue != null)
        retValue.width += 15;
    return retValue;
      }
  });
  // become a listener on the document to update the tree.
  document.addDocumentListener(this);
  // become a PropertyChangeListener to know when the Document has
  // changed.
  editor.addPropertyChangeListener(this);
  // Become a CaretListener
  editor.addCaretListener(this);
  // configure the panel and frame containing it.
  setLayout(new BorderLayout());
  add(new JScrollPane(tree), BorderLayout.CENTER);
  // Add a label above tree to describe what is being shown
  JLabel     label = new JLabel("Elements that make up the current document", SwingConstants.CENTER);
  label.setFont(new Font("Dialog", Font.BOLD, 14));
  add(label, BorderLayout.NORTH);
  setPreferredSize(new Dimension(400, 400));
    }
    /**
     * Resets the JTextComponent to editor. This will update
     * the tree accordingly.
     */
    public void setEditor(JTextComponent editor) {
  if (this.editor == editor) {
      return;
  }
  if (this.editor != null) {
      Document      oldDoc = this.editor.getDocument();
      oldDoc.removeDocumentListener(this);
      this.editor.removePropertyChangeListener(this);
      this.editor.removeCaretListener(this);
  }
  this.editor = editor;
  if (editor == null) {
      treeModel = null;
      tree.setModel(null);
  }
  else {
      Document   newDoc = editor.getDocument();
      newDoc.addDocumentListener(this);
      editor.addPropertyChangeListener(this);
      editor.addCaretListener(this);
      treeModel = new ElementTreeModel(newDoc);
      tree.setModel(treeModel);
  }
    }
    // PropertyChangeListener
    /**
     * Invoked when a property changes. We are only interested in when the
     * Document changes to reset the DocumentListener.
     */
    public void propertyChange(PropertyChangeEvent e) {
  if (e.getSource() == getEditor() &&
      e.getPropertyName().equals("document")) {
      JTextComponent      editor = getEditor();
      Document            oldDoc = (Document)e.getOldValue();
      Document            newDoc = (Document)e.getNewValue();
      // Reset the DocumentListener
      oldDoc.removeDocumentListener(this);
      newDoc.addDocumentListener(this);
      // Recreate the TreeModel.
      treeModel = new ElementTreeModel(newDoc);
      tree.setModel(treeModel);
  }
    }
    // DocumentListener
    /**
     * Gives notification that there was an insert into the document.  The
     * given range bounds the freshly inserted region.
     *
     * @param e the document event
     */
    public void insertUpdate(DocumentEvent e) {
  updateTree(e);
    }
    /**
     * Gives notification that a portion of the document has been
     * removed.  The range is given in terms of what the view last
     * saw (that is, before updating sticky positions).
     *
     * @param e the document event
     */
    public void removeUpdate(DocumentEvent e) {
  updateTree(e);
    }
    /**
     * Gives notification that an attribute or set of attributes changed.
     *
     * @param e the document event
     */
    public void changedUpdate(DocumentEvent e) {
  updateTree(e);
    }
    // CaretListener
    /**
     * Messaged when the selection in the editor has changed. Will update
     * the selection in the tree.
     */
    public void caretUpdate(CaretEvent e) {
  if(!updatingSelection) {
      JTextComponent     editor = getEditor();
      int                selBegin = Math.min(e.getDot(), e.getMark());
      int                end = Math.max(e.getDot(), e.getMark());
      Vector             paths = new Vector();
      TreeModel          model = getTreeModel();
      Object             root = model.getRoot();
      int                rootCount = model.getChildCount(root);
      // Build an array of all the paths to all the character elements
      // in the selection.
      for(int counter = 0; counter < rootCount; counter++) {
    int            start = selBegin;
    while(start <= end) {
        TreePath    path = getPathForIndex(start, root,
               (Element)model.getChild(root, counter));
        Element     charElement = (Element)path.
                             getLastPathComponent();
        paths.addElement(path);
        if(start >= charElement.getEndOffset())
      start++;
        else
      start = charElement.getEndOffset();
    }
      }
      // If a path was found, select it (them).
      int               numPaths = paths.size();
      if(numPaths > 0) {
    TreePath[]    pathArray = new TreePath[numPaths];
    paths.copyInto(pathArray);
    updatingSelection = true;
    try {
        getTree().setSelectionPaths(pathArray);
        getTree().scrollPathToVisible(pathArray[0]);
    }
    finally {
        updatingSelection = false;
    }
      }
  }
    }
    // TreeSelectionListener
    /**
      * Called whenever the value of the selection changes.
      * @param e the event that characterizes the change.
      */
    public void valueChanged(TreeSelectionEvent e) {
  JTree       tree = getTree();
  if(!updatingSelection && tree.getSelectionCount() == 1) {
      TreePath      selPath = tree.getSelectionPath();
      Object        lastPathComponent = selPath.getLastPathComponent();
      if(!(lastPathComponent instanceof DefaultMutableTreeNode)) {
    Element       selElement = (Element)lastPathComponent;
    updatingSelection = true;
    try {
        getEditor().select(selElement.getStartOffset(),
               selElement.getEndOffset());
    }
    finally {
        updatingSelection = false;
    }
      }
  }
    }
    // Local methods
    /**
     * @return tree showing elements.
     */
    protected JTree getTree() {
  return tree;
    }
    /**
     * @return JTextComponent showing elements for.
     */
    protected JTextComponent getEditor() {
  return editor;
    }
    /**
     * @return TreeModel implementation used to represent the elements.
     */
    public DefaultTreeModel getTreeModel() {
  return treeModel;
    }
    /**
     * Updates the tree based on the event type. This will invoke either
     * updateTree with the root element, or handleChange.
     */
    protected void updateTree(DocumentEvent event) {
  updatingSelection = true;
  try {
      TreeModel        model = getTreeModel();
      Object           root = model.getRoot();
      for(int counter = model.getChildCount(root) - 1; counter >= 0;
    counter--) {
    updateTree(event, (Element)model.getChild(root, counter));
      }
  }
  finally {
      updatingSelection = false;
  }
    }
    /**
     * Creates TreeModelEvents based on the DocumentEvent and messages
     * the treemodel. This recursively invokes this method with children
     * elements.
     * @param event indicates what elements in the tree hierarchy have
     * changed.
     * @param element Current element to check for changes against.
     */
    protected void updateTree(DocumentEvent event, Element element) {
        DocumentEvent.ElementChange ec = event.getChange(element);
        if (ec != null) {
      Element[]       removed = ec.getChildrenRemoved();
      Element[]       added = ec.getChildrenAdded();
      int             startIndex = ec.getIndex();
      // Check for removed.
      if(removed != null && removed.length > 0) {
    int[]            indices = new int[removed.length];
    for(int counter = 0; counter < removed.length; counter++) {
        indices[counter] = startIndex + counter;
    }
    getTreeModel().nodesWereRemoved((TreeNode)element, indices,
            removed);
      }
      // check for added
      if(added != null && added.length > 0) {
    int[]            indices = new int[added.length];
    for(int counter = 0; counter < added.length; counter++) {
        indices[counter] = startIndex + counter;
    }
    getTreeModel().nodesWereInserted((TreeNode)element, indices);
      }
        }
  if(!element.isLeaf()) {
      int        startIndex = element.getElementIndex
                           (event.getOffset());
      int        elementCount = element.getElementCount();
      int        endIndex = Math.min(elementCount - 1,
             element.getElementIndex
             (event.getOffset() + event.getLength()));
      if(startIndex > 0 && startIndex < elementCount &&
         element.getElement(startIndex).getStartOffset() ==
         event.getOffset()) {
    // Force checking the previous element.
    startIndex--;
      }
      if(startIndex != -1 && endIndex != -1) {
    for(int counter = startIndex; counter <= endIndex; counter++) {
        updateTree(event, element.getElement(counter));
    }
      }
  }
  else {
      // Element is a leaf, assume it changed
      getTreeModel().nodeChanged((TreeNode)element);
  }
    }
    /**
     * Returns a TreePath to the element at position.
     */
    protected TreePath getPathForIndex(int position, Object root,
               Element rootElement) {
  TreePath         path = new TreePath(root);
  Element          child = rootElement.getElement
                              (rootElement.getElementIndex(position));
  path = path.pathByAddingChild(rootElement);
  path = path.pathByAddingChild(child);
  while(!child.isLeaf()) {
      child = child.getElement(child.getElementIndex(position));
      path = path.pathByAddingChild(child);
  }
  return path;
    }
    /**
     * ElementTreeModel is an implementation of TreeModel to handle displaying
     * the Elements from a Document. AbstractDocument.AbstractElement is
     * the default implementation used by the swing text package to implement
     * Element, and it implements TreeNode. This makes it trivial to create
     * a DefaultTreeModel rooted at a particular Element from the Document.
     * Unfortunately each Document can have more than one root Element.
     * Implying that to display all the root elements as a child of another
     * root a fake node has be created. This class creates a fake node as
     * the root with the children being the root elements of the Document
     * (getRootElements).
     * 

This subclasses DefaultTreeModel. The majority of the TreeModel
     * methods have been subclassed, primarily to special case the root.
     */
    public static class ElementTreeModel extends DefaultTreeModel {
  protected Element[]         rootElements;
  public ElementTreeModel(Document document) {
      super(new DefaultMutableTreeNode("root"), false);
      rootElements = document.getRootElements();
  }
  /**
   * Returns the child of parent at index index in
   * the parent's child array.  parent must be a node
   * previously obtained from this data source. This should
   * not return null if index is a valid index for
   * parent (that is index >= 0 && index
   * < getChildCount(parent)).
   *
   * @param   parent  a node in the tree, obtained from this data source
   * @return  the child of parent at index index
   */
  public Object getChild(Object parent, int index) {
      if(parent == root)
    return rootElements[index];
      return super.getChild(parent, index);
  }
  /**
   * Returns the number of children of parent.  Returns 0
   * if the node is a leaf or if it has no children.
   * parent must be a node previously obtained from this
   * data source.
   *
   * @param   parent  a node in the tree, obtained from this data source
   * @return  the number of children of the node parent
   */
  public int getChildCount(Object parent) {
      if(parent == root)
    return rootElements.length;
      return super.getChildCount(parent);
  }
  /**
   * Returns true if node is a leaf.  It is possible for
   * this method to return false even if node has no
   * children.  A directory in a filesystem, for example, may
   * contain no files; the node representing the directory is
   * not a leaf, but it also has no children.
   *
   * @param   node    a node in the tree, obtained from this data source
   * @return  true if node is a leaf
   */
  public boolean isLeaf(Object node) {
      if(node == root)
    return false;
      return super.isLeaf(node);
  }
  /**
   * Returns the index of child in parent.
   */
  public int getIndexOfChild(Object parent, Object child) {
      if(parent == root) {
    for(int counter = rootElements.length - 1; counter >= 0;
        counter--) {
        if(rootElements[counter] == child)
      return counter;
    }
    return -1;
      }
      return super.getIndexOfChild(parent, child);
  }
  /**
   * Invoke this method after you've changed how node is to be
   * represented in the tree.
   */
  public void nodeChanged(TreeNode node) {
      if(listenerList != null && node != null) {
    TreeNode         parent = node.getParent();
    if(parent == null && node != root) {
        parent = root;
    }
    if(parent != null) {
        int        anIndex = getIndexOfChild(parent, node);
        if(anIndex != -1) {
      int[]        cIndexs = new int[1];
      cIndexs[0] = anIndex;
      nodesChanged(parent, cIndexs);
        }
    }
      }
        }
  /**
   * Returns the path to a particluar node. This is recursive.
   */
  protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
      TreeNode[]              retNodes;
      /* Check for null, in case someone passed in a null node, or
         they passed in an element that isn't rooted at root. */
      if(aNode == null) {
    if(depth == 0)
        return null;
    else
        retNodes = new TreeNode[depth];
      }
      else {
    depth++;
    if(aNode == root)
        retNodes = new TreeNode[depth];
    else {
        TreeNode parent = aNode.getParent();
        if(parent == null)
      parent = root;
        retNodes = getPathToRoot(parent, depth);
    }
    retNodes[retNodes.length - depth] = aNode;
      }
      return retNodes;
  }
    }
}
        
Notepad.zip( 51 k)