JCardPane

Aus Byte-Welt Wiki
Zur Navigation springenZur Suche springen

JCardPaneDemo.jpg

/*
 * JCardPane.java
 * wraps a JTabbedPane, hiding the tabs.
 * The main method shows an example.
 */
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.util.*;
import java.util.logging.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;

public class JCardPane extends JComponent {

    private JTabbedPane pane;
    private CardAction nextAction;
    private CardAction previousAction;
    private TabFocusHandler tabFocusHandler;
    private TabbedPaneUI uiNoTabs = new BasicTabbedPaneUI() {

        @Override
        protected int calculateTabAreaHeight(int tabPlacement,
                int horizRunCount, int maxTabHeight) {
            return 0;
        }

        @Override
        protected Insets getContentBorderInsets(int tabPlacement) {
            return new Insets(0, 0, 0, 0);
        }

        @Override
        protected MouseListener createMouseListener() {
            return null;
        }

        @Override
        protected void installKeyboardActions() {
        }

        @Override
        protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
            return 0;
        }
    };

    /**
     * Creates an empty cardpane
     */
    public JCardPane() {
        pane = new JTabbedPane();
        tabFocusHandler = new TabFocusHandler(pane);
        hideTabs();
        pane.addChangeListener(new ChangeListener() {

            @Override
            public void stateChanged(final ChangeEvent e) {
                if (nextAction != null) {
                    nextAction.setEnabled(hasNext());
                }
                if (previousAction != null) {
                    previousAction.setEnabled(hasPrevious());
                }
            }
        });
        setLayout(new BorderLayout());
        super.add(pane);
    }

    /**
     * Flips to the first card of the cardpane.
     * @return 0 if this cardpane has a card
     * or -1 if this cardpane has no card
     */
    public int first() {
        return show(0);
    }

    /**
     * Flips to the last card of the cardpane.
     * @return 0 if this cardpane has a card
     * or -1 if this cardpane has no card
     */
    public int last() {
        return show(-1);
    }

    /**
     * Flips to the next card of the cardpane.
     * If the currently visible card is the last one,
     * this method flips to the first card of the cardpane.
     * @return the index of the card
     * or -1 if this cardpane has no card
     */
    public int next() {
        return show(pane.getSelectedIndex() + 1);
    }

    /**
     * Flips to the previous card of the cardpane.
     * If the currently visible card is the first one,
     * this method flips to the last card of the cardpane.
     * @return the index of the card
     * or -1 if this cardpane has no card
     */
    public int previous() {
        return show(pane.getSelectedIndex() - 1);
    }

    /**
     * Flips to the card at the specified index.
     * If the index is positive but does not exist,
     * this method flips to the first card of the cardpane.
     * If the index is negative,
     * this method flips to the last card of the cardpane.
     * @return the index of the card
     * or -1 if this cardpane has no card
     */
    public int show(final int index) {
        if (pane.getTabCount() == 0) {
            return -1;
        }
        if (index >= pane.getTabCount()) {
            pane.setSelectedIndex(0);
            return 0;

        }
        if (index < 0) {
            pane.setSelectedIndex(pane.getTabCount() - 1);
            return pane.getTabCount() - 1;

        }
        pane.setSelectedIndex(index);
        return index;
    }

    /**
     * Flips to the card with the specified name.
     * If the name does not exist, nothing happens.
     * @return the index of the card
     * or -1 if the name does not exist
     */
    public int show(final String name) {
        int index = pane.indexOfTab(name);
        if (index >= 0) {
            pane.setSelectedIndex(index);
        }
        return index;
    }

    /**
     * Flips to the specified card.
     * If the card does not exist in the cardpane, it is added to it.
     * @return the index of the specified card
     */
    public int show(final JComponent card) {
        int index = pane.indexOfComponent(card);
        if (index < 0) {
            index = pane.getTabCount();
            pane.addTab(card.getName(), card);
        }
        pane.setSelectedIndex(index);
        return index;
    }

    /**
     * Flips to the specified card.
     * If the card does not exist in the cardpane, it is added to it.
     * @return the index of the specified card
     */
    public int add(final JComponent card) {
        return show(card);
    }

    /**
     * Inserts the specified card at the sepcified index and
     * flips to the specified card.
     */
    public void insertAt(JComponent card, int index){
        pane.insertTab(null, null, card, null, index);
        pane.setSelectedIndex(index);
    }

    /**
     * @return true if there is a card after the visible one
     */
    public boolean hasNext() {
        return pane.getSelectedIndex() < pane.getComponentCount() - 1;
    }

    /**
     * @return true if there is a card before the visible one
     */
    public boolean hasPrevious() {
        return pane.getSelectedIndex() > 0;
    }

    /**
     * @return the visible card
     */
    public Component getCurrent() {
        return pane.getSelectedComponent();
    }

    /**
     * Adds a <code>ChangeListener</code> to this cardpane.
     */
    public void addChangeListener(final ChangeListener l) {
        pane.addChangeListener(l);
    }

    /**
     * Removes a <code>ChangeListener</code> from this cardpane.
     */
    public void removeChangeListener(final ChangeListener l) {
        pane.removeChangeListener(l);
    }

    /**
     * gets the action to be used for a "Next" button
     */
    public Action getNextAction(final String name) {
        if (nextAction == null) {
            nextAction = new CardAction(name, true);
            nextAction.putValue(Action.MNEMONIC_KEY, (int) name.charAt(0));
            nextAction.setEnabled(hasNext());
        }
        return nextAction;
    }

    /**
     * gets the action to be used for a "Previous" button
     */
    public Action getPreviousAction(final String name) {
        if (previousAction == null) {
            previousAction = new CardAction(name, false);
            previousAction.putValue(Action.MNEMONIC_KEY, (int) name.charAt(0));
            previousAction.setEnabled(hasPrevious());
        }
        return previousAction;
    }

    /**
     *
     * retain: if true, the component focused within a card will remain focused
     * each time it becomes visible, otherwise the first component of each card is focused
     */
    public void retainFocus(final boolean retain) {
        int focusPolicy = TabFocusHandler.RESET_FOCUS;
        if (retain) {
            focusPolicy = TabFocusHandler.RETAIN_FOCUS;
        }
        tabFocusHandler.uninstall();
        tabFocusHandler = new TabFocusHandler(pane, focusPolicy);
    }

    private void hideTabs() {
        pane.setUI(uiNoTabs);
    }

    private class CardAction extends AbstractAction {

        private boolean isNext;

        CardAction(final String text, final boolean isNext) {
            super(text);
            this.isNext = isNext;
            putValue(Action.SHORT_DESCRIPTION, getValue(Action.NAME));
        }

        @Override
        public void actionPerformed(final ActionEvent e) {
            if (isNext) {
                next();
            } else {
                previous();
            }
        }
    }

    /**
     *  Manage the focus when a new tab is selected. You can select a focus policy:
     *  a) Reset Focus - focus is reset to the first focusable component on the tab
     *  b) Retain Focus - focus returns to the last component with focus on the tab
     *
     *  In addition you add tabs that you want to exclude from the focus policy,
     *  in which case the other policy will be in effect.
     */
    private class TabFocusHandler {

        public final static int RESET_FOCUS = 0;
        public final static int RETAIN_FOCUS = 1;
        private HashMap<Component, Component> tabFocus = new HashMap<Component, Component>(16);
        private HashSet<Component> exceptions;
        private JTabbedPane tabbedPane;
        private int focusPolicy;
        private final KeyboardFocusManager focusManager;
        private final ChangeListener changeListener;
        private final PropertyChangeListener propertyChangeListener;

        /**
         *  Create with the default Retain Focus policy
         */
        TabFocusHandler(final JTabbedPane tabbedPane) {
            this(tabbedPane, RETAIN_FOCUS);
        }

        /**
         *  Create using the specified focus policy
         */
        TabFocusHandler(final JTabbedPane tabbedPane, final int focusPolicy) {
            if (focusPolicy != RESET_FOCUS && focusPolicy != RETAIN_FOCUS) {
                throw new IllegalArgumentException("Invalid focus policy");
            }
            this.tabbedPane = tabbedPane;
            this.focusPolicy = focusPolicy;
            //  Add listeners to manage a tab change
            changeListener = new TabChangeListener();
            propertyChangeListener = new TabPropertyChangeListener();
            tabbedPane.addChangeListener(changeListener);
            focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
            focusManager.addPropertyChangeListener("permanentFocusOwner", propertyChangeListener);
        }

        public void uninstall() {
            tabbedPane.removeChangeListener(changeListener);
            focusManager.removePropertyChangeListener(propertyChangeListener);
        }

        /**
         *  Specify a tab with an exception to the focus policy rule
         */
        public void addException(final int index) {
            if (exceptions == null) {
                exceptions = new HashSet<Component>(16);
            }
            Component key = tabbedPane.getComponentAt(index);
            exceptions.add(key);
        }

        private class TabPropertyChangeListener implements PropertyChangeListener {

            TabPropertyChangeListener() {
            }

            /**
             *  Track focus changes and update the current focus component
             *  for the current tab
             */
            @Override
            public void propertyChange(final PropertyChangeEvent e) {
                //  No need to track focus change
                if (exceptions == null && focusPolicy == RESET_FOCUS) {
                    return;
                }
                Component key = tabbedPane.getComponentAt(tabbedPane.getSelectedIndex());
                if (exceptions != null) {
                    if (focusPolicy == RESET_FOCUS && !exceptions.contains(key)) {
                        return;
                    }
                    if (focusPolicy == RETAIN_FOCUS && exceptions.contains(key)) {
                        return;
                    }
                }
                Component value = (Component) e.getNewValue();
                if (value != null && SwingUtilities.isDescendingFrom(value, key)) {
                    tabFocus.put(key, value);
                }
            }
        }

        private class TabChangeListener implements ChangeListener {

            TabChangeListener() {
            }

            /**
             * Tab has changed. Focus on saved component for the given tab.
             * When there is no saved component, focus on the first component.
             */
            @Override
            public void stateChanged(final ChangeEvent e) {
                Component key = tabbedPane.getComponentAt(tabbedPane.getSelectedIndex());
                if (key == null) {
                    return;
                }
                Component value = tabFocus.get(key);
                //  First time selecting this tab or focus policy is RESET_FOCUS
                if (value == null) {
                    key.transferFocus();
                    tabFocus.put(key, value);
                } else //  Use the saved component for focusing
                {
                    value.requestFocusInWindow();
                }
            }
        }
    }

    //for testing only:
    public static void main(final String... args) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(JCardPane.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            Logger.getLogger(JCardPane.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            Logger.getLogger(JCardPane.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnsupportedLookAndFeelException ex) {
            Logger.getLogger(JCardPane.class.getName()).log(Level.SEVERE, null, ex);
        }
        Runnable gui = new Runnable() {

            @Override
            public void run() {
                final JFrame f = new JFrame("JCardPane Demo");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setSize(400, 150);
                f.setLocationRelativeTo(null);
                final JCardPane cardPane = new JCardPane();
                f.add(cardPane, BorderLayout.CENTER);
                class Card extends JPanel {

                    Card(final String name) {
                        JTextField tf = new JTextField(20);
                        tf.setText(name);
                        add(tf);
                        tf = new JTextField(20);
                        tf.setText(name);
                        add(tf);
                        setName(name);
                    }
                }
                for (int i = 0; i < 5; i++) {
                    cardPane.show(new Card(String.valueOf(i + 1)));
                }
                cardPane.first();
                JPanel control = new JPanel();
                control.add(new JButton(cardPane.getPreviousAction("Previous")));
                control.add(new JButton(cardPane.getNextAction("Next")));
                f.add(control, BorderLayout.SOUTH);
                f.setVisible(true);
            }
        };
        //GUI must start on EventDispatchThread:
        SwingUtilities.invokeLater(gui);
    }
}