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}