001    /*
002     * File $Workfile: ControlPanel.java $
003     * $Archive: /Tower Of Hanoi/toh/src/net/pacbell/jfai/toh/ui/ControlPanel.java $
004     * Last changed by $Author: Jürgen Failenschmid $
005     * Last modified   $Modtime: 12/09/05 4:57p $
006     * Last checked in $Date: 12/09/05 5:10p $
007     * $Revision: 3 $
008     *
009     *
010     * Copyright (C) 2004-2005 Jürgen Failenschmid. All rights reserved.
011     */
012    
013    package net.pacbell.jfai.toh.ui;
014    
015    import java.awt.GridBagConstraints;
016    import java.awt.GridBagLayout;
017    import java.awt.GridLayout;
018    import java.awt.Insets;
019    import java.util.Dictionary;
020    
021    import javax.swing.ButtonGroup;
022    import javax.swing.JButton;
023    import javax.swing.JLabel;
024    import javax.swing.JPanel;
025    import javax.swing.JSlider;
026    import javax.swing.JToggleButton;
027    import javax.swing.SwingConstants;
028    
029    /**
030     * A panel containing the configuration controls, such as sliders, buttons.
031     * 
032     * @author {@link <a href="http://www.anycpu.com">Jürgen Failenschmid</a>}
033     */
034    @SuppressWarnings("serial")
035    public class ControlPanel extends JPanel
036    {
037        private static final String SLIDER_MIN_LABEL = "1"; //$NON-NLS-1$
038        private static final int ACTION_BUTTON_GRID_GAP_SIZE = 5;
039        private static final int BORDER_SIZE = 5;
040        private static final int DISKS_SLIDER_LABEL_INCREMENT = 5;
041        private static final int DISKS_SLIDER_LABEL_START = 5;
042        private static final int DISKS_SLIDER_MIN_UPPER_VALUE = 20;
043        private static final String DISKS_SLIDER_MIN_LABEL = SLIDER_MIN_LABEL;
044        private static final int PIN_PICK_BUTTON_GRID_GAP_SIZE = 10;
045        private static final int SPEED_SLIDER_LABEL_INCREMENT = 5;
046        private static final int SPEED_SLIDER_LABEL_START = 5;
047        private static final String SPEED_SLIDER_MIN_LABEL = SLIDER_MIN_LABEL;
048        private static final int SPEED_SLIDER_UPPER_VALUE = 10;
049    
050        private JButton applyButton;
051        private JButton cancelButton;
052        private JSlider disksSlider;
053        private JToggleButton invisibleButton;
054        private JToggleButton sourceButton;
055        private JSlider speedSlider;
056        private JButton startButton;
057        private JToggleButton targetButton;
058    
059        /**
060         * Creates a panel with the various buttons and sliders for the
061         * configuration of the puzzle.
062         * 
063         * @param sourcePin label for the button to select the source pin
064         * @param targetPin label for the button to select the target pin
065         * @param numberOfDisks the total number of disks of the puzzle
066         */
067        public ControlPanel(String sourcePin, String targetPin, int numberOfDisks) {
068            super(new GridBagLayout());
069    
070            // Vertical and horizontal border for each component
071            final Insets insets = new Insets(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE,
072                BORDER_SIZE);
073            /*
074             * Constraints that apply to all components: adjust size in x and y,
075             * occupy 1 cell in y (default), relative placement (default)
076             */
077            final GridBagConstraints constraints = new GridBagConstraints();
078            constraints.fill = GridBagConstraints.BOTH;
079            constraints.insets = insets;
080    
081            int row = 0;
082    
083            addNumberOfDisksComponents(constraints, row, numberOfDisks);
084            row++;
085    
086            addPinSelectionComponents(constraints, row, sourcePin, targetPin);
087            row++;
088    
089            addActionButtons(constraints, row);
090    
091            addSpeedComponents(constraints, row);
092        }
093    
094        /**
095         * Gets the apply button.
096         * 
097         * @return the applyButton.
098         */
099        public JButton getApplyButton() {
100            return applyButton;
101        }
102    
103        /**
104         * Gets the cancel button.
105         * 
106         * @return the cancelButton.
107         */
108        public JButton getCancelButton() {
109            return cancelButton;
110        }
111    
112        /**
113         * Gets the slider for the number of disks.
114         * 
115         * @return the disksSlider.
116         */
117        public JSlider getDisksSlider() {
118            return disksSlider;
119        }
120    
121        /**
122         * Gets the invisible button.
123         * 
124         * @return the invisibleButton.
125         */
126        public JToggleButton getInvisibleButton() {
127            return invisibleButton;
128        }
129    
130        /**
131         * Gets the source button.
132         * 
133         * @return the sourceButton.
134         */
135        public JToggleButton getSourceButton() {
136            return sourceButton;
137        }
138    
139        /**
140         * Gets the speed slider.
141         * 
142         * @return the speedSlider.
143         */
144        public JSlider getSpeedSlider() {
145            return speedSlider;
146        }
147    
148        /**
149         * Gets the start button.
150         * 
151         * @return the startButton.
152         */
153        public JButton getStartButton() {
154            return startButton;
155        }
156    
157        /**
158         * Gets the target button.
159         * 
160         * @return the targetButton.
161         */
162        public JToggleButton getTargetButton() {
163            return targetButton;
164        }
165    
166        private void addActionButtons(GridBagConstraints constraints, int row) {
167            /*
168             * Puts all action buttons in a row into a JPanel with a GridLayout to
169             * make all buttons the same size. Uses 5 pixels as horizontal and
170             * vertical gap between buttons.
171             */
172            final JPanel panel = new JPanel(new GridLayout(1, 0,
173                ACTION_BUTTON_GRID_GAP_SIZE, ACTION_BUTTON_GRID_GAP_SIZE));
174    
175            // Action button to accept changes
176            setApplyButton(new JButton("Apply")); //$NON-NLS-1$
177            panel.add(getApplyButton());
178            getApplyButton().setToolTipText("Press this button to apply changes"); //$NON-NLS-1$
179    
180            // Cancel button to reject changes
181            setCancelButton(new JButton("Cancel")); //$NON-NLS-1$
182            panel.add(getCancelButton());
183            getCancelButton().setToolTipText("Press this button to undo changes"); //$NON-NLS-1$
184    
185            /*
186             * Button to start the animation - will change depending on solver's
187             * state
188             */
189            setStartButton(new JButton("Solve")); //$NON-NLS-1$
190            panel.add(getStartButton());
191    
192            /*
193             * Adds the button panel to the container: first column, 2 cells wide,
194             * don't grow
195             */
196            constraints.gridx = 0;
197            constraints.gridy = row;
198            constraints.gridwidth = 2;
199            constraints.weightx = 0;
200            constraints.weighty = 0;
201            add(panel, constraints);
202        }
203    
204        @SuppressWarnings({"boxing", "unchecked" })
205        private void addNumberOfDisksComponents(GridBagConstraints constraints,
206            int row, final int numberOfDisks)
207        {
208            // Label for number of disks: first column, 1 cell wide, don't grow
209            constraints.gridx = 0;
210            constraints.gridy = row;
211            constraints.gridwidth = 1;
212            constraints.weightx = 0;
213            constraints.weighty = 0;
214            add(new JLabel("Number of disks:"), constraints); //$NON-NLS-1$
215    
216            // Slider for number of disks, second column, use rest of row
217            setDisksSlider(new JSlider(1, Math.max(DISKS_SLIDER_MIN_UPPER_VALUE,
218                numberOfDisks), numberOfDisks));
219            final Dictionary<Integer, JLabel> labels = getDisksSlider()
220                .createStandardLabels(DISKS_SLIDER_LABEL_INCREMENT,
221                    DISKS_SLIDER_LABEL_START);
222            labels.put(1, new JLabel(DISKS_SLIDER_MIN_LABEL));
223            getDisksSlider().setLabelTable(labels);
224            getDisksSlider().setMinorTickSpacing(1);
225            getDisksSlider().setPaintTicks(true);
226            getDisksSlider().setPaintLabels(true);
227            getDisksSlider().setSnapToTicks(true);
228            constraints.gridx = 1;
229            constraints.gridy = row;
230            constraints.gridwidth = GridBagConstraints.REMAINDER;
231            constraints.weightx = 1;
232            constraints.weighty = 1;
233            add(getDisksSlider(), constraints);
234            getDisksSlider().setToolTipText("Select the number of disks"); //$NON-NLS-1$
235        }
236    
237        private void addPinSelectionComponents(GridBagConstraints constraints,
238            int row, String sourcePin, String targetPin)
239        {
240            final JPanel panel = new JPanel(new GridLayout(1, 0,
241                PIN_PICK_BUTTON_GRID_GAP_SIZE, PIN_PICK_BUTTON_GRID_GAP_SIZE));
242    
243            // Group for toggle buttons
244            final ButtonGroup buttonGroup = new ButtonGroup();
245    
246            // Label for source pin
247            panel.add(new JLabel("Source:", SwingConstants.RIGHT)); //$NON-NLS-1$
248    
249            // Toggle button for source pin
250            setSourceButton(new JToggleButton(sourcePin));
251            buttonGroup.add(getSourceButton());
252            panel.add(getSourceButton());
253            getSourceButton().setToolTipText(
254                "Press button to select the source pin. Click on a pin."); //$NON-NLS-1$
255    
256            // Label for target pin
257            panel.add(new JLabel("Target:", SwingConstants.RIGHT)); //$NON-NLS-1$
258    
259            // Toggle button for target pin
260            setTargetButton(new JToggleButton(targetPin));
261            buttonGroup.add(getTargetButton());
262            panel.add(getTargetButton());
263            getTargetButton().setToolTipText(
264                "Press button to select the target pin. Click on a pin."); //$NON-NLS-1$
265    
266            // Adds panel to grid bag: 1st column, all columns, grow
267            constraints.gridx = 0;
268            constraints.gridy = row;
269            constraints.gridwidth = GridBagConstraints.REMAINDER;
270            constraints.weightx = 1;
271            constraints.weighty = 1;
272            add(panel, constraints);
273    
274            /*
275             * Invisible toggle button used to remove focus from the two visible
276             * buttons
277             */
278            setInvisibleButton(new JToggleButton());
279            buttonGroup.add(getInvisibleButton());
280            getInvisibleButton().setVisible(false);
281            constraints.gridx = 0;
282            constraints.gridy = row;
283            constraints.gridwidth = 0;
284            constraints.weightx = 0;
285            constraints.weighty = 0;
286            add(getInvisibleButton(), constraints);
287        }
288    
289        @SuppressWarnings({"boxing", "unchecked" })
290        private void addSpeedComponents(GridBagConstraints constraints, int row)
291        {
292            // Label for speed: 2nd column, 1 cell wide, don't grow
293            constraints.gridx = 2;
294            constraints.gridy = row;
295            constraints.gridwidth = GridBagConstraints.RELATIVE;
296            constraints.weightx = 0;
297            constraints.weighty = 0;
298            add(new JLabel("Seconds per move:"), constraints); //$NON-NLS-1$
299    
300            /*
301             * Slider for speed, 3rd column, use rest of row. Speed of animator is
302             * in milliseconds.
303             */
304            setSpeedSlider(new JSlider(1, SPEED_SLIDER_UPPER_VALUE,
305                (int) DiskMoveAnimator.getDuration() / DiskMoveAnimator.MS_PER_S));
306            final Dictionary<Integer, JLabel> labels = getSpeedSlider()
307                .createStandardLabels(SPEED_SLIDER_LABEL_INCREMENT,
308                    SPEED_SLIDER_LABEL_START);
309            labels.put(1, new JLabel(SPEED_SLIDER_MIN_LABEL));
310            getSpeedSlider().setLabelTable(labels);
311            getSpeedSlider().setMinorTickSpacing(1);
312            getSpeedSlider().setPaintTicks(true);
313            getSpeedSlider().setPaintLabels(true);
314            getSpeedSlider().setSnapToTicks(true);
315    
316            constraints.gridx = 3;
317            constraints.gridy = row;
318            constraints.gridwidth = GridBagConstraints.REMAINDER;
319            constraints.weightx = 1;
320            constraints.weighty = 1;
321            add(getSpeedSlider(), constraints);
322            getSpeedSlider().setToolTipText("Select the time per move"); //$NON-NLS-1$
323        }
324    
325        /**
326         * Sets the apply button.
327         * 
328         * @param applyButton The applyButton to set.
329         */
330        private void setApplyButton(JButton applyButton) {
331            this.applyButton = applyButton;
332        }
333    
334        /**
335         * Sets the cancel button.
336         * 
337         * @param cancelButton The cancelButton to set.
338         */
339        private void setCancelButton(JButton cancelButton) {
340            this.cancelButton = cancelButton;
341        }
342    
343        /**
344         * Sets the slider for the number of disks.
345         * 
346         * @param disksSlider The disksSlider to set.
347         */
348        private void setDisksSlider(JSlider disksSlider) {
349            this.disksSlider = disksSlider;
350        }
351    
352        /**
353         * Sets the invisible button.
354         * 
355         * @param invisibleButton The invisibleButton to set.
356         */
357        private void setInvisibleButton(JToggleButton invisibleButton) {
358            this.invisibleButton = invisibleButton;
359        }
360    
361        /**
362         * Sets the source button.
363         * 
364         * @param sourceButton The sourceButton to set.
365         */
366        private void setSourceButton(JToggleButton sourceButton) {
367            this.sourceButton = sourceButton;
368        }
369    
370        /**
371         * Sets the speed slider.
372         * 
373         * @param speedSlider The speedSlider to set.
374         */
375        private void setSpeedSlider(JSlider speedSlider) {
376            this.speedSlider = speedSlider;
377        }
378    
379        /**
380         * Sets the start button.
381         * 
382         * @param startButton The startButton to set.
383         */
384        private void setStartButton(JButton startButton) {
385            this.startButton = startButton;
386        }
387    
388        /**
389         * Sets the target button.
390         * 
391         * @param targetButton The targetButton to set.
392         */
393        private void setTargetButton(JToggleButton targetButton) {
394            this.targetButton = targetButton;
395        }
396    }