001package jmri.jmrit.throttle;
002
003import java.awt.*;
004import java.awt.event.*;
005
006import javax.swing.*;
007import javax.swing.border.*;
008
009import jmri.swing.JTitledSeparator;
010
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * A preferences panel to display and edit JMRI throttle keyboard shortcuts
016 *
017 * @author Lionel Jeanson - 2021
018 *
019 */
020public class ThrottlesPreferencesControlsSettingsPane extends JPanel {
021
022    private ShortCutsField tfNextThrottleWindow;
023    private ShortCutsField tfPrevThrottleWindow;
024    private ShortCutsField tfNextThrottleFrame;
025    private ShortCutsField tfPrevThrottleFrame;
026    private ShortCutsField tfNextRunningThrottleFrame;
027    private ShortCutsField tfPrevRunningThrottleFrame;
028    private ShortCutsField tfNextThrottleInternalWindow;
029    private ShortCutsField tfPrevThrottleInternalWindow;
030    private ShortCutsField tfGotoControl;
031    private ShortCutsField tfGotoFunctions;
032    private ShortCutsField tfGotoAddress;
033    private ShortCutsField tfForward;
034    private ShortCutsField tfReverse;
035    private ShortCutsField tfSwitchDir;
036    private ShortCutsField tfSpeedIdle;
037    private ShortCutsField tfSpeedStop;
038    private ShortCutsField tfSpeedUp;
039    private ShortCutsField tfSpeedDown;
040    private ShortCutsField tfSpeedUpMore;
041    private ShortCutsField tfSpeedDownMore;
042
043    private JTextField tfSpeedMultiplier;
044    private float origSpeedMultiplier;
045
046    private ShortCutsField[] tfFunctionKeys;
047    private ThrottlesPreferencesWindowKeyboardControls _tpwkc;
048
049    public ThrottlesPreferencesControlsSettingsPane(ThrottlesPreferences tp) {
050        try {
051            _tpwkc = tp.getThrottlesKeyboardControls().clone();
052        } catch (CloneNotSupportedException ex) {
053            log.debug("Couldn't clone ThrottlesPreferencesWindowKeyboardControls");
054        }
055        initComponents();
056    }
057
058    private void initComponents() {
059
060        JPanel propertyPanel = new JPanel();
061        propertyPanel.setLayout(new GridBagLayout());
062        this.add(propertyPanel);
063
064        GridBagConstraints constraintsL = new GridBagConstraints();
065        constraintsL.fill = GridBagConstraints.HORIZONTAL;
066        constraintsL.gridheight = 1;
067        constraintsL.gridwidth = 1;
068        constraintsL.ipadx = 0;
069        constraintsL.ipady = 0;
070        constraintsL.insets = new Insets(2, 18, 2, 2);
071        constraintsL.weightx = 1;
072        constraintsL.weighty = 1;
073        constraintsL.anchor = GridBagConstraints.WEST;
074        constraintsL.gridx = 0;
075        constraintsL.gridy = 0;
076
077        GridBagConstraints constraintsR = (GridBagConstraints) constraintsL.clone();
078        constraintsR.anchor = GridBagConstraints.CENTER;
079        constraintsR.gridx = 1;
080
081        GridBagConstraints constraintsS = (GridBagConstraints) constraintsL.clone();
082        constraintsS.gridwidth = 2;
083        constraintsS.insets = new Insets(18, 2, 2, 2);
084
085
086        propertyPanel.add(new JTitledSeparator(Bundle.getMessage("ThrottleWindowControls")),constraintsS);
087        constraintsL.gridy++;
088        constraintsR.gridy++;
089        constraintsS.gridy++;
090
091        propertyPanel.add(new JLabel(Bundle.getMessage("NextThrottleWindow")), constraintsL);
092        tfNextThrottleWindow = new ShortCutsField( _tpwkc.getNextThrottleWindowKeys());
093        propertyPanel.add(tfNextThrottleWindow, constraintsR);
094        constraintsL.gridy++;
095        constraintsR.gridy++;
096        constraintsS.gridy++;
097
098        propertyPanel.add(new JLabel(Bundle.getMessage("PrevThrottleWindow")), constraintsL);
099        tfPrevThrottleWindow = new ShortCutsField( _tpwkc.getPrevThrottleWindowKeys());
100        propertyPanel.add(tfPrevThrottleWindow, constraintsR);
101        constraintsL.gridy++;
102        constraintsR.gridy++;
103        constraintsS.gridy++;
104
105        propertyPanel.add(new JLabel(Bundle.getMessage("NextThrottleFrame")), constraintsL);
106        tfNextThrottleFrame = new ShortCutsField( _tpwkc.getNextThrottleFrameKeys());
107        propertyPanel.add(tfNextThrottleFrame, constraintsR);
108        constraintsL.gridy++;
109        constraintsR.gridy++;
110        constraintsS.gridy++;
111
112        propertyPanel.add(new JLabel(Bundle.getMessage("PrevThrottleFrame")), constraintsL);
113        tfPrevThrottleFrame = new ShortCutsField( _tpwkc.getPrevThrottleFrameKeys());
114        propertyPanel.add(tfPrevThrottleFrame, constraintsR);
115        constraintsL.gridy++;
116        constraintsR.gridy++;
117        constraintsS.gridy++;
118
119        propertyPanel.add(new JLabel(Bundle.getMessage("NextRunningThrottleFrame")), constraintsL);
120        tfNextRunningThrottleFrame = new ShortCutsField( _tpwkc.getNextRunThrottleFrameKeys());
121        propertyPanel.add(tfNextRunningThrottleFrame, constraintsR);
122        constraintsL.gridy++;
123        constraintsR.gridy++;
124        constraintsS.gridy++;
125
126        propertyPanel.add(new JLabel(Bundle.getMessage("PrevRunningThrottleFrame")), constraintsL);
127        tfPrevRunningThrottleFrame = new ShortCutsField( _tpwkc.getPrevRunThrottleFrameKeys());
128        propertyPanel.add(tfPrevRunningThrottleFrame, constraintsR);
129        constraintsL.gridy++;
130        constraintsR.gridy++;
131        constraintsS.gridy++;
132
133        propertyPanel.add(new JLabel(Bundle.getMessage("NextThrottleInternalWindow")), constraintsL);
134        tfNextThrottleInternalWindow = new ShortCutsField( _tpwkc.getNextThrottleInternalWindowKeys());
135        propertyPanel.add(tfNextThrottleInternalWindow, constraintsR);
136        constraintsL.gridy++;
137        constraintsR.gridy++;
138        constraintsS.gridy++;
139
140        propertyPanel.add(new JLabel(Bundle.getMessage("PrevThrottleInternalWindow")), constraintsL);
141        tfPrevThrottleInternalWindow = new ShortCutsField( _tpwkc.getPrevThrottleInternalWindowKeys());
142        propertyPanel.add(tfPrevThrottleInternalWindow, constraintsR);
143        constraintsL.gridy++;
144        constraintsR.gridy++;
145        constraintsS.gridy++;
146
147        propertyPanel.add(new JLabel(Bundle.getMessage("GotoControl")), constraintsL);
148        tfGotoControl = new ShortCutsField( _tpwkc.getMoveToControlPanelKeys());
149        propertyPanel.add(tfGotoControl, constraintsR);
150        constraintsL.gridy++;
151        constraintsR.gridy++;
152        constraintsS.gridy++;
153
154        propertyPanel.add(new JLabel(Bundle.getMessage("GotoFunctions")), constraintsL);
155        tfGotoFunctions = new ShortCutsField( _tpwkc.getMoveToFunctionPanelKeys());
156        propertyPanel.add(tfGotoFunctions, constraintsR);
157        constraintsL.gridy++;
158        constraintsR.gridy++;
159        constraintsS.gridy++;
160
161        propertyPanel.add(new JLabel(Bundle.getMessage("GotoAddress")), constraintsL);
162        tfGotoAddress = new ShortCutsField( _tpwkc.getMoveToAddressPanelKeys());
163        propertyPanel.add(tfGotoAddress, constraintsR);
164        constraintsL.gridy++;
165        constraintsR.gridy++;
166        constraintsS.gridy++;
167
168        propertyPanel.add(new JTitledSeparator(Bundle.getMessage("ThrottleSpeedControls")),constraintsS);
169        constraintsL.gridy++;
170        constraintsR.gridy++;
171        constraintsS.gridy++;
172
173        propertyPanel.add(new JLabel(Bundle.getMessage("Forward")), constraintsL);
174        tfForward = new ShortCutsField( _tpwkc.getForwardKeys());
175        propertyPanel.add(tfForward, constraintsR);
176        constraintsL.gridy++;
177        constraintsR.gridy++;
178        constraintsS.gridy++;
179
180        propertyPanel.add(new JLabel(Bundle.getMessage("Backward")), constraintsL);
181        tfReverse = new ShortCutsField( _tpwkc.getReverseKeys());
182        propertyPanel.add(tfReverse, constraintsR);
183        constraintsL.gridy++;
184        constraintsR.gridy++;
185        constraintsS.gridy++;
186
187        propertyPanel.add(new JLabel(Bundle.getMessage("SwitchDirection")), constraintsL);
188        tfSwitchDir = new ShortCutsField( _tpwkc.getSwitchDirectionKeys());
189        propertyPanel.add(tfSwitchDir, constraintsR);
190        constraintsL.gridy++;
191        constraintsR.gridy++;
192        constraintsS.gridy++;
193
194        propertyPanel.add(new JLabel(Bundle.getMessage("SpeedIdle")), constraintsL);
195        tfSpeedIdle = new ShortCutsField( _tpwkc.getIdleKeys());
196        propertyPanel.add(tfSpeedIdle, constraintsR);
197        constraintsL.gridy++;
198        constraintsR.gridy++;
199        constraintsS.gridy++;
200
201        propertyPanel.add(new JLabel(Bundle.getMessage("SpeedStop")), constraintsL);
202        tfSpeedStop = new ShortCutsField( _tpwkc.getStopKeys());
203        propertyPanel.add(tfSpeedStop, constraintsR);
204        constraintsL.gridy++;
205        constraintsR.gridy++;
206        constraintsS.gridy++;
207
208        propertyPanel.add(new JLabel(Bundle.getMessage("SpeedUp")), constraintsL);
209        tfSpeedUp = new ShortCutsField( _tpwkc.getAccelerateKeys());
210        propertyPanel.add(tfSpeedUp, constraintsR);
211        constraintsL.gridy++;
212        constraintsR.gridy++;
213        constraintsS.gridy++;
214
215        propertyPanel.add(new JLabel(Bundle.getMessage("SpeedDown")), constraintsL);
216        tfSpeedDown = new ShortCutsField( _tpwkc.getDecelerateKeys());
217        propertyPanel.add(tfSpeedDown, constraintsR);
218        constraintsL.gridy++;
219        constraintsR.gridy++;
220        constraintsS.gridy++;
221
222        propertyPanel.add(new JLabel(Bundle.getMessage("SpeedUpMore")), constraintsL);
223        tfSpeedUpMore = new ShortCutsField( _tpwkc.getAccelerateMoreKeys());
224        propertyPanel.add(tfSpeedUpMore, constraintsR);
225        constraintsL.gridy++;
226        constraintsR.gridy++;
227        constraintsS.gridy++;
228
229        propertyPanel.add(new JLabel(Bundle.getMessage("SpeedDownMore")), constraintsL);
230        tfSpeedDownMore = new ShortCutsField( _tpwkc.getDecelerateMoreKeys());
231        propertyPanel.add(tfSpeedDownMore, constraintsR);
232        constraintsL.gridy++;
233        constraintsR.gridy++;
234        constraintsS.gridy++;
235
236        propertyPanel.add(new JLabel(Bundle.getMessage("SpeedMultiplier")), constraintsL);
237        origSpeedMultiplier = _tpwkc.getMoreSpeedMultiplier();
238        tfSpeedMultiplier = new JTextField(""+origSpeedMultiplier);
239        tfSpeedMultiplier.setColumns(5);
240        propertyPanel.add(tfSpeedMultiplier, constraintsR);
241        constraintsL.gridy++;
242        constraintsR.gridy++;
243        constraintsS.gridy++;
244
245        propertyPanel.add(new JTitledSeparator(Bundle.getMessage("ThrottleFunctionsControls")),constraintsS);
246        constraintsL.gridy++;
247        constraintsR.gridy++;
248        constraintsS.gridy++;
249
250        tfFunctionKeys = new ShortCutsField[_tpwkc.getNbFunctionsKeys()];
251        for (int i=0; i<tfFunctionKeys.length; i++) {
252            propertyPanel.add(new JLabel(Bundle.getMessage("Function")+" "+i), constraintsL);
253            tfFunctionKeys[i] = new ShortCutsField( _tpwkc.getFunctionsKeys(i));
254            propertyPanel.add(tfFunctionKeys[i], constraintsR);
255            constraintsL.gridy++;
256            constraintsR.gridy++;
257            constraintsS.gridy++;
258        }
259    }
260
261    public ThrottlesPreferences updateThrottlesPreferences(ThrottlesPreferences tp) {
262        ThrottlesPreferencesWindowKeyboardControls tpwkc = tp.getThrottlesKeyboardControls();
263        if (tfNextThrottleWindow.isDirty()) {
264            tpwkc.setNextThrottleWindowKeys(tfNextThrottleWindow.getShortCuts() );
265        }
266        if (tfPrevThrottleWindow.isDirty()) {
267            tpwkc.setPrevThrottleWindowKeys(tfPrevThrottleWindow.getShortCuts() );
268        }
269        if (tfNextThrottleFrame.isDirty()) {
270            tpwkc.setNextTrottleFrameKeys( tfNextThrottleFrame.getShortCuts() );
271        }
272        if (tfPrevThrottleFrame.isDirty()) {
273            tpwkc.setPrevThrottleFrameKeys( tfPrevThrottleFrame.getShortCuts() );
274        }
275        if (tfNextRunningThrottleFrame.isDirty()) {
276            tpwkc.setNextRunThrottleFrameKeys( tfNextRunningThrottleFrame.getShortCuts() );
277        }
278        if (tfPrevRunningThrottleFrame.isDirty()) {
279            tpwkc.setPrevRunThrottleFrameKeys( tfPrevRunningThrottleFrame.getShortCuts() );
280        }
281        if (tfNextThrottleInternalWindow.isDirty()) {
282            tpwkc.setNextThrottleInternalWindowKeys( tfNextThrottleInternalWindow.getShortCuts() );
283        }
284        if (tfPrevThrottleInternalWindow.isDirty()) {
285            tpwkc.setPrevThrottleInternalWindowKeys( tfPrevThrottleInternalWindow.getShortCuts() );
286        }
287        if (tfGotoControl.isDirty()) {
288            tpwkc.setMoveToControlPanelKeys( tfGotoControl.getShortCuts() );
289        }
290        if (tfGotoFunctions.isDirty()) {
291            tpwkc.setMoveToFunctionPanelKeys( tfGotoFunctions.getShortCuts() );
292        }
293        if (tfGotoAddress.isDirty()) {
294            tpwkc.setMoveToAddressPanelKeys( tfGotoAddress.getShortCuts() );
295        }
296        if (tfForward.isDirty()) {
297            tpwkc.setForwardKeys( tfForward.getShortCuts() );
298        }
299        if (tfReverse.isDirty()) {
300            tpwkc.setReverseKeys( tfReverse.getShortCuts() );
301        }
302        if (tfSwitchDir.isDirty()) {
303            tpwkc.setSwitchDirectionKeys( tfSwitchDir.getShortCuts() );
304        }
305        if (tfSpeedIdle.isDirty()) {
306            tpwkc.setIdleKeys( tfSpeedIdle.getShortCuts() );
307        }
308        if (tfSpeedStop.isDirty()) {
309            tpwkc.setStopKeys( tfSpeedStop.getShortCuts() );
310        }
311        if (tfSpeedUp.isDirty()) {
312            tpwkc.setAccelerateKeys( tfSpeedUp.getShortCuts() );
313        }
314        if (tfSpeedDown.isDirty()) {
315            tpwkc.setDecelerateKeys( tfSpeedDown.getShortCuts() );
316        }
317        if (tfSpeedUpMore.isDirty()) {
318            tpwkc.setAccelerateMoreKeys( tfSpeedUpMore.getShortCuts() );
319        }
320        if (tfSpeedDownMore.isDirty()) {
321            tpwkc.setDecelerateMoreKeys( tfSpeedDownMore.getShortCuts() );
322        }
323        for (int i=0; i<tfFunctionKeys.length; i++) {
324            if (tfFunctionKeys[i].isDirty) {
325                tpwkc.setFunctionsKeys (i, tfFunctionKeys[i].getShortCuts() );
326            }
327        }
328        try {
329            float sm = Float.parseFloat(tfSpeedMultiplier.getText());
330            if (Math.abs(sm - tpwkc.getMoreSpeedMultiplier()) > 0.0001) {
331                tpwkc.setMoreSpeedMultiplier(sm);
332            }
333        }
334        catch (NumberFormatException e) {
335            log.error("Speed multiplier must be a numerical float value.");
336        }
337        return tp;
338    }
339
340    void resetComponents(ThrottlesPreferences tp) {
341        try {
342            _tpwkc = tp.getThrottlesKeyboardControls().clone();
343        } catch (CloneNotSupportedException ex) {
344            log.debug("Couldn't clone ThrottlesPreferencesWindowKeyboardControls");
345        }
346        this.removeAll();
347        initComponents();
348        revalidate();
349    }
350
351    boolean isDirty() {
352        boolean ret = false;
353        ret = tfNextThrottleWindow.isDirty() || ret;
354        ret = tfPrevThrottleWindow.isDirty() || ret;
355        ret = tfNextThrottleFrame.isDirty() || ret;
356        ret = tfPrevThrottleFrame.isDirty() || ret;
357        ret = tfNextRunningThrottleFrame.isDirty() || ret;
358        ret = tfPrevRunningThrottleFrame.isDirty() || ret;
359        ret = tfNextThrottleInternalWindow.isDirty() || ret;
360        ret = tfPrevThrottleInternalWindow.isDirty() || ret;
361        ret = tfGotoControl.isDirty() || ret;
362        ret = tfGotoFunctions.isDirty() || ret;
363        ret = tfGotoAddress.isDirty() || ret;
364        ret = tfForward.isDirty() || ret;
365        ret = tfReverse.isDirty() || ret;
366        ret = tfSwitchDir.isDirty() || ret;
367        ret = tfSpeedIdle.isDirty() || ret;
368        ret = tfSpeedStop.isDirty() || ret;
369        ret = tfSpeedDown.isDirty() || ret;
370        ret = tfSpeedUp.isDirty() || ret;
371        ret = tfSpeedDownMore.isDirty() || ret;
372        ret = tfSpeedUpMore.isDirty() || ret;
373        for (ShortCutsField tfFunctionKey : tfFunctionKeys) {
374            if (tfFunctionKey.isDirty) {
375                ret = tfFunctionKey.isDirty() || ret;
376            }
377        }
378        try {
379            float sm = Float.parseFloat(tfSpeedMultiplier.getText());
380            ret = (Math.abs(sm - origSpeedMultiplier) > 0.0001) || ret;
381        }
382        catch (NumberFormatException e) {
383            log.error("Speed multiplier must be a numerical float value.");
384        }
385        return ret;
386    }
387
388    final private class ShortCutsField extends JPanel {
389        int[][] shortcuts;
390        boolean isDirty = false;
391
392        ShortCutsField(int[][] values) {
393            super();
394            shortcuts = values;
395            setLayout(new GridLayout());
396            for (int[] v:shortcuts) {
397                if (v[0]!=0 || v[1]!=0) {
398                    add(new ShortCutPanel( this, v));
399                }
400            }
401            add(new ShortCutTextField( this));
402        }
403
404        private void addValue(int[] values, Component cmp) {
405            shortcuts = java.util.Arrays.copyOf(shortcuts, shortcuts.length+1);
406            shortcuts[shortcuts.length-1]=values;
407            add(new ShortCutPanel( this, shortcuts[shortcuts.length-1]));
408            add(new ShortCutTextField( this));
409            setDirty(true);
410            remove(cmp);
411            revalidate();
412        }
413
414        public boolean isDirty() {
415            return isDirty;
416        }
417
418        public void setDirty(boolean b) {
419            isDirty = b;
420        }
421
422        public int[][] getShortCuts() {
423            return shortcuts;
424        }
425    }
426
427    final private class ShortCutPanel extends JPanel {
428        ShortCutsField shortCutsField;
429        int[] shortcut; // [0]:modifier , [1]: extended key code
430
431        ShortCutPanel(ShortCutsField scf, int[] sc) {
432            super();
433            shortCutsField = scf;
434            shortcut = sc;
435            setLayout(new BorderLayout());
436            add(new ShortCutTextField(shortcut));
437            JButton removeBtn = new JButton("X");
438            removeBtn.addActionListener((ActionEvent e) -> {
439                shortcut[0]=0;
440                shortcut[1]=0;
441                shortCutsField.setDirty(true);
442                shortCutsField.remove(this);
443                shortCutsField.revalidate();
444            });
445            add(removeBtn,BorderLayout.WEST);
446            setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
447        }
448    }
449
450    final private class ShortCutTextField extends JTextField {
451        ShortCutsField shortCutsField;
452
453        @SuppressWarnings("deprecation") // KeyEvent.getKeyModifiersText
454        ShortCutTextField(int[] v) {
455            super();
456            setEditable(false);
457            String text="";
458            if (v[0]!=0) {
459                text +=  ( KeyEvent.getKeyModifiersText(v[0]).isEmpty() ?
460                                KeyEvent.getModifiersExText(v[0]) :
461                                KeyEvent.getKeyModifiersText(v[0])
462                            ) + " + ";
463            }
464            if (v[1]!=0) {
465                text += KeyEvent.getKeyText(v[1]);
466            }
467            super.setText(text);
468        }
469
470        ShortCutTextField(ShortCutsField scf) {
471            super();
472            setEditable(false);
473            shortCutsField = scf;
474            addKeyListener(new KeyAdapter() {
475                    @Override
476                    @SuppressWarnings("deprecation")  // getModifiers()
477                    public void keyReleased(KeyEvent e){
478                        int[] values = new int[2];
479                        values[0] = e.getModifiersEx();
480                        values[1] = e.getExtendedKeyCode();
481                        shortCutsField.addValue(values, e.getComponent());
482                        log.debug("Key pressed: {} / modifier: {} / ext. key code: {} / location: {}",
483                            e.getKeyCode(), e.getModifiersEx(), e.getExtendedKeyCode(), e.getKeyLocation());
484                    }
485                });
486        }
487    }
488
489    private final static Logger log = LoggerFactory.getLogger(ThrottlesPreferencesControlsSettingsPane.class);
490}