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 }