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}