Java/Tiny Application/Editor

Материал из Java эксперт
Версия от 18:01, 31 мая 2010; (обсуждение)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

Ekit

/*
http://www.hexidec.ru/ekit.php

Ekit is a free open source Java HTML editor applet and application. The Ekit standalone also allows for HTML to be loaded and saved, as well as serialized and saved as an RTF. It is approaching its first production release version.
Ekit has proven to be very popular, with hundreds of developers using it in over 30 countries. Many have added to it already, and I always welcome additional contributions. Please feel free to share any enhancements or bug reports with me.
*/





Sample application using the simple text editor component that supports only one font

 
/*
 * @(#)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.rupareTo("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();
  }
    }
}
/*
 * @(#)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 <code>editor</code>. 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 <code>position</code>.
     */
    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).
     * <p>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 <I>parent</I> at index <I>index</I> in
   * the parent"s child array.  <I>parent</I> must be a node
   * previously obtained from this data source. This should
   * not return null if <i>index</i> is a valid index for
   * <i>parent</i> (that is <i>index</i> >= 0 && <i>index</i>
   * < getChildCount(<i>parent</i>)).
   *
   * @param   parent  a node in the tree, obtained from this data source
   * @return  the child of <I>parent</I> at index <I>index</I>
   */
  public Object getChild(Object parent, int index) {
      if(parent == root)
    return rootElements[index];
      return super.getChild(parent, index);
  }

  /**
   * Returns the number of children of <I>parent</I>.  Returns 0
   * if the node is a leaf or if it has no children.
   * <I>parent</I> 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 <I>parent</I>
   */
  public int getChildCount(Object parent) {
      if(parent == root)
    return rootElements.length;
      return super.getChildCount(parent);
  }

  /**
   * Returns true if <I>node</I> is a leaf.  It is possible for
   * this method to return false even if <I>node</I> 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 <I>node</I> 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;
  }
    }
}





Scriptpad is a notepad like editor to open/edit/save and run script (JavaScript) files.

Source code for Styled pad