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}