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