/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander.ui.button;
import com.mucommander.ui.helper.MnemonicHelper;
import javax.swing.*;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/**
* ButtonChoicePanel lays out an array of buttons on a grid and provides an easy way
* for the user to navigate and select buttons :
* <ul>
* <li>At any given time, the current active button (the first one initially) can be selected by pressing ENTER
* <li>LEFT key goes back to the previous button, to the last button if current button is the first one
* <li>RIGHT key goes forward one button, to the first button if current button is the last one
* <li>UP key goes up one row, to the last row if current button is on the first row
* <li>DOWN key goes down one row, to the first row if current button is on the last row
* <li>Buttons can directly be selected by pressing the Mnemonic associated with the button (if any)
* </ul>
* This does not interfere with regular focus management where TAB (resp. Shift+TAB) goes to the next (resp. previous)
* focusable component.
*
* @author Maxence Bernard
*/
public class ButtonChoicePanel extends JPanel implements KeyListener, FocusListener {
/** Provided JButton instances */
private JButton buttons[];
/** RootPane associated with this ButtonChoicePanel */
private JRootPane rootPane;
/** Number of columns of the buttons grid */
private int nbCols;
/** Number of row of the buttons grid */
private int nbRows;
/** Current button, i.e. the one that currently has focus */
private int currentButton;
/** Total number of buttons */
private int nbButtons;
/**
* Creates a new ButtonChoicePanel and lays out the given buttons on a grid
* according to the provided number of colums.
*
* <p>Initial focus will be given to the first button.</p>
*
* @param buttons the JButton instances to layout
* @param nbCols number of columns for the buttons grid, if <=0 all buttons will be put on a single row
* @param rootPane associated with this ButtonChoicePanel
*/
public ButtonChoicePanel(JButton buttons[], int nbCols, JRootPane rootPane) {
this.buttons = buttons;
this.nbButtons = buttons.length;
this.nbCols = nbCols<=0?nbButtons:nbCols;
this.rootPane = rootPane;
this.nbRows = nbCols<=0?1:nbButtons/nbCols+(nbButtons%nbCols==0?0:1);
// If the provided number of columns is <= 0, lay out all buttons on a single row
// and use FlowLayout to do that
if (nbCols<=0) {
setLayout(new FlowLayout(FlowLayout.RIGHT));
}
// Use GridLayout to lay out buttons on 2-dimensional grid
else {
setLayout(new GridLayout(0, nbCols));
}
JButton button;
for(int i=0; i<nbButtons; i++) {
button = buttons[i];
// Listener to key events to transfer focus
button.addKeyListener(this);
// Allow buttons to be made 'default buttons' when enter is pressed
button.setDefaultCapable(true);
// Listen to focus events to make the focused button the default button
button.addFocusListener(this);
add(button);
}
// Set mnemonics to buttons
updateMnemonics();
// First button is made 'default button'
rootPane.setDefaultButton(buttons[0]);
this.currentButton = 0;
}
/**
* Updates the buttons mnemonics. This method should be called when at least one of the button's label has changed.
*/
public void updateMnemonics() {
MnemonicHelper mnemonicHelper = new MnemonicHelper();
char mnemonic;
JButton button;
for(int i=0; i<nbButtons; i++) {
button = buttons[i];
mnemonic = mnemonicHelper.getMnemonic(button);
if(mnemonic!=0)
button.setMnemonic(mnemonic);
}
}
/////////////////////////
// KeyListener methods //
/////////////////////////
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
int oldCurrentButton = currentButton;
// LEFT key goes back one button, to the last button if current button is the first one
if (keyCode==KeyEvent.VK_LEFT) {
this.currentButton = currentButton==0?nbButtons-1:currentButton-1;
}
// RIGHT key goes forward one button, to the first button if current button is the last one
else if (keyCode==KeyEvent.VK_RIGHT) {
this.currentButton = currentButton==nbButtons-1?0:currentButton+1;
}
// UP key goes up one row, to the last row if current button is on the first row
else if (keyCode==KeyEvent.VK_UP) {
if (currentButton<nbCols) { // If current button is on the first row
this.currentButton = (nbRows-1)*nbCols+currentButton%nbCols;
if(this.currentButton>nbButtons-1)
this.currentButton -= nbCols;
}
else
this.currentButton -= nbCols;
}
// DOWN key goes down one row, to the first row if current button is on the last row
else if (keyCode==KeyEvent.VK_DOWN) {
if(nbButtons-currentButton>0 && nbButtons-currentButton<=nbCols) // If current button is on the last row
this.currentButton = currentButton%nbCols;
else
this.currentButton += nbCols;
}
// Click button when a key that corresponds to one of the buttons' mnemonic has been pressed
else if(!e.isAltDown()) {
for(int i=0; i<nbButtons; i++) {
if (keyCode==buttons[i].getMnemonic()) {
buttons[i].doClick();
return;
}
}
}
// Make the new button the default button and request focus on this button
if(oldCurrentButton!=currentButton) {
rootPane.setDefaultButton(buttons[currentButton]);
buttons[currentButton].requestFocus();
}
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
//////////////////////////////////
// FocusListener implementation //
//////////////////////////////////
public void focusGained(FocusEvent focusEvent) {
// Makes the newly focused button the default button
rootPane.setDefaultButton((JButton)focusEvent.getComponent());
}
public void focusLost(FocusEvent focusEvent) {
}
}