You are viewing a plain text version of this content. The canonical link for it is here.
Posted to batik-commits@xmlgraphics.apache.org by ca...@apache.org on 2007/11/13 01:40:58 UTC

svn commit: r594367 [7/9] - in /xmlgraphics/batik/trunk: ./ resources/org/apache/batik/apps/svgbrowser/resources/ resources/org/apache/batik/util/gui/resources/ sources-1.3/org/apache/batik/util/ sources-1.3/org/apache/batik/util/gui/ sources-1.4/org/a...

Added: xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/DropDownComponent.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/DropDownComponent.java?rev=594367&view=auto
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/DropDownComponent.java (added)
+++ xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/DropDownComponent.java Mon Nov 12 16:40:53 2007
@@ -0,0 +1,869 @@
+/*
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+package org.apache.batik.util.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.GridLayout;
+import java.awt.Point;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.EventListener;
+import java.util.EventObject;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.event.EventListenerList;
+import javax.swing.plaf.basic.BasicButtonUI;
+
+import org.apache.batik.util.resources.ResourceManager;
+
+/**
+ * The drop down menu component. Supports drop down popup menu and the main
+ * button.
+ *
+ * @version $Id$
+ */
+public class DropDownComponent extends JPanel {
+
+    /**
+     * The main button for this component.
+     */
+    private JButton mainButton;
+
+    /**
+     * The drop down button. When clicked the dropdown popup menu appears.
+     */
+    private JButton dropDownButton;
+
+    /**
+     * The icon for enabled drop down button.
+     */
+    private Icon enabledDownArrow;
+
+    /**
+     * The icon for disabled drop down button.
+     */
+    private Icon disabledDownArrow;
+
+    /**
+     * The scrollable pop up menu.
+     */
+    private ScrollablePopupMenu popupMenu;
+
+    /**
+     * If drop down menu appears when clicked on dropdown button.
+     */
+    private boolean isDropDownEnabled;
+
+    /**
+     * Creates the dropdown menu with the given main button.
+     *
+     * @param mainButton
+     *            the components main button
+     */
+    public DropDownComponent(JButton mainButton) {
+        super(new BorderLayout());
+
+        // Initializes pop up menu
+        popupMenu = getPopupMenu();
+
+        this.mainButton = mainButton;
+        add(this.mainButton, BorderLayout.WEST);
+        this.mainButton.setMaximumSize(new Dimension(24, 24));
+        this.mainButton.setPreferredSize(new Dimension(24, 24));
+
+        // Initializes dropdown button and icons for dropdown button
+        enabledDownArrow = new SmallDownArrow();
+        disabledDownArrow = new SmallDisabledDownArrow();
+        dropDownButton = new JButton(disabledDownArrow);
+        dropDownButton.setDisabledIcon(disabledDownArrow);
+        dropDownButton.addMouseListener(new DropDownListener());
+        dropDownButton.setMaximumSize(new Dimension(18, 24));
+        dropDownButton.setMinimumSize(new Dimension(18, 10));
+        dropDownButton.setPreferredSize(new Dimension(18, 10));
+        dropDownButton.setFocusPainted(false);
+        add(dropDownButton, BorderLayout.EAST);
+
+        setEnabled(false);
+    }
+
+    /**
+     * Gets the dropdown popup menu.
+     *
+     * @return ScrollablePopupMenu
+     */
+    public ScrollablePopupMenu getPopupMenu() {
+        if (popupMenu == null) {
+            popupMenu = new ScrollablePopupMenu(this);
+            popupMenu.setEnabled(false);
+            // If the pop up menu gets disabled,
+            // the dropdown buttons should be disabled as well
+            popupMenu.addPropertyChangeListener
+                ("enabled",
+                 new PropertyChangeListener() {
+                     public void propertyChange(PropertyChangeEvent evt) {
+                         setEnabled
+                            (((Boolean) evt.getNewValue()).booleanValue());
+                     }
+                 });
+
+            // Listens for the changes in the scrollable pop up menu
+            popupMenu.addListener
+                (new ScrollablePopupMenuAdapter() {
+
+                     public void itemsWereAdded(ScrollablePopupMenuEvent ev) {
+                         updateMainButtonTooltip(ev.getDetails());
+                     }
+
+                     public void itemsWereRemoved(ScrollablePopupMenuEvent ev) {
+                         updateMainButtonTooltip(ev.getDetails());
+                     }
+                 });
+        }
+        return popupMenu;
+    }
+
+    public void setEnabled(boolean enable) {
+        isDropDownEnabled = enable;
+        mainButton.setEnabled(enable);
+        dropDownButton.setEnabled(enable);
+        dropDownButton.setIcon(enable ? enabledDownArrow : disabledDownArrow);
+    }
+
+    public boolean isEnabled() {
+        return isDropDownEnabled;
+    }
+
+    /**
+     * Sets new tooltip text to the main button.
+     *
+     * @param newTooltip
+     *            the new tooltip text
+     */
+    public void updateMainButtonTooltip(String newTooltip) {
+        mainButton.setToolTipText(newTooltip);
+    }
+
+    /**
+     * Shows the pop up menu when clicked.
+     */
+    private class DropDownListener extends MouseAdapter {
+        public void mousePressed(MouseEvent e) {
+            if (popupMenu.isShowing() && isDropDownEnabled) {
+                popupMenu.setVisible(false);
+            } else if (isDropDownEnabled) {
+                popupMenu.showMenu
+                    ((Component) e.getSource(), DropDownComponent.this);
+            }
+        }
+    }
+
+    /**
+     * A small downward-pointing arrow icon.
+     */
+    private static class SmallDownArrow implements Icon {
+
+        /**
+         * The arrow color.
+         */
+        protected Color arrowColor = Color.black;
+
+        public void paintIcon(Component c, Graphics g, int x, int y) {
+            g.setColor(arrowColor);
+            g.drawLine(x, y, x + 4, y);
+            g.drawLine(x + 1, y + 1, x + 3, y + 1);
+            g.drawLine(x + 2, y + 2, x + 2, y + 2);
+        }
+
+        public int getIconWidth() {
+            return 6;
+        }
+
+        public int getIconHeight() {
+            return 4;
+        }
+    }
+
+    /**
+     * A disabled small downward-pointing arrow icon.
+     */
+    private static class SmallDisabledDownArrow extends SmallDownArrow {
+
+        /**
+         * Constructor.
+         */
+        public SmallDisabledDownArrow() {
+            arrowColor = new Color(140, 140, 140);
+        }
+
+        public void paintIcon(Component c, Graphics g, int x, int y) {
+            super.paintIcon(c, g, x, y);
+            g.setColor(Color.white);
+            g.drawLine(x + 3, y + 2, x + 4, y + 1);
+            g.drawLine(x + 3, y + 3, x + 5, y + 1);
+        }
+    }
+
+    /**
+     * The scrollable pop up menu item.
+     */
+    public static interface ScrollablePopupMenuItem {
+
+        /**
+         * Selects and deselects the item.
+         *
+         * @param selected
+         *            is selected
+         */
+        void setSelected(boolean selected);
+
+        /**
+         * Checks if the item is selected.
+         *
+         * @return True if selected
+         */
+        boolean isSelected();
+
+        /**
+         * Returns the item name.
+         *
+         * @return the name
+         */
+        String getText();
+
+        /**
+         * Sets the item name.
+         *
+         * @param text
+         *            The new item name
+         */
+        void setText(String text);
+
+        /**
+         * Enables / disables the item
+         *
+         * @param enabled
+         *            True - enables the item
+         */
+        void setEnabled(boolean enabled);
+    }
+
+    /**
+     * Default implementation of the scrollable popup menu item.
+     */
+    public static class DefaultScrollablePopupMenuItem extends JButton
+            implements ScrollablePopupMenuItem {
+
+        /**
+         * The selected item background color.
+         */
+        public static final Color MENU_HIGHLIGHT_BG_COLOR =
+            UIManager.getColor("MenuItem.selectionBackground");
+
+        /**
+         * The selected item foreground color.
+         */
+        public static final Color MENU_HIGHLIGHT_FG_COLOR =
+            UIManager.getColor("MenuItem.selectionForeground");
+
+        /**
+         * The item background color.
+         */
+        public static final Color MENUITEM_BG_COLOR =
+            UIManager.getColor("MenuItem.background");
+
+        /**
+         * The item foreground color.
+         */
+        public static final Color MENUITEM_FG_COLOR =
+            UIManager.getColor("MenuItem.foreground");
+
+        /**
+         * The parent scrollable popup menu.
+         */
+        private ScrollablePopupMenu parent;
+
+        /**
+         * Constructor.
+         */
+        public DefaultScrollablePopupMenuItem(ScrollablePopupMenu parent,
+                                              String text) {
+            super(text);
+            this.parent = parent;
+            init();
+        }
+
+        /**
+         * Initializes this item.
+         */
+        private void init() {
+            this.setUI(BasicButtonUI.createUI(this));
+            setBorder(BorderFactory.createEmptyBorder(5, 15, 5, 20));
+            setMenuItemDefaultColors();
+            this.setAlignmentX(JButton.LEFT_ALIGNMENT);
+            setSelected(false);
+
+            this.addMouseListener
+                (new MouseAdapter() {
+
+                     public void mouseEntered(MouseEvent e) {
+                         if (DefaultScrollablePopupMenuItem.this.isEnabled()) {
+                             setSelected(true);
+                             parent.selectionChanged
+                                 (DefaultScrollablePopupMenuItem.this, true);
+                         }
+                     }
+
+                     public void mouseExited(MouseEvent e) {
+                         if (DefaultScrollablePopupMenuItem.this.isEnabled()) {
+                             setSelected(false);
+                             parent.selectionChanged
+                                (DefaultScrollablePopupMenuItem.this, false);
+                         }
+                     }
+
+                     public void mouseClicked(MouseEvent e) {
+                         parent.processItemClicked();
+                     }
+                 });
+        }
+
+        /**
+         * Sets the default item colors.
+         */
+        private void setMenuItemDefaultColors() {
+            setBackground(MENUITEM_BG_COLOR);
+            setForeground(MENUITEM_FG_COLOR);
+        }
+
+        public void setSelected(boolean selected) {
+            super.setSelected(selected);
+            if (selected) {
+                setBackground(MENU_HIGHLIGHT_BG_COLOR);
+                setForeground(MENU_HIGHLIGHT_FG_COLOR);
+            } else {
+                setMenuItemDefaultColors();
+            }
+        }
+
+        public String getText() {
+            return super.getText();
+        }
+
+        public void setText(String text) {
+            super.setText(text);
+        }
+
+        public void setEnabled(boolean b) {
+            super.setEnabled(b);
+        }
+    }
+
+    /**
+     * The scrollable popup menu model.
+     */
+    public static interface ScrollablePopupMenuModel {
+
+        /**
+         * Gets the footer text for the ScrollablePopupMenu's footer item.
+         * @return    the footer text.
+         */
+        String getFooterText();
+
+        /**
+         * Processes the click on the pop up menu item.
+         */
+        void processItemClicked();
+
+        /**
+         * Processes the showing of the pop up menu. Invoked before showing the
+         * pop up menu
+         */
+        void processBeforeShowed();
+
+        /**
+         * Processes the showing of the pop up menu. Invoked after showing the
+         * pop up menu
+         */
+        void processAfterShowed();
+    }
+
+    /**
+     * The Scrollable Popup Menu Component.
+     */
+    public static class ScrollablePopupMenu extends JPopupMenu {
+
+        /**
+         * The resource file name.
+         */
+        private static final String RESOURCES =
+            "org.apache.batik.util.gui.resources.ScrollablePopupMenuMessages";
+
+        /**
+         * The resource bundle.
+         */
+        private static ResourceBundle bundle;
+
+        /**
+         * The resource manager.
+         */
+        private static ResourceManager resources;
+        static {
+            bundle = ResourceBundle.getBundle(RESOURCES, Locale.getDefault());
+            resources = new ResourceManager(bundle);
+        }
+
+        /**
+         * The menu panel.
+         */
+        private JPanel menuPanel = new JPanel();
+
+        /**
+         * The scroll pane.
+         */
+        private JScrollPane scrollPane;
+
+        /**
+         * Max menu height.
+         */
+        private int preferredHeight = resources.getInteger("PreferredHeight");
+
+        /**
+         * The model for this component.
+         */
+        private ScrollablePopupMenuModel model;
+
+        /**
+         * The owner component.
+         */
+        private JComponent ownerComponent;
+
+        /**
+         * Footer item. Should be always shown at the bottom of this pop up.
+         */
+        private ScrollablePopupMenuItem footer;
+
+        /**
+         * Listeners list.
+         */
+        private EventListenerList eventListeners = new EventListenerList();
+
+        /**
+         * Constructor.
+         *
+         * @param owner
+         *            The owner component
+         */
+        public ScrollablePopupMenu(JComponent owner) {
+            super();
+            this.setLayout(new BorderLayout());
+            menuPanel.setLayout(new GridLayout(0, 1));
+            ownerComponent = owner;
+            init();
+        }
+
+        /**
+         * Initializes this popup menu.
+         */
+        private void init() {
+            super.removeAll();
+            scrollPane = new JScrollPane();
+            scrollPane.setViewportView(menuPanel);
+            scrollPane.setBorder(null);
+            int minWidth = resources.getInteger("ScrollPane.minWidth");
+            int minHeight = resources.getInteger("ScrollPane.minHeight");
+            int maxWidth = resources.getInteger("ScrollPane.maxWidth");
+            int maxHeight = resources.getInteger("ScrollPane.maxHeight");
+            scrollPane.setMinimumSize(new Dimension(minWidth, minHeight));
+            scrollPane.setMaximumSize(new Dimension(maxWidth, maxHeight));
+            scrollPane.setHorizontalScrollBarPolicy
+                (ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+            add(scrollPane, BorderLayout.CENTER);
+            addFooter(new DefaultScrollablePopupMenuItem(this, ""));
+        }
+
+        /**
+         * Shows this popup menu.
+         *
+         * @param invoker
+         *            The popup menu invoker component
+         * @param refComponent
+         *            The dropdown component that containts this menu
+         */
+        public void showMenu(Component invoker, Component refComponent) {
+            model.processBeforeShowed();
+
+            Point abs = new Point(0, refComponent.getHeight());
+            SwingUtilities.convertPointToScreen(abs, refComponent);
+            this.setLocation(abs);
+            this.setInvoker(invoker);
+            this.setVisible(true);
+            this.revalidate();
+            this.repaint();
+
+            model.processAfterShowed();
+        }
+
+        /**
+         * Adds the item to this component at the specified location.
+         *
+         * @param menuItem
+         *            the item to add
+         */
+        public void add(ScrollablePopupMenuItem menuItem, int index,
+                        int oldSize, int newSize) {
+            menuPanel.add((Component) menuItem, index);
+            if (oldSize == 0) {
+                this.setEnabled(true);
+            }
+        }
+
+        /**
+         * Removes the item from this component.
+         *
+         * @param menuItem
+         *            the item to remove
+         */
+        public void remove(ScrollablePopupMenuItem menuItem, int oldSize,
+                           int newSize) {
+            menuPanel.remove((Component) menuItem);
+            if (newSize == 0) {
+                this.setEnabled(false);
+            }
+        }
+
+        /**
+         * Gets the preferred width of this pop up menu.
+         *
+         * @return the preferred width
+         */
+        private int getPreferredWidth() {
+            Component[] components = menuPanel.getComponents();
+            int maxWidth = 0;
+            for (int i = 0; i < components.length; i++) {
+                int currentWidth = components[i].getPreferredSize().width;
+                if (maxWidth < currentWidth) {
+                    maxWidth = currentWidth;
+                }
+            }
+            int footerWidth = ((Component) footer).getPreferredSize().width;
+            if (footerWidth > maxWidth) {
+                maxWidth = footerWidth;
+            }
+            int widthOffset = 30;
+            return maxWidth + widthOffset;
+        }
+
+        /**
+         * Gets the preferred height of this component.
+         *
+         * @return the preferred height
+         */
+        private int getPreferredHeight() {
+            if (scrollPane.getPreferredSize().height < preferredHeight) {
+                int heightOffset = 10;
+                return scrollPane.getPreferredSize().height
+                        + ((Component) footer).getPreferredSize().height
+                        + heightOffset;
+            }
+            return preferredHeight
+                    + ((Component) footer).getPreferredSize().height;
+        }
+
+        public Dimension getPreferredSize() {
+            return new Dimension(getPreferredWidth(), getPreferredHeight());
+        }
+
+        /**
+         * Invoked when item selection changes.
+         */
+        public void selectionChanged(ScrollablePopupMenuItem targetItem,
+                boolean wasSelected) {
+            Component[] comps = menuPanel.getComponents();
+            int n = comps.length;
+            // Deselect all if something was selected
+            if (!wasSelected) {
+                for (int i = n - 1; i >= 0; i--) {
+                    ScrollablePopupMenuItem item = (ScrollablePopupMenuItem) comps[i];
+                    item.setSelected(wasSelected);
+                }
+            } else {
+                for (int i = 0; i < n; i++) {
+                    ScrollablePopupMenuItem item = (ScrollablePopupMenuItem) comps[i];
+                    if (item == targetItem) {
+                        break;
+                    }
+                    item.setSelected(true);
+                }
+            }
+            footer.setText(model.getFooterText() + getSelectedItemsCount());
+            repaint();
+        }
+
+        /**
+         * Sets the ScrollablePopupMenuModel.
+         *
+         * @param model
+         *            the model to set
+         */
+        public void setModel(ScrollablePopupMenuModel model) {
+            this.model = model;
+            this.footer.setText(model.getFooterText());
+        }
+
+        /**
+         * Gets the ScrollablePopupMenuModel
+         *
+         * @return the ScrollablePopupMenuModel model
+         */
+        public ScrollablePopupMenuModel getModel() {
+            return model;
+        }
+
+        /**
+         * Gets the number of the selected items.
+         *
+         * @return number of selected items
+         */
+        public int getSelectedItemsCount() {
+            int selectionCount = 0;
+            Component[] components = menuPanel.getComponents();
+            for (int i = 0; i < components.length; i++) {
+                ScrollablePopupMenuItem item = (ScrollablePopupMenuItem) components[i];
+                if (item.isSelected()) {
+                    selectionCount++;
+                }
+            }
+            return selectionCount;
+        }
+
+        /**
+         * Processes click on the pop up menu item.
+         */
+        public void processItemClicked() {
+            footer.setText(model.getFooterText() + 0);
+            setVisible(false);
+            model.processItemClicked();
+        }
+
+        /**
+         * Gets the owner component.
+         * @return    the owner component
+         */
+        public JComponent getOwner() {
+            return ownerComponent;
+        }
+
+        /**
+         * Adds the footer item to this pop up menu.
+         */
+        private void addFooter(ScrollablePopupMenuItem footer) {
+            this.footer = footer;
+            this.footer.setEnabled(false);
+            add((Component)this.footer, BorderLayout.SOUTH);
+        }
+
+        /**
+         * Gets the footer item.
+         * @return    the footer
+         */
+        public ScrollablePopupMenuItem getFooter() {
+            return footer;
+        }
+
+        /**
+         * Adds the listener to the listener list.
+         *
+         * @param l
+         *            The listener to add
+         */
+        public void addListener(ScrollablePopupMenuListener listener) {
+            eventListeners.add(ScrollablePopupMenuListener.class, listener);
+        }
+
+        /**
+         * Fires the itemsWereAdded event, when the items are added to this pop
+         * up menu.
+         *
+         * @param event
+         *            The associated ScrollablePopupMenuEvent event
+         */
+        public void fireItemsWereAdded(ScrollablePopupMenuEvent event) {
+            Object[] listeners = eventListeners.getListenerList();
+            int length = listeners.length;
+            for (int i = 0; i < length; i += 2) {
+                if (listeners[i] == ScrollablePopupMenuListener.class) {
+                    ((ScrollablePopupMenuListener) listeners[i + 1])
+                            .itemsWereAdded(event);
+                }
+            }
+        }
+
+        /**
+         * Fires the itemsWereRemove event, when the items are removed from this
+         * pop up menu.
+         *
+         * @param event
+         *            The associated ScrollablePopupMenuEvent event
+         */
+        public void fireItemsWereRemoved(ScrollablePopupMenuEvent event) {
+            Object[] listeners = eventListeners.getListenerList();
+            int length = listeners.length;
+            for (int i = 0; i < length; i += 2) {
+                if (listeners[i] == ScrollablePopupMenuListener.class) {
+                    ((ScrollablePopupMenuListener) listeners[i + 1])
+                            .itemsWereRemoved(event);
+                }
+            }
+        }
+    }
+
+    // Custom event support for ScrollablePopupMenu
+
+    /**
+     * Event to pass to listener.
+     */
+    public static class ScrollablePopupMenuEvent extends EventObject {
+
+        // The codes for the event type
+        public static final int ITEMS_ADDED = 1;
+        public static final int ITEMS_REMOVED = 2;
+
+        /**
+         * The event type.
+         */
+        private int type;
+
+        /**
+         * The number of items that were added / removed.
+         */
+        private int itemNumber;
+
+        /**
+         * The details about the event.
+         */
+        private String details;
+
+        /**
+         * Creates the ScrollablePopupMenuEvent.
+         *
+         * @param source
+         *            The source component
+         * @param type
+         *            The event type
+         * @param itemNumber
+         *            The item number
+         * @param details
+         *            The event details
+         */
+        public ScrollablePopupMenuEvent(Object source, int type,
+                                        int itemNumber, String details) {
+            super(source);
+            initEvent(type, itemNumber, details);
+        }
+
+        /**
+         * Initializes this event.
+         */
+        public void initEvent(int type, int itemNumber, String details) {
+            this.type = type;
+            this.itemNumber = itemNumber;
+            this.details = details;
+        }
+
+        /**
+         * Gets the event details.
+         *
+         * @return the details
+         */
+        public String getDetails() {
+            return details;
+        }
+
+        /**
+         * Gets the item number.
+         *
+         * @return the item number
+         */
+        public int getItemNumber() {
+            return itemNumber;
+        }
+
+        /**
+         * Gets the event type.
+         *
+         * @return the type
+         */
+        public int getType() {
+            return type;
+        }
+    }
+
+    /**
+     * The ScrollablePopupMenu listener. Handles the events that
+     * ScrollablePopupMenu fires
+     */
+    public static interface ScrollablePopupMenuListener extends EventListener {
+
+        /**
+         * Handles the 'itemsWereAdded' event.
+         *
+         * @param ev
+         *            The associated event
+         */
+        void itemsWereAdded(ScrollablePopupMenuEvent ev);
+
+        /**
+         * Handles the 'itemsWereRemoved' event.
+         *
+         * @param ev
+         *            The associated event
+         */
+        void itemsWereRemoved(ScrollablePopupMenuEvent ev);
+    }
+
+    /**
+     * The adapter for the ScrollablePopupMenuListener.
+     */
+    public static class ScrollablePopupMenuAdapter
+            implements ScrollablePopupMenuListener {
+
+        public void itemsWereAdded(ScrollablePopupMenuEvent ev) {
+        }
+
+        public void itemsWereRemoved(ScrollablePopupMenuEvent ev) {
+        }
+    }
+}

Modified: xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/resource/ButtonFactory.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/resource/ButtonFactory.java?rev=594367&r1=594366&r2=594367&view=diff
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/resource/ButtonFactory.java (original)
+++ xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/resource/ButtonFactory.java Mon Nov 12 16:40:53 2007
@@ -28,6 +28,7 @@
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JRadioButton;
+import javax.swing.JToggleButton;
 
 import org.apache.batik.util.resources.ResourceFormatException;
 import org.apache.batik.util.resources.ResourceManager;
@@ -120,6 +121,31 @@
             result = new JToolbarButton(getString(name+TEXT_SUFFIX));
         } catch (MissingResourceException e) {
             result = new JToolbarButton();
+        }
+        initializeButton(result, name);
+        return result;
+    }
+    
+    /**
+     * Creates and returns a new swing button initialised
+     * to be used as a toolbar toggle button
+     * @param name the name of the button in the resource bundle
+     * @throws MissingResourceException if key is not the name of a button.
+     *         It is not thrown if the mnemonic and the action keys are missing
+     * @throws ResourceFormatException if the mnemonic is not a single
+     *         character
+     * @throws MissingListenerException if the button action is not found in
+     *         the action map
+     */
+    public JToggleButton createJToolbarToggleButton(String name)
+        throws MissingResourceException,
+               ResourceFormatException,
+               MissingListenerException {
+        JToggleButton result;
+        try {
+            result = new JToolbarToggleButton(getString(name+TEXT_SUFFIX));
+        } catch (MissingResourceException e) {
+            result = new JToolbarToggleButton();
         }
         initializeButton(result, name);
         return result;

Added: xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/resource/JToolbarToggleButton.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/resource/JToolbarToggleButton.java?rev=594367&view=auto
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/resource/JToolbarToggleButton.java (added)
+++ xmlgraphics/batik/trunk/sources/org/apache/batik/util/gui/resource/JToolbarToggleButton.java Mon Nov 12 16:40:53 2007
@@ -0,0 +1,82 @@
+/*
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+package org.apache.batik.util.gui.resource;
+
+import java.awt.Insets;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JToggleButton;
+import javax.swing.UIManager;
+
+/**
+ * This class represents the buttons used in toolbars.
+ *
+ * @version $Id: JToolbarButton.java 498555 2007-01-22 08:09:33Z cam $
+ */
+public class JToolbarToggleButton extends JToggleButton {
+
+    /**
+     * Creates a new toolbar button.
+     */
+    public JToolbarToggleButton() {
+        initialize();
+    }
+
+    /**
+     * Creates a new toolbar button.
+     * @param txt The button text.
+     */
+    public JToolbarToggleButton(String txt) {
+        super(txt);
+        initialize();
+    }
+
+    /**
+     * Initializes the button.
+     */
+    protected void initialize() {
+        if (!System.getProperty("java.version").startsWith("1.3")) {
+            setOpaque(false);
+            setBackground(new java.awt.Color(0, 0, 0, 0));
+        }
+        setBorderPainted(false);
+        setMargin(new Insets(2, 2, 2, 2));
+
+        // Windows XP look and feel seems to have a bug due to which the
+        // size of the parent container changes when the border painted
+        // property is set. Temporary fix: disable mouseover behavior if
+        // installed lnf is Windows XP
+        if (!UIManager.getLookAndFeel().getName().equals("Windows")) {
+            addMouseListener(new MouseListener());
+        }
+    }
+
+    /**
+     * To manage the mouse interactions.
+     */
+    protected class MouseListener extends MouseAdapter {
+        public void mouseEntered(MouseEvent ev) {
+            setBorderPainted(true);
+        }
+        public void mouseExited(MouseEvent ev) {
+            setBorderPainted(false);
+        }
+    }
+}

Added: xmlgraphics/batik/trunk/sources/org/gjt/sp/jedit/textarea/DefaultInputHandler.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/gjt/sp/jedit/textarea/DefaultInputHandler.java?rev=594367&view=auto
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/gjt/sp/jedit/textarea/DefaultInputHandler.java (added)
+++ xmlgraphics/batik/trunk/sources/org/gjt/sp/jedit/textarea/DefaultInputHandler.java Mon Nov 12 16:40:53 2007
@@ -0,0 +1,347 @@
+/*
+ * DefaultInputHandler.java - Default implementation of an input handler
+ * Copyright (C) 1999 Slava Pestov
+ *
+ * You may use and modify this package for any purpose. Redistribution is
+ * permitted, in both source and binary form, provided that this notice
+ * remains intact in all source distributions of this package.
+ */
+package org.gjt.sp.jedit.textarea;
+
+import javax.swing.KeyStroke;
+import java.awt.event.*;
+import java.awt.Toolkit;
+import java.util.Hashtable;
+import java.util.StringTokenizer;
+
+/**
+ * The default input handler. It maps sequences of keystrokes into actions
+ * and inserts key typed events into the text area.
+ * @author Slava Pestov
+ * @version $Id: DefaultInputHandler.java,v 1.18 1999/12/13 03:40:30 sp Exp $
+ */
+public class DefaultInputHandler extends InputHandler
+{
+	/**
+	 * Creates a new input handler with no key bindings defined.
+	 */
+	public DefaultInputHandler()
+	{
+		bindings = currentBindings = new Hashtable();
+	}
+
+	/**
+	 * Sets up the default key bindings.
+	 */
+	public void addDefaultKeyBindings()
+	{
+		addKeyBinding("BACK_SPACE",BACKSPACE);
+		addKeyBinding("C+BACK_SPACE",BACKSPACE_WORD);
+		addKeyBinding("DELETE",DELETE);
+		addKeyBinding("C+DELETE",DELETE_WORD);
+
+		addKeyBinding("ENTER",INSERT_BREAK);
+		addKeyBinding("TAB",INSERT_TAB);
+
+		addKeyBinding("INSERT",OVERWRITE);
+		addKeyBinding("C+\\",TOGGLE_RECT);
+
+		addKeyBinding("HOME",HOME);
+		addKeyBinding("END",END);
+		addKeyBinding("S+HOME",SELECT_HOME);
+		addKeyBinding("S+END",SELECT_END);
+		addKeyBinding("C+HOME",DOCUMENT_HOME);
+		addKeyBinding("C+END",DOCUMENT_END);
+		addKeyBinding("CS+HOME",SELECT_DOC_HOME);
+		addKeyBinding("CS+END",SELECT_DOC_END);
+
+		addKeyBinding("PAGE_UP",PREV_PAGE);
+		addKeyBinding("PAGE_DOWN",NEXT_PAGE);
+		addKeyBinding("S+PAGE_UP",SELECT_PREV_PAGE);
+		addKeyBinding("S+PAGE_DOWN",SELECT_NEXT_PAGE);
+
+		addKeyBinding("LEFT",PREV_CHAR);
+		addKeyBinding("S+LEFT",SELECT_PREV_CHAR);
+		addKeyBinding("C+LEFT",PREV_WORD);
+		addKeyBinding("CS+LEFT",SELECT_PREV_WORD);
+		addKeyBinding("RIGHT",NEXT_CHAR);
+		addKeyBinding("S+RIGHT",SELECT_NEXT_CHAR);
+		addKeyBinding("C+RIGHT",NEXT_WORD);
+		addKeyBinding("CS+RIGHT",SELECT_NEXT_WORD);
+		addKeyBinding("UP",PREV_LINE);
+		addKeyBinding("S+UP",SELECT_PREV_LINE);
+		addKeyBinding("DOWN",NEXT_LINE);
+		addKeyBinding("S+DOWN",SELECT_NEXT_LINE);
+
+		addKeyBinding("C+ENTER",REPEAT);
+	}
+
+	/**
+	 * Adds a key binding to this input handler. The key binding is
+	 * a list of white space separated key strokes of the form
+	 * <i>[modifiers+]key</i> where modifier is C for Control, A for Alt,
+	 * or S for Shift, and key is either a character (a-z) or a field
+	 * name in the KeyEvent class prefixed with VK_ (e.g., BACK_SPACE)
+	 * @param keyBinding The key binding
+	 * @param action The action
+	 */
+	public void addKeyBinding(String keyBinding, ActionListener action)
+	{
+	        Hashtable current = bindings;
+
+		StringTokenizer st = new StringTokenizer(keyBinding);
+		while(st.hasMoreTokens())
+		{
+			KeyStroke keyStroke = parseKeyStroke(st.nextToken());
+			if(keyStroke == null)
+				return;
+
+			if(st.hasMoreTokens())
+			{
+				Object o = current.get(keyStroke);
+				if(o instanceof Hashtable)
+					current = (Hashtable)o;
+				else
+				{
+					o = new Hashtable();
+					current.put(keyStroke,o);
+					current = (Hashtable)o;
+				}
+			}
+			else
+				current.put(keyStroke,action);
+		}
+	}
+
+	/**
+	 * Removes a key binding from this input handler. This is not yet
+	 * implemented.
+	 * @param keyBinding The key binding
+	 */
+	public void removeKeyBinding(String keyBinding)
+	{
+		throw new InternalError("Not yet implemented");
+	}
+
+	/**
+	 * Removes all key bindings from this input handler.
+	 */
+	public void removeAllKeyBindings()
+	{
+		bindings.clear();
+	}
+
+	/**
+	 * Returns a copy of this input handler that shares the same
+	 * key bindings. Setting key bindings in the copy will also
+	 * set them in the original.
+	 */
+	public InputHandler copy()
+	{
+		return new DefaultInputHandler(this);
+	}
+
+	/**
+	 * Handle a key pressed event. This will look up the binding for
+	 * the key stroke and execute it.
+	 */
+	public void keyPressed(KeyEvent evt)
+	{
+		int keyCode = evt.getKeyCode();
+		int modifiers = evt.getModifiers();
+
+		if(keyCode == KeyEvent.VK_CONTROL ||
+			keyCode == KeyEvent.VK_SHIFT ||
+			keyCode == KeyEvent.VK_ALT ||
+			keyCode == KeyEvent.VK_META)
+			return;
+
+		if((modifiers & ~KeyEvent.SHIFT_MASK) != 0
+			|| evt.isActionKey()
+			|| keyCode == KeyEvent.VK_BACK_SPACE
+			|| keyCode == KeyEvent.VK_DELETE
+			|| keyCode == KeyEvent.VK_ENTER
+			|| keyCode == KeyEvent.VK_TAB
+			|| keyCode == KeyEvent.VK_ESCAPE)
+		{
+			if(grabAction != null)
+			{
+				handleGrabAction(evt);
+				return;
+			}
+
+			KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode,
+				modifiers);
+			Object o = currentBindings.get(keyStroke);
+			if(o == null)
+			{
+				// Don't beep if the user presses some
+				// key we don't know about unless a
+				// prefix is active. Otherwise it will
+				// beep when caps lock is pressed, etc.
+				if(currentBindings != bindings)
+				{
+					Toolkit.getDefaultToolkit().beep();
+					// F10 should be passed on, but C+e F10
+					// shouldn't
+					repeatCount = 0;
+					repeat = false;
+					evt.consume();
+				}
+				currentBindings = bindings;
+				return;
+			}
+			else if(o instanceof ActionListener)
+			{
+				currentBindings = bindings;
+
+				executeAction(((ActionListener)o),
+					evt.getSource(),null);
+
+				evt.consume();
+				return;
+			}
+			else if(o instanceof Hashtable)
+			{
+				currentBindings = (Hashtable)o;
+				evt.consume();
+				return;
+			}
+		}
+	}
+
+	/**
+	 * Handle a key typed event. This inserts the key into the text area.
+	 */
+	public void keyTyped(KeyEvent evt)
+	{
+		int modifiers = evt.getModifiers();
+		char c = evt.getKeyChar();
+		if(c != KeyEvent.CHAR_UNDEFINED &&
+			(modifiers & KeyEvent.ALT_MASK) == 0)
+		{
+			if(c >= 0x20 && c != 0x7f)
+			{
+				KeyStroke keyStroke = KeyStroke.getKeyStroke(
+					Character.toUpperCase(c));
+				Object o = currentBindings.get(keyStroke);
+
+				if(o instanceof Hashtable)
+				{
+					currentBindings = (Hashtable)o;
+					return;
+				}
+				else if(o instanceof ActionListener)
+				{
+					currentBindings = bindings;
+					executeAction((ActionListener)o,
+						evt.getSource(),
+						String.valueOf(c));
+					return;
+				}
+
+				currentBindings = bindings;
+
+				if(grabAction != null)
+				{
+					handleGrabAction(evt);
+					return;
+				}
+
+				// 0-9 adds another 'digit' to the repeat number
+				if(repeat && Character.isDigit(c))
+				{
+					repeatCount *= 10;
+					repeatCount += (c - '0');
+					return;
+				}
+
+				executeAction(INSERT_CHAR,evt.getSource(),
+					String.valueOf(evt.getKeyChar()));
+
+				repeatCount = 0;
+				repeat = false;
+			}
+		}
+	}
+
+	/**
+	 * Converts a string to a keystroke. The string should be of the
+	 * form <i>modifiers</i>+<i>shortcut</i> where <i>modifiers</i>
+	 * is any combination of A for Alt, C for Control, S for Shift
+	 * or M for Meta, and <i>shortcut</i> is either a single character,
+	 * or a keycode name from the <code>KeyEvent</code> class, without
+	 * the <code>VK_</code> prefix.
+	 * @param keyStroke A string description of the key stroke
+	 */
+	public static KeyStroke parseKeyStroke(String keyStroke)
+	{
+		if(keyStroke == null)
+			return null;
+		int modifiers = 0;
+		int index = keyStroke.indexOf('+');
+		if(index != -1)
+		{
+			for(int i = 0; i < index; i++)
+			{
+				switch(Character.toUpperCase(keyStroke
+					.charAt(i)))
+				{
+				case 'A':
+					modifiers |= InputEvent.ALT_MASK;
+					break;
+				case 'C':
+					modifiers |= InputEvent.CTRL_MASK;
+					break;
+				case 'M':
+					modifiers |= InputEvent.META_MASK;
+					break;
+				case 'S':
+					modifiers |= InputEvent.SHIFT_MASK;
+					break;
+				}
+			}
+		}
+		String key = keyStroke.substring(index + 1);
+		if(key.length() == 1)
+		{
+			char ch = Character.toUpperCase(key.charAt(0));
+			if(modifiers == 0)
+				return KeyStroke.getKeyStroke(ch);
+			else
+				return KeyStroke.getKeyStroke(ch,modifiers);
+		}
+		else if(key.length() == 0)
+		{
+			System.err.println("Invalid key stroke: " + keyStroke);
+			return null;
+		}
+		else
+		{
+			int ch;
+
+			try
+			{
+				ch = KeyEvent.class.getField("VK_".concat(key))
+					.getInt(null);
+			}
+			catch(Exception e)
+			{
+				System.err.println("Invalid key stroke: "
+					+ keyStroke);
+				return null;
+			}
+
+			return KeyStroke.getKeyStroke(ch,modifiers);
+		}
+	}
+
+	// private members
+	private Hashtable bindings;
+	private Hashtable currentBindings;
+
+	private DefaultInputHandler(DefaultInputHandler copy)
+	{
+		bindings = currentBindings = copy.bindings;
+	}
+}

Added: xmlgraphics/batik/trunk/sources/org/gjt/sp/jedit/textarea/InputHandler.java
URL: http://svn.apache.org/viewvc/xmlgraphics/batik/trunk/sources/org/gjt/sp/jedit/textarea/InputHandler.java?rev=594367&view=auto
==============================================================================
--- xmlgraphics/batik/trunk/sources/org/gjt/sp/jedit/textarea/InputHandler.java (added)
+++ xmlgraphics/batik/trunk/sources/org/gjt/sp/jedit/textarea/InputHandler.java Mon Nov 12 16:40:53 2007
@@ -0,0 +1,1070 @@
+/*
+ * InputHandler.java - Manages key bindings and executes actions
+ * Copyright (C) 1999 Slava Pestov
+ *
+ * You may use and modify this package for any purpose. Redistribution is
+ * permitted, in both source and binary form, provided that this notice
+ * remains intact in all source distributions of this package.
+ */
+package org.gjt.sp.jedit.textarea;
+
+import javax.swing.text.*;
+import javax.swing.JPopupMenu;
+import java.awt.event.*;
+import java.awt.Component;
+import java.util.*;
+
+/**
+ * An input handler converts the user's key strokes into concrete actions.
+ * It also takes care of macro recording and action repetition.<p>
+ *
+ * This class provides all the necessary support code for an input
+ * handler, but doesn't actually do any key binding logic. It is up
+ * to the implementations of this class to do so.
+ *
+ * @author Slava Pestov
+ * @version $Id: InputHandler.java,v 1.14 1999/12/13 03:40:30 sp Exp $
+ * @see org.gjt.sp.jedit.textarea.DefaultInputHandler
+ */
+public abstract class InputHandler extends KeyAdapter
+{
+	/**
+	 * If this client property is set to Boolean.TRUE on the text area,
+	 * the home/end keys will support 'smart' BRIEF-like behaviour
+	 * (one press = start/end of line, two presses = start/end of
+	 * viewscreen, three presses = start/end of document). By default,
+	 * this property is not set.
+	 */
+	public static final String SMART_HOME_END_PROPERTY = "InputHandler.homeEnd";
+
+	public static final ActionListener BACKSPACE = new backspace();
+	public static final ActionListener BACKSPACE_WORD = new backspace_word();
+	public static final ActionListener DELETE = new delete();
+	public static final ActionListener DELETE_WORD = new delete_word();
+	public static final ActionListener END = new end(false);
+	public static final ActionListener DOCUMENT_END = new document_end(false);
+	public static final ActionListener SELECT_END = new end(true);
+	public static final ActionListener SELECT_DOC_END = new document_end(true);
+	public static final ActionListener INSERT_BREAK = new insert_break();
+	public static final ActionListener INSERT_TAB = new insert_tab();
+	public static final ActionListener HOME = new home(false);
+	public static final ActionListener DOCUMENT_HOME = new document_home(false);
+	public static final ActionListener SELECT_HOME = new home(true);
+	public static final ActionListener SELECT_DOC_HOME = new document_home(true);
+	public static final ActionListener NEXT_CHAR = new next_char(false);
+	public static final ActionListener NEXT_LINE = new next_line(false);
+	public static final ActionListener NEXT_PAGE = new next_page(false);
+	public static final ActionListener NEXT_WORD = new next_word(false);
+	public static final ActionListener SELECT_NEXT_CHAR = new next_char(true);
+	public static final ActionListener SELECT_NEXT_LINE = new next_line(true);
+	public static final ActionListener SELECT_NEXT_PAGE = new next_page(true);
+	public static final ActionListener SELECT_NEXT_WORD = new next_word(true);
+	public static final ActionListener OVERWRITE = new overwrite();
+	public static final ActionListener PREV_CHAR = new prev_char(false);
+	public static final ActionListener PREV_LINE = new prev_line(false);
+	public static final ActionListener PREV_PAGE = new prev_page(false);
+	public static final ActionListener PREV_WORD = new prev_word(false);
+	public static final ActionListener SELECT_PREV_CHAR = new prev_char(true);
+	public static final ActionListener SELECT_PREV_LINE = new prev_line(true);
+	public static final ActionListener SELECT_PREV_PAGE = new prev_page(true);
+	public static final ActionListener SELECT_PREV_WORD = new prev_word(true);
+	public static final ActionListener REPEAT = new repeat();
+	public static final ActionListener TOGGLE_RECT = new toggle_rect();
+
+	// Default action
+	public static final ActionListener INSERT_CHAR = new insert_char();
+
+	private static Hashtable actions;
+
+	static
+	{
+		actions = new Hashtable();
+		actions.put("backspace",BACKSPACE);
+		actions.put("backspace-word",BACKSPACE_WORD);
+		actions.put("delete",DELETE);
+		actions.put("delete-word",DELETE_WORD);
+		actions.put("end",END);
+		actions.put("select-end",SELECT_END);
+		actions.put("document-end",DOCUMENT_END);
+		actions.put("select-doc-end",SELECT_DOC_END);
+		actions.put("insert-break",INSERT_BREAK);
+		actions.put("insert-tab",INSERT_TAB);
+		actions.put("home",HOME);
+		actions.put("select-home",SELECT_HOME);
+		actions.put("document-home",DOCUMENT_HOME);
+		actions.put("select-doc-home",SELECT_DOC_HOME);
+		actions.put("next-char",NEXT_CHAR);
+		actions.put("next-line",NEXT_LINE);
+		actions.put("next-page",NEXT_PAGE);
+		actions.put("next-word",NEXT_WORD);
+		actions.put("select-next-char",SELECT_NEXT_CHAR);
+		actions.put("select-next-line",SELECT_NEXT_LINE);
+		actions.put("select-next-page",SELECT_NEXT_PAGE);
+		actions.put("select-next-word",SELECT_NEXT_WORD);
+		actions.put("overwrite",OVERWRITE);
+		actions.put("prev-char",PREV_CHAR);
+		actions.put("prev-line",PREV_LINE);
+		actions.put("prev-page",PREV_PAGE);
+		actions.put("prev-word",PREV_WORD);
+		actions.put("select-prev-char",SELECT_PREV_CHAR);
+		actions.put("select-prev-line",SELECT_PREV_LINE);
+		actions.put("select-prev-page",SELECT_PREV_PAGE);
+		actions.put("select-prev-word",SELECT_PREV_WORD);
+		actions.put("repeat",REPEAT);
+		actions.put("toggle-rect",TOGGLE_RECT);
+		actions.put("insert-char",INSERT_CHAR);
+	}
+
+	/**
+	 * Returns a named text area action.
+	 * @param name The action name
+	 */
+	public static ActionListener getAction(String name)
+	{
+		return (ActionListener)actions.get(name);
+	}
+
+	/**
+	 * Returns the name of the specified text area action.
+	 * @param listener The action
+	 */
+	public static String getActionName(ActionListener listener)
+	{
+		Enumeration e = getActions();
+		while(e.hasMoreElements())
+		{
+			String name = (String)e.nextElement();
+			ActionListener _listener = getAction(name);
+			if(_listener == listener)
+				return name;
+		}
+		return null;
+	}
+
+	/**
+	 * Returns an enumeration of all available actions.
+	 */
+	public static Enumeration getActions()
+	{
+		return actions.keys();
+	}
+
+	/**
+	 * Adds the default key bindings to this input handler.
+	 * This should not be called in the constructor of this
+	 * input handler, because applications might load the
+	 * key bindings from a file, etc.
+	 */
+	public abstract void addDefaultKeyBindings();
+
+	/**
+	 * Adds a key binding to this input handler.
+	 * @param keyBinding The key binding (the format of this is
+	 * input-handler specific)
+	 * @param action The action
+	 */
+	public abstract void addKeyBinding(String keyBinding, ActionListener action);
+
+	/**
+	 * Removes a key binding from this input handler.
+	 * @param keyBinding The key binding
+	 */
+	public abstract void removeKeyBinding(String keyBinding);
+
+	/**
+	 * Removes all key bindings from this input handler.
+	 */
+	public abstract void removeAllKeyBindings();
+
+	/**
+	 * Grabs the next key typed event and invokes the specified
+	 * action with the key as a the action command.
+	 * @param action The action
+	 */
+	public void grabNextKeyStroke(ActionListener listener)
+	{
+		grabAction = listener;
+	}
+
+	/**
+	 * Returns if repeating is enabled. When repeating is enabled,
+	 * actions will be executed multiple times. This is usually
+	 * invoked with a special key stroke in the input handler.
+	 */
+	public boolean isRepeatEnabled()
+	{
+		return repeat;
+	}
+
+	/**
+	 * Enables repeating. When repeating is enabled, actions will be
+	 * executed multiple times. Once repeating is enabled, the input
+	 * handler should read a number from the keyboard.
+	 */
+	public void setRepeatEnabled(boolean repeat)
+	{
+		this.repeat = repeat;
+	}
+
+	/**
+	 * Returns the number of times the next action will be repeated.
+	 */
+	public int getRepeatCount()
+	{
+		return (repeat ? Math.max(1,repeatCount) : 1);
+	}
+
+	/**
+	 * Sets the number of times the next action will be repeated.
+	 * @param repeatCount The repeat count
+	 */
+	public void setRepeatCount(int repeatCount)
+	{
+		this.repeatCount = repeatCount;
+	}
+
+	/**
+	 * Returns the macro recorder. If this is non-null, all executed
+	 * actions should be forwarded to the recorder.
+	 */
+	public InputHandler.MacroRecorder getMacroRecorder()
+	{
+		return recorder;
+	}
+
+	/**
+	 * Sets the macro recorder. If this is non-null, all executed
+	 * actions should be forwarded to the recorder.
+	 * @param recorder The macro recorder
+	 */
+	public void setMacroRecorder(InputHandler.MacroRecorder recorder)
+	{
+		this.recorder = recorder;
+	}
+
+	/**
+	 * Returns a copy of this input handler that shares the same
+	 * key bindings. Setting key bindings in the copy will also
+	 * set them in the original.
+	 */
+	public abstract InputHandler copy();
+
+	/**
+	 * Executes the specified action, repeating and recording it as
+	 * necessary.
+	 * @param listener The action listener
+	 * @param source The event source
+	 * @param actionCommand The action command
+	 */
+	public void executeAction(ActionListener listener, Object source,
+		String actionCommand)
+	{
+		// create event
+		ActionEvent evt = new ActionEvent(source,
+			ActionEvent.ACTION_PERFORMED,
+			actionCommand);
+
+		// don't do anything if the action is a wrapper
+		// (like EditAction.Wrapper)
+		if(listener instanceof Wrapper)
+		{
+			listener.actionPerformed(evt);
+			return;
+		}
+
+		// remember old values, in case action changes them
+		boolean _repeat = repeat;
+		int _repeatCount = getRepeatCount();
+
+		// execute the action
+		if(listener instanceof InputHandler.NonRepeatable)
+			listener.actionPerformed(evt);
+		else
+		{
+			for(int i = 0; i < Math.max(1,repeatCount); i++)
+				listener.actionPerformed(evt);
+		}
+
+		// do recording. Notice that we do no recording whatsoever
+		// for actions that grab keys
+		if(grabAction == null)
+		{
+			if(recorder != null)
+			{
+				if(!(listener instanceof InputHandler.NonRecordable))
+				{
+					if(_repeatCount != 1)
+						recorder.actionPerformed(REPEAT,String.valueOf(_repeatCount));
+
+					recorder.actionPerformed(listener,actionCommand);
+				}
+			}
+
+			// If repeat was true originally, clear it
+			// Otherwise it might have been set by the action, etc
+			if(_repeat)
+			{
+				repeat = false;
+				repeatCount = 0;
+			}
+		}
+	}
+
+	/**
+	 * Returns the text area that fired the specified event.
+	 * @param evt The event
+	 */
+	public static JEditTextArea getTextArea(EventObject evt)
+	{
+		if(evt != null)
+		{
+			Object o = evt.getSource();
+			if(o instanceof Component)
+			{
+				// find the parent text area
+				Component c = (Component)o;
+				for(;;)
+				{
+					if(c instanceof JEditTextArea)
+						return (JEditTextArea)c;
+					else if(c == null)
+						break;
+					if(c instanceof JPopupMenu)
+						c = ((JPopupMenu)c)
+							.getInvoker();
+					else
+						c = c.getParent();
+				}
+			}
+		}
+
+		// this shouldn't happen
+		System.err.println("BUG: getTextArea() returning null");
+		System.err.println("Report this to Slava Pestov <sp...@gjt.org>");
+		return null;
+	}
+
+	// protected members
+
+	/**
+	 * If a key is being grabbed, this method should be called with
+	 * the appropriate key event. It executes the grab action with
+	 * the typed character as the parameter.
+	 */
+	protected void handleGrabAction(KeyEvent evt)
+	{
+		// Clear it *before* it is executed so that executeAction()
+		// resets the repeat count
+		ActionListener _grabAction = grabAction;
+		grabAction = null;
+		executeAction(_grabAction,evt.getSource(),
+			String.valueOf(evt.getKeyChar()));
+	}
+
+	// protected members
+	protected ActionListener grabAction;
+	protected boolean repeat;
+	protected int repeatCount;
+	protected InputHandler.MacroRecorder recorder;
+
+	/**
+	 * If an action implements this interface, it should not be repeated.
+	 * Instead, it will handle the repetition itself.
+	 */
+	public interface NonRepeatable {}
+
+	/**
+	 * If an action implements this interface, it should not be recorded
+	 * by the macro recorder. Instead, it will do its own recording.
+	 */
+	public interface NonRecordable {}
+
+	/**
+	 * For use by EditAction.Wrapper only.
+	 * @since jEdit 2.2final
+	 */
+	public interface Wrapper {}
+
+	/**
+	 * Macro recorder.
+	 */
+	public interface MacroRecorder
+	{
+		void actionPerformed(ActionListener listener,
+			String actionCommand);
+	}
+
+	public static class backspace implements ActionListener
+	{
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+
+			if(!textArea.isEditable())
+			{
+				textArea.getToolkit().beep();
+				return;
+			}
+
+			if(textArea.getSelectionStart()
+			   != textArea.getSelectionEnd())
+			{
+				textArea.setSelectedText("");
+			}
+			else
+			{
+				int caret = textArea.getCaretPosition();
+				if(caret == 0)
+				{
+					textArea.getToolkit().beep();
+					return;
+				}
+				try
+				{
+					textArea.getDocument().remove(caret - 1,1);
+				}
+				catch(BadLocationException bl)
+				{
+					bl.printStackTrace();
+				}
+			}
+		}
+	}
+
+	public static class backspace_word implements ActionListener
+	{
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			int start = textArea.getSelectionStart();
+			if(start != textArea.getSelectionEnd())
+			{
+				textArea.setSelectedText("");
+			}
+
+			int line = textArea.getCaretLine();
+			int lineStart = textArea.getLineStartOffset(line);
+			int caret = start - lineStart;
+
+			String lineText = textArea.getLineText(textArea
+				.getCaretLine());
+
+			if(caret == 0)
+			{
+				if(lineStart == 0)
+				{
+					textArea.getToolkit().beep();
+					return;
+				}
+				caret--;
+			}
+			else
+			{
+				String noWordSep = (String)textArea.getDocument().getProperty("noWordSep");
+				caret = TextUtilities.findWordStart(lineText,caret,noWordSep);
+			}
+
+			try
+			{
+				textArea.getDocument().remove(
+						caret + lineStart,
+						start - (caret + lineStart));
+			}
+			catch(BadLocationException bl)
+			{
+				bl.printStackTrace();
+			}
+		}
+	}
+
+	public static class delete implements ActionListener
+	{
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+
+			if(!textArea.isEditable())
+			{
+				textArea.getToolkit().beep();
+				return;
+			}
+
+			if(textArea.getSelectionStart()
+			   != textArea.getSelectionEnd())
+			{
+				textArea.setSelectedText("");
+			}
+			else
+			{
+				int caret = textArea.getCaretPosition();
+				if(caret == textArea.getDocumentLength())
+				{
+					textArea.getToolkit().beep();
+					return;
+				}
+				try
+				{
+					textArea.getDocument().remove(caret,1);
+				}
+				catch(BadLocationException bl)
+				{
+					bl.printStackTrace();
+				}
+			}
+		}
+	}
+
+	public static class delete_word implements ActionListener
+	{
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			int start = textArea.getSelectionStart();
+			if(start != textArea.getSelectionEnd())
+			{
+				textArea.setSelectedText("");
+			}
+
+			int line = textArea.getCaretLine();
+			int lineStart = textArea.getLineStartOffset(line);
+			int caret = start - lineStart;
+
+			String lineText = textArea.getLineText(textArea
+				.getCaretLine());
+
+			if(caret == lineText.length())
+			{
+				if(lineStart + caret == textArea.getDocumentLength())
+				{
+					textArea.getToolkit().beep();
+					return;
+				}
+				caret++;
+			}
+			else
+			{
+				String noWordSep = (String)textArea.getDocument().getProperty("noWordSep");
+				caret = TextUtilities.findWordEnd(lineText,caret,noWordSep);
+			}
+
+			try
+			{
+				textArea.getDocument().remove(start,
+					(caret + lineStart) - start);
+			}
+			catch(BadLocationException bl)
+			{
+				bl.printStackTrace();
+			}
+		}
+	}
+
+	public static class end implements ActionListener
+	{
+		private boolean select;
+
+		public end(boolean select)
+		{
+			this.select = select;
+		}
+
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+
+			int caret = textArea.getCaretPosition();
+
+			int lastOfLine = textArea.getLineEndOffset(
+				textArea.getCaretLine()) - 1;
+			int lastVisibleLine = textArea.getFirstLine()
+				+ textArea.getVisibleLines();
+			if(lastVisibleLine >= textArea.getLineCount())
+			{
+				lastVisibleLine = Math.min(textArea.getLineCount() - 1,
+					lastVisibleLine);
+			}
+			else
+				lastVisibleLine -= (textArea.getElectricScroll() + 1);
+
+			int lastVisible = textArea.getLineEndOffset(lastVisibleLine) - 1;
+			int lastDocument = textArea.getDocumentLength();
+
+			if(caret == lastDocument)
+			{
+				textArea.getToolkit().beep();
+				return;
+			}
+			else if(!Boolean.TRUE.equals(textArea.getClientProperty(
+				SMART_HOME_END_PROPERTY)))
+				caret = lastOfLine;
+			else if(caret == lastVisible)
+				caret = lastDocument;
+			else if(caret == lastOfLine)
+				caret = lastVisible;
+			else
+				caret = lastOfLine;
+
+			if(select)
+				textArea.select(textArea.getMarkPosition(),caret);
+			else
+				textArea.setCaretPosition(caret);
+		}
+	}
+
+	public static class document_end implements ActionListener
+	{
+		private boolean select;
+
+		public document_end(boolean select)
+		{
+			this.select = select;
+		}
+
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			if(select)
+				textArea.select(textArea.getMarkPosition(),
+					textArea.getDocumentLength());
+			else
+				textArea.setCaretPosition(textArea
+					.getDocumentLength());
+		}
+	}
+
+	public static class home implements ActionListener
+	{
+		private boolean select;
+
+		public home(boolean select)
+		{
+			this.select = select;
+		}
+
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+
+			int caret = textArea.getCaretPosition();
+
+			int firstLine = textArea.getFirstLine();
+
+			int firstOfLine = textArea.getLineStartOffset(
+				textArea.getCaretLine());
+			int firstVisibleLine = (firstLine == 0 ? 0 :
+				firstLine + textArea.getElectricScroll());
+			int firstVisible = textArea.getLineStartOffset(
+				firstVisibleLine);
+
+			if(caret == 0)
+			{
+				textArea.getToolkit().beep();
+				return;
+			}
+			else if(!Boolean.TRUE.equals(textArea.getClientProperty(
+				SMART_HOME_END_PROPERTY)))
+				caret = firstOfLine;
+			else if(caret == firstVisible)
+				caret = 0;
+			else if(caret == firstOfLine)
+				caret = firstVisible;
+			else
+				caret = firstOfLine;
+
+			if(select)
+				textArea.select(textArea.getMarkPosition(),caret);
+			else
+				textArea.setCaretPosition(caret);
+		}
+	}
+
+	public static class document_home implements ActionListener
+	{
+		private boolean select;
+
+		public document_home(boolean select)
+		{
+			this.select = select;
+		}
+
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			if(select)
+				textArea.select(textArea.getMarkPosition(),0);
+			else
+				textArea.setCaretPosition(0);
+		}
+	}
+
+	public static class insert_break implements ActionListener
+	{
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+
+			if(!textArea.isEditable())
+			{
+				textArea.getToolkit().beep();
+				return;
+			}
+
+			textArea.setSelectedText("\n");
+		}
+	}
+
+	public static class insert_tab implements ActionListener
+	{
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+
+			if(!textArea.isEditable())
+			{
+				textArea.getToolkit().beep();
+				return;
+			}
+
+			textArea.overwriteSetSelectedText("\t");
+		}
+	}
+
+	public static class next_char implements ActionListener
+	{
+		private boolean select;
+
+		public next_char(boolean select)
+		{
+			this.select = select;
+		}
+
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			int caret = textArea.getCaretPosition();
+			if(caret == textArea.getDocumentLength())
+			{
+				textArea.getToolkit().beep();
+				return;
+			}
+
+			if(select)
+				textArea.select(textArea.getMarkPosition(),
+					caret + 1);
+			else
+				textArea.setCaretPosition(caret + 1);
+		}
+	}
+
+	public static class next_line implements ActionListener
+	{
+		private boolean select;
+
+		public next_line(boolean select)
+		{
+			this.select = select;
+		}
+
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			int caret = textArea.getCaretPosition();
+			int line = textArea.getCaretLine();
+
+			if(line == textArea.getLineCount() - 1)
+			{
+				textArea.getToolkit().beep();
+				return;
+			}
+
+			int magic = textArea.getMagicCaretPosition();
+			if(magic == -1)
+			{
+				magic = textArea.offsetToX(line,
+					caret - textArea.getLineStartOffset(line));
+			}
+
+			caret = textArea.getLineStartOffset(line + 1)
+				+ textArea.xToOffset(line + 1,magic);
+			if(select)
+				textArea.select(textArea.getMarkPosition(),caret);
+			else
+				textArea.setCaretPosition(caret);
+			textArea.setMagicCaretPosition(magic);
+		}
+	}
+
+	public static class next_page implements ActionListener
+	{
+		private boolean select;
+
+		public next_page(boolean select)
+		{
+			this.select = select;
+		}
+
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			int lineCount = textArea.getLineCount();
+			int firstLine = textArea.getFirstLine();
+			int visibleLines = textArea.getVisibleLines();
+			int line = textArea.getCaretLine();
+
+			firstLine += visibleLines;
+
+			if(firstLine + visibleLines >= lineCount - 1)
+				firstLine = lineCount - visibleLines;
+
+			textArea.setFirstLine(firstLine);
+
+			int caret = textArea.getLineStartOffset(
+				Math.min(textArea.getLineCount() - 1,
+				line + visibleLines));
+			if(select)
+				textArea.select(textArea.getMarkPosition(),caret);
+			else
+				textArea.setCaretPosition(caret);
+		}
+	}
+
+	public static class next_word implements ActionListener
+	{
+		private boolean select;
+
+		public next_word(boolean select)
+		{
+			this.select = select;
+		}
+
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			int caret = textArea.getCaretPosition();
+			int line = textArea.getCaretLine();
+			int lineStart = textArea.getLineStartOffset(line);
+			caret -= lineStart;
+
+			String lineText = textArea.getLineText(textArea
+				.getCaretLine());
+
+			if(caret == lineText.length())
+			{
+				if(lineStart + caret == textArea.getDocumentLength())
+				{
+					textArea.getToolkit().beep();
+					return;
+				}
+				caret++;
+			}
+			else
+			{
+				String noWordSep = (String)textArea.getDocument().getProperty("noWordSep");
+				caret = TextUtilities.findWordEnd(lineText,caret,noWordSep);
+			}
+
+			if(select)
+				textArea.select(textArea.getMarkPosition(),
+					lineStart + caret);
+			else
+				textArea.setCaretPosition(lineStart + caret);
+		}
+	}
+
+	public static class overwrite implements ActionListener
+	{
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			textArea.setOverwriteEnabled(
+				!textArea.isOverwriteEnabled());
+		}
+	}
+
+	public static class prev_char implements ActionListener
+	{
+		private boolean select;
+
+		public prev_char(boolean select)
+		{
+			this.select = select;
+		}
+
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			int caret = textArea.getCaretPosition();
+			if(caret == 0)
+			{
+				textArea.getToolkit().beep();
+				return;
+			}
+
+			if(select)
+				textArea.select(textArea.getMarkPosition(),
+					caret - 1);
+			else
+				textArea.setCaretPosition(caret - 1);
+		}
+	}
+
+	public static class prev_line implements ActionListener
+	{
+		private boolean select;
+
+		public prev_line(boolean select)
+		{
+			this.select = select;
+		}
+
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			int caret = textArea.getCaretPosition();
+			int line = textArea.getCaretLine();
+
+			if(line == 0)
+			{
+				textArea.getToolkit().beep();
+				return;
+			}
+
+			int magic = textArea.getMagicCaretPosition();
+			if(magic == -1)
+			{
+				magic = textArea.offsetToX(line,
+					caret - textArea.getLineStartOffset(line));
+			}
+
+			caret = textArea.getLineStartOffset(line - 1)
+				+ textArea.xToOffset(line - 1,magic);
+			if(select)
+				textArea.select(textArea.getMarkPosition(),caret);
+			else
+				textArea.setCaretPosition(caret);
+			textArea.setMagicCaretPosition(magic);
+		}
+	}
+
+	public static class prev_page implements ActionListener
+	{
+		private boolean select;
+
+		public prev_page(boolean select)
+		{
+			this.select = select;
+		}
+
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			int firstLine = textArea.getFirstLine();
+			int visibleLines = textArea.getVisibleLines();
+			int line = textArea.getCaretLine();
+
+			if(firstLine < visibleLines)
+				firstLine = visibleLines;
+
+			textArea.setFirstLine(firstLine - visibleLines);
+
+			int caret = textArea.getLineStartOffset(
+				Math.max(0,line - visibleLines));
+			if(select)
+				textArea.select(textArea.getMarkPosition(),caret);
+			else
+				textArea.setCaretPosition(caret);
+		}
+	}
+
+	public static class prev_word implements ActionListener
+	{
+		private boolean select;
+
+		public prev_word(boolean select)
+		{
+			this.select = select;
+		}
+
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			int caret = textArea.getCaretPosition();
+			int line = textArea.getCaretLine();
+			int lineStart = textArea.getLineStartOffset(line);
+			caret -= lineStart;
+
+			String lineText = textArea.getLineText(textArea
+				.getCaretLine());
+
+			if(caret == 0)
+			{
+				if(lineStart == 0)
+				{
+					textArea.getToolkit().beep();
+					return;
+				}
+				caret--;
+			}
+			else
+			{
+				String noWordSep = (String)textArea.getDocument().getProperty("noWordSep");
+				caret = TextUtilities.findWordStart(lineText,caret,noWordSep);
+			}
+
+			if(select)
+				textArea.select(textArea.getMarkPosition(),
+					lineStart + caret);
+			else
+				textArea.setCaretPosition(lineStart + caret);
+		}
+	}
+
+	public static class repeat implements ActionListener,
+		InputHandler.NonRecordable
+	{
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			textArea.getInputHandler().setRepeatEnabled(true);
+			String actionCommand = evt.getActionCommand();
+			if(actionCommand != null)
+			{
+				textArea.getInputHandler().setRepeatCount(
+					Integer.parseInt(actionCommand));
+			}
+		}
+	}
+
+	public static class toggle_rect implements ActionListener
+	{
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			textArea.setSelectionRectangular(
+				!textArea.isSelectionRectangular());
+		}
+	}
+
+	public static class insert_char implements ActionListener,
+		InputHandler.NonRepeatable
+	{
+		public void actionPerformed(ActionEvent evt)
+		{
+			JEditTextArea textArea = getTextArea(evt);
+			String str = evt.getActionCommand();
+			int repeatCount = textArea.getInputHandler().getRepeatCount();
+
+			if(textArea.isEditable())
+			{
+				StringBuffer buf = new StringBuffer();
+				for(int i = 0; i < repeatCount; i++)
+					buf.append(str);
+				textArea.overwriteSetSelectedText(buf.toString());
+			}
+			else
+			{
+				textArea.getToolkit().beep();
+			}
+		}
+	}
+}