001package net.kreatious.pianoleopard.keyboardselect;
002
003import java.awt.Component;
004import java.awt.Dialog.ModalityType;
005import java.awt.event.KeyEvent;
006import java.util.Optional;
007import java.util.prefs.Preferences;
008
009import javax.swing.JButton;
010import javax.swing.JDialog;
011import javax.swing.JLabel;
012
013import com.google.common.annotations.VisibleForTesting;
014import com.jgoodies.forms.factories.FormFactory;
015import com.jgoodies.forms.layout.ColumnSpec;
016import com.jgoodies.forms.layout.FormLayout;
017import com.jgoodies.forms.layout.RowSpec;
018
019/**
020 * Provides the GUI for selecting the desired MIDI device
021 *
022 * @author Jay-R Studer
023 */
024public class SelectKeyboardDialog {
025    private final Preferences preferences;
026    private final KeyboardSelector input;
027    private final KeyboardSelector output;
028    private final LightedKeyboardSelector navigationChannel;
029    private final JDialog dialog;
030    private Optional<Keyboard> keyboard;
031
032    /**
033     * Constructs a new {@link SelectKeyboardDialog} for selecting the desired
034     * MIDI device.
035     *
036     * @param keyboard
037     *            the original {@link Keyboard} model to display, if applicable
038     * @param deviceFactory
039     *            the {@link MidiDeviceFactory} for obtaining available MIDI
040     *            devices
041     * @param preferences
042     *            the preferences to store options on
043     */
044    public SelectKeyboardDialog(Optional<Keyboard> keyboard, MidiDeviceFactory deviceFactory, Preferences preferences) {
045        this.keyboard = keyboard;
046        this.preferences = preferences;
047        dialog = new JDialog();
048        dialog.setResizable(false);
049        dialog.setModalityType(ModalityType.DOCUMENT_MODAL);
050        dialog.setTitle("MIDI Keyboard Selection");
051        dialog.setLayout(new FormLayout(new ColumnSpec[] { FormFactory.RELATED_GAP_COLSPEC, FormFactory.BUTTON_COLSPEC,
052                FormFactory.RELATED_GAP_COLSPEC, ColumnSpec.decode("default:grow"), FormFactory.RELATED_GAP_COLSPEC,
053                FormFactory.BUTTON_COLSPEC, FormFactory.RELATED_GAP_COLSPEC, FormFactory.BUTTON_COLSPEC,
054                FormFactory.RELATED_GAP_COLSPEC, }, new RowSpec[] { FormFactory.RELATED_GAP_ROWSPEC,
055                FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC,
056                FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC,
057                FormFactory.DEFAULT_ROWSPEC, FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC,
058                FormFactory.RELATED_GAP_ROWSPEC, }));
059
060        dialog.add(new JLabel("Select which MIDI keyboard to use:"), "2, 2, 7, 1");
061
062        input = new KeyboardSelector("Input:", device -> device.getMaxTransmitters() != 0, deviceFactory);
063        keyboard.map(Keyboard::getInput).ifPresent(input::setSelectedDevice);
064        input.addToContainer(dialog, 2, 4, 7);
065
066        output = new KeyboardSelector("Output:", device -> device.getMaxReceivers() != 0, deviceFactory);
067        keyboard.map(Keyboard::getOutput).ifPresent(output::setSelectedDevice);
068        output.addToContainer(dialog, 2, 6, 7);
069
070        navigationChannel = new LightedKeyboardSelector();
071        navigationChannel.load(preferences);
072        navigationChannel.addToContainer(dialog, 2, 8, 7);
073
074        final JButton btnOk = new JButton("OK");
075        btnOk.addActionListener(event -> apply());
076        btnOk.addActionListener(event -> dialog.dispose());
077        dialog.add(btnOk, "6, 10");
078
079        final JButton btnRefresh = new JButton("Refresh");
080        btnRefresh.setMnemonic(KeyEvent.VK_R);
081        btnRefresh.addActionListener(event -> input.reloadDevices());
082        btnRefresh.addActionListener(event -> output.reloadDevices());
083        dialog.add(btnRefresh, "2, 10");
084
085        final JButton btnCancel = new JButton("Cancel");
086        btnCancel.addActionListener(event -> dialog.dispose());
087        dialog.add(btnCancel, "8, 10");
088
089        dialog.pack();
090    }
091
092    /**
093     * Shows the select keyboard dialog. This method blocks until a keyboard has
094     * been selected.
095     * <p>
096     * If a parent is not provided, the dialog will not be shown and this method
097     * returns immediately.
098     *
099     * @param parent
100     *            the parent component to layout this dialog relative to
101     * @return the selected keyboard, unless the dialog was cancelled
102     */
103    public Optional<Keyboard> showDialog(Optional<Component> parent) {
104        parent.ifPresent(dialog::setLocationRelativeTo);
105
106        final Optional<Keyboard> oldKeyboard = keyboard;
107        input.reloadDevices();
108        output.reloadDevices();
109        dialog.setVisible(parent.isPresent());
110        return oldKeyboard.equals(keyboard) ? Optional.empty() : keyboard;
111    }
112
113    private void apply() {
114        keyboard = Optional.of(new Keyboard(input.getSelectedDevice().get(), output.getSelectedDevice().get()));
115        navigationChannel.save(preferences);
116    }
117
118    /**
119     * @return the {@link JDialog} associated with this dialog
120     */
121    @VisibleForTesting
122    JDialog getDialog() {
123        return dialog;
124    }
125
126    /**
127     * @return the selected keyboard
128     */
129    @VisibleForTesting
130    Optional<Keyboard> getKeyboard() {
131        return keyboard;
132    }
133}