001    /*
002     * This file is part of muCommander, http://www.mucommander.com
003     * Copyright (C) 2002-2008 Maxence Bernard
004     *
005     * muCommander is free software; you can redistribute it and/or modify
006     * it under the terms of the GNU General Public License as published by
007     * the Free Software Foundation; either version 3 of the License, or
008     * (at your option) any later version.
009     *
010     * muCommander is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    
019    
020    package com.mucommander.ui.button;
021    
022    import com.mucommander.ui.helper.MnemonicHelper;
023    
024    import javax.swing.*;
025    import java.awt.*;
026    import java.awt.event.FocusEvent;
027    import java.awt.event.FocusListener;
028    import java.awt.event.KeyEvent;
029    import java.awt.event.KeyListener;
030    
031    
032    /**
033     * ButtonChoicePanel lays out an array of buttons on a grid and provides an easy way
034     * for the user to navigate and select buttons :
035     * <ul>
036     *  <li>At any given time, the current active button (the first one initially) can be selected by pressing ENTER
037     *  <li>LEFT key goes back to the previous button, to the last button if current button is the first one
038     *  <li>RIGHT key goes forward one button, to the first button if current button is the last one
039     *  <li>UP key goes up one row, to the last row if current button is on the first row
040     *  <li>DOWN key goes down one row, to the first row if current button is on the last row
041     *  <li>Buttons can directly be selected by pressing the Mnemonic associated with the button (if any)
042     * </ul>
043     * This does not interfere with regular focus management where TAB (resp. Shift+TAB) goes to the next (resp. previous)
044     * focusable component.
045     * 
046     * @author Maxence Bernard
047     */
048    public class ButtonChoicePanel extends JPanel implements KeyListener, FocusListener {
049    
050        /** Provided JButton instances */
051        private JButton buttons[];
052            
053        /** RootPane associated with this ButtonChoicePanel */
054        private JRootPane rootPane;
055    
056        /** Number of columns of the buttons grid */
057        private int nbCols;
058        /** Number of row of the buttons grid */
059        private int nbRows;
060    
061        /** Current button, i.e. the one that currently has focus */
062        private int currentButton;
063            
064        /** Total number of buttons */
065        private int nbButtons;
066    
067            
068        /**
069         * Creates a new ButtonChoicePanel and lays out the given buttons on a grid
070         * according to the provided number of colums.
071         *
072         * <p>Initial focus will be given to the first button.</p>
073         *
074         * @param buttons  the JButton instances to layout
075         * @param nbCols   number of columns for the buttons grid, if <=0 all buttons will be put on a single row
076         * @param rootPane associated with this ButtonChoicePanel
077         */
078        public ButtonChoicePanel(JButton buttons[], int nbCols, JRootPane rootPane) {
079            this.buttons = buttons;
080            this.nbButtons = buttons.length;
081            this.nbCols = nbCols<=0?nbButtons:nbCols;
082            this.rootPane = rootPane;
083            this.nbRows = nbCols<=0?1:nbButtons/nbCols+(nbButtons%nbCols==0?0:1);
084    
085            // If the provided number of columns is <= 0, lay out all buttons on a single row
086            // and use FlowLayout to do that
087            if (nbCols<=0) {
088                setLayout(new FlowLayout(FlowLayout.RIGHT));
089            }
090            // Use GridLayout to lay out buttons on 2-dimensional grid
091            else {
092                setLayout(new GridLayout(0, nbCols));
093            }
094            
095            JButton button;
096            for(int i=0; i<nbButtons; i++) {
097                button = buttons[i];
098    
099                // Listener to key events to transfer focus 
100                button.addKeyListener(this);
101    
102                // Allow buttons to be made 'default buttons' when enter is pressed
103                button.setDefaultCapable(true);
104    
105                // Listen to focus events to make the focused button the default button
106                button.addFocusListener(this);
107    
108                add(button);
109            }
110    
111            // Set mnemonics to buttons
112            updateMnemonics();
113    
114            // First button is made 'default button'
115            rootPane.setDefaultButton(buttons[0]);
116            this.currentButton = 0;
117        }
118    
119    
120        /**
121         * Updates the buttons mnemonics. This method should be called when at least one of the button's label has changed.
122         */
123        public void updateMnemonics() {
124            MnemonicHelper mnemonicHelper = new MnemonicHelper();
125            char mnemonic;
126    
127            JButton button;
128            for(int i=0; i<nbButtons; i++) {
129                button = buttons[i];
130                mnemonic = mnemonicHelper.getMnemonic(button);
131                if(mnemonic!=0)
132                    button.setMnemonic(mnemonic);
133            }
134        }
135    
136    
137        /////////////////////////
138        // KeyListener methods //
139        /////////////////////////
140    
141        public void keyPressed(KeyEvent e) {
142            int keyCode = e.getKeyCode();
143    
144            int oldCurrentButton = currentButton;
145    
146            // LEFT key goes back one button, to the last button if current button is the first one
147            if (keyCode==KeyEvent.VK_LEFT) {
148                this.currentButton = currentButton==0?nbButtons-1:currentButton-1;
149            }
150            // RIGHT key goes forward one button, to the first button if current button is the last one
151            else if (keyCode==KeyEvent.VK_RIGHT) {
152                this.currentButton = currentButton==nbButtons-1?0:currentButton+1;
153            }
154            // UP key goes up one row, to the last row if current button is on the first row
155            else if (keyCode==KeyEvent.VK_UP) {
156                if (currentButton<nbCols) {              // If current button is on the first row
157                    this.currentButton = (nbRows-1)*nbCols+currentButton%nbCols;
158                    if(this.currentButton>nbButtons-1)
159                        this.currentButton -= nbCols;
160                }
161                else
162                    this.currentButton -= nbCols;
163            }
164            // DOWN key goes down one row, to the first row if current button is on the last row
165            else if (keyCode==KeyEvent.VK_DOWN) {
166                if(nbButtons-currentButton>0 && nbButtons-currentButton<=nbCols)              // If current button is on the last row
167                    this.currentButton = currentButton%nbCols;
168                else
169                    this.currentButton += nbCols;
170            }
171            // Click button when a key that corresponds to one of the buttons' mnemonic has been pressed
172            else if(!e.isAltDown()) {
173                for(int i=0; i<nbButtons; i++) {
174                    if (keyCode==buttons[i].getMnemonic()) {
175                        buttons[i].doClick();
176                        return;
177                    }
178                }
179            }
180    
181            // Make the new button the default button and request focus on this button
182            if(oldCurrentButton!=currentButton) {
183                rootPane.setDefaultButton(buttons[currentButton]);
184                buttons[currentButton].requestFocus();
185            }
186        }
187    
188        public void keyReleased(KeyEvent e) {
189        }
190    
191        public void keyTyped(KeyEvent e) {
192        }
193    
194    
195        //////////////////////////////////
196        // FocusListener implementation //
197        //////////////////////////////////
198    
199        public void focusGained(FocusEvent focusEvent) {
200            // Makes the newly focused button the default button
201            rootPane.setDefaultButton((JButton)focusEvent.getComponent());
202        }
203    
204        public void focusLost(FocusEvent focusEvent) {
205        }
206    }