001package jmri.jmrit.roster; 002 003import java.awt.*; 004import java.awt.event.FocusEvent; 005import java.awt.event.FocusListener; 006import java.text.DateFormat; 007import java.util.ArrayList; 008import java.util.Arrays; 009import java.util.List; 010 011import javax.swing.*; 012 013import jmri.DccLocoAddress; 014import jmri.InstanceManager; 015import jmri.LocoAddress; 016import jmri.jmrit.DccLocoAddressSelector; 017import jmri.jmrit.decoderdefn.DecoderFile; 018import jmri.jmrit.decoderdefn.DecoderIndexFile; 019import jmri.util.swing.JmriJOptionPane; 020 021/** 022 * Display and enable editing a RosterEntry panel to display on first tab "Roster Entry". 023 * Called from {@link jmri.jmrit.symbolicprog.tabbedframe.PaneProgFrame}#makeInfoPane(RosterEntry) 024 * 025 * @author Bob Jacobsen Copyright (C) 2001 026 * @author Dennis Miller Copyright 2004, 2005 027 */ 028public class RosterEntryPane extends javax.swing.JPanel { 029 030 // Field sizes expanded to 30 from 12 to match comment 031 // fields and allow for more text to be displayed 032 JTextField id = new JTextField(30); 033 JTextField roadName = new JTextField(30); 034 JTextField maxSpeed = new JTextField(3); 035 JSpinner maxSpeedSpinner = new JSpinner(); // percentage stored as fraction 036 037 JTextField roadNumber = new JTextField(30); 038 JTextField mfg = new JTextField(30); 039 JTextField model = new JTextField(30); 040 JTextField owner = new JTextField(30); 041 DccLocoAddressSelector addrSel = new DccLocoAddressSelector(); 042 043 JTextArea comment = new JTextArea(3, 50); 044 public String getComment() {return comment.getText();} 045 public void setComment(String text) {comment.setText(text);} 046 047 // JScrollPanes are defined with scroll bars on always to avoid undesirable resizing behavior 048 // Without this the field will shrink to minimum size any time the scroll bars become needed and 049 // the scroll bars are inside, not outside the field area, obscuring their contents. 050 // This way the shrinking does not happen and the scroll bars are outside the field area, 051 // leaving the contents visible 052 JScrollPane commentScroller = new JScrollPane(comment, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 053 JLabel dateUpdated = new JLabel(); 054 JLabel decoderModel = new JLabel(); 055 JLabel decoderFamily = new JLabel(); 056 JLabel decoderProgModes = new JLabel(); 057 JTextArea decoderComment = new JTextArea(3, 50); 058 JScrollPane decoderCommentScroller = new JScrollPane(decoderComment, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 059 060 Component pane; 061 RosterEntry re; 062 063 public RosterEntryPane(RosterEntry r) { 064 065 maxSpeedSpinner.setModel(new SpinnerNumberModel(1.00d, 0.00d, 1.00d, 0.01d)); 066 maxSpeedSpinner.setEditor(new JSpinner.NumberEditor(maxSpeedSpinner, "# %")); 067 id.setText(r.getId()); 068 069 if (r.getDccAddress().isEmpty()) { 070 // null address, so clear selector 071 addrSel.reset(); 072 } else { 073 // non-null address, so load 074 DccLocoAddress tempAddr = new DccLocoAddress( 075 Integer.parseInt(r.getDccAddress()), r.getProtocol()); 076 addrSel.setAddress(tempAddr); 077 } 078 079 // fill contents 080 RosterEntryPane.this.updateGUI(r); 081 082 pane = this; 083 re = r; 084 085 // add options 086 id.setToolTipText(Bundle.getMessage("ToolTipID")); 087 088 addrSel.setEnabled(false); 089 addrSel.setLocked(false); 090 091 if ((InstanceManager.getNullableDefault(jmri.ThrottleManager.class) != null) 092 && !InstanceManager.throttleManagerInstance().addressTypeUnique()) { 093 // This goes through to find common protocols between the command station and the decoder 094 // and will set the selection box list to match those that are common. 095 jmri.ThrottleManager tm = InstanceManager.throttleManagerInstance(); 096 List<LocoAddress.Protocol> protocolTypes = new ArrayList<>(Arrays.asList(tm.getAddressProtocolTypes())); 097 098 if (!protocolTypes.contains(LocoAddress.Protocol.DCC_LONG) && !protocolTypes.contains(LocoAddress.Protocol.DCC_SHORT)) { 099 //Multi-protocol systems so far are not worried about dcc long vs dcc short 100 List<DecoderFile> l = InstanceManager.getDefault(DecoderIndexFile.class).matchingDecoderList(null, r.getDecoderFamily(), null, null, null, r.getDecoderModel()); 101 if (log.isDebugEnabled()) { 102 log.debug("found {} matched", l.size()); 103 } 104 if (l.isEmpty()) { 105 log.debug("Loco uses {} {} decoder, but no such decoder defined", decoderFamily, decoderModel); 106 // fall back to use just the decoder name, not family 107 l = InstanceManager.getDefault(DecoderIndexFile.class).matchingDecoderList(null, null, null, null, null, r.getDecoderModel()); 108 if (log.isDebugEnabled()) { 109 log.debug("found {} matches without family key", l.size()); 110 } 111 } 112 DecoderFile d; 113 if (!l.isEmpty()) { 114 d = l.get(0); 115 if (d != null && d.getSupportedProtocols().length > 0) { 116 ArrayList<String> protocols = new ArrayList<>(d.getSupportedProtocols().length); 117 118 for (LocoAddress.Protocol i : d.getSupportedProtocols()) { 119 if (protocolTypes.contains(i)) { 120 protocols.add(tm.getAddressTypeString(i)); 121 } 122 } 123 addrSel = new DccLocoAddressSelector(protocols.toArray(new String[0])); 124 DccLocoAddress tempAddr = new DccLocoAddress( 125 Integer.parseInt(r.getDccAddress()), r.getProtocol()); 126 addrSel.setAddress(tempAddr); 127 addrSel.setEnabled(false); 128 addrSel.setLocked(false); 129 addrSel.setEnabledProtocol(true); 130 } 131 } 132 } 133 } 134 135 JPanel selPanel = addrSel.getCombinedJPanel(); 136 selPanel.setToolTipText(Bundle.getMessage("ToolTipDccAddress")); 137 decoderModel.setToolTipText(Bundle.getMessage("ToolTipDecoderModel")); 138 decoderFamily.setToolTipText(Bundle.getMessage("ToolTipDecoderFamily")); 139 decoderProgModes.setToolTipText(Bundle.getMessage("ToolTipDecoderProgModes")); 140 dateUpdated.setToolTipText(Bundle.getMessage("ToolTipDateUpdated")); 141 id.addFocusListener(new FocusListener() { 142 @Override 143 public void focusGained(FocusEvent e) { 144 } 145 146 @Override 147 public void focusLost(FocusEvent e) { 148 if (checkDuplicate()) { 149 JmriJOptionPane.showMessageDialog(pane, Bundle.getMessage("ErrorDuplicateID")); 150 } 151 } 152 }); 153 154 // New GUI to allow multiline Comment and Decoder Comment fields 155 // Set up constraints objects for convenience in GridBagLayout alignment 156 GridBagLayout gbLayout = new GridBagLayout(); 157 GridBagConstraints cL = new GridBagConstraints(); 158 GridBagConstraints cR = new GridBagConstraints(); 159 Dimension minFieldDim = new Dimension(150, 20); 160 Dimension minScrollerDim = new Dimension(165, 42); 161 super.setLayout(gbLayout); 162 163 cL.gridx = 0; 164 cL.gridy = 0; 165 cL.ipadx = 3; 166 cL.anchor = GridBagConstraints.NORTHWEST; 167 cL.insets = new Insets(0, 0, 0, 15); 168 JLabel row0Label = new JLabel(Bundle.getMessage("FieldID") + ":"); 169 id.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldID")); 170 gbLayout.setConstraints(row0Label, cL); 171 super.add(row0Label); 172 173 cR.gridx = 1; 174 cR.gridy = 0; 175 cR.anchor = GridBagConstraints.WEST; 176 id.setMinimumSize(minFieldDim); 177 gbLayout.setConstraints(id, cR); 178 super.add(id); 179 180 cL.gridy++; 181 JLabel row1Label = new JLabel(Bundle.getMessage("FieldRoadName") + ":"); 182 roadName.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldRoadName")); 183 gbLayout.setConstraints(row1Label, cL); 184 super.add(row1Label); 185 186 cR.gridy = cL.gridy; 187 roadName.setMinimumSize(minFieldDim); 188 gbLayout.setConstraints(roadName, cR); 189 super.add(roadName); 190 191 cL.gridy++; 192 JLabel row2Label = new JLabel(Bundle.getMessage("FieldRoadNumber") + ":"); 193 roadNumber.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldRoadNumber")); 194 gbLayout.setConstraints(row2Label, cL); 195 super.add(row2Label); 196 197 cR.gridy = cL.gridy; 198 roadNumber.setMinimumSize(minFieldDim); 199 gbLayout.setConstraints(roadNumber, cR); 200 super.add(roadNumber); 201 202 cL.gridy++; 203 JLabel row3Label = new JLabel(Bundle.getMessage("FieldManufacturer") + ":"); 204 mfg.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldManufacturer")); 205 gbLayout.setConstraints(row3Label, cL); 206 super.add(row3Label); 207 208 cR.gridy = cL.gridy; 209 mfg.setMinimumSize(minFieldDim); 210 gbLayout.setConstraints(mfg, cR); 211 super.add(mfg); 212 213 cL.gridy++; 214 JLabel row4Label = new JLabel(Bundle.getMessage("FieldOwner") + ":"); 215 owner.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldOwner")); 216 gbLayout.setConstraints(row4Label, cL); 217 super.add(row4Label); 218 219 cR.gridy = cL.gridy; 220 owner.setMinimumSize(minFieldDim); 221 gbLayout.setConstraints(owner, cR); 222 super.add(owner); 223 224 cL.gridy++; 225 JLabel row5Label = new JLabel(Bundle.getMessage("FieldModel") + ":"); 226 model.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldModel")); 227 gbLayout.setConstraints(row5Label, cL); 228 super.add(row5Label); 229 230 cR.gridy = cL.gridy; 231 model.setMinimumSize(minFieldDim); 232 gbLayout.setConstraints(model, cR); 233 super.add(model); 234 235 cL.gridy++; 236 JLabel row6Label = new JLabel(Bundle.getMessage("FieldDCCAddress") + ":"); 237 selPanel.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldDCCAddress")); 238 gbLayout.setConstraints(row6Label, cL); 239 super.add(row6Label); 240 241 cR.gridy = cL.gridy; 242 gbLayout.setConstraints(selPanel, cR); 243 super.add(selPanel); 244 245 cL.gridy++; 246 JLabel row7Label = new JLabel(Bundle.getMessage("FieldSpeedLimit") + ":"); 247 maxSpeedSpinner.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldSpeedLimit")); 248 gbLayout.setConstraints(row7Label, cL); 249 super.add(row7Label); 250 251 cR.gridy = cL.gridy; // JSpinner is initialised in RosterEntryPane() 252 gbLayout.setConstraints(maxSpeedSpinner, cR); 253 super.add(maxSpeedSpinner); 254 255 cL.gridy++; 256 JLabel row8Label = new JLabel(Bundle.getMessage("FieldComment") + ":"); 257 // ensure same font on textarea as textfield 258 // as this is not true in all GUI types. 259 comment.setFont(owner.getFont()); 260 commentScroller.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldComment")); 261 gbLayout.setConstraints(row8Label, cL); 262 super.add(row8Label); 263 264 cR.gridy = cL.gridy; 265 commentScroller.setMinimumSize(minScrollerDim); 266 gbLayout.setConstraints(commentScroller, cR); 267 super.add(commentScroller); 268 269 cL.gridy++; 270 JLabel row9Label = new JLabel(Bundle.getMessage("FieldDecoderFamily") + ":"); 271 decoderFamily.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldDecoderFamily")); 272 gbLayout.setConstraints(row9Label, cL); 273 super.add(row9Label); 274 275 cR.gridy = cL.gridy; 276 decoderFamily.setMinimumSize(minFieldDim); 277 gbLayout.setConstraints(decoderFamily, cR); 278 super.add(decoderFamily); 279 280 cL.gridy++; 281 JLabel row10Label = new JLabel(Bundle.getMessage("FieldDecoderModel") + ":"); 282 decoderModel.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldDecoderModel")); 283 gbLayout.setConstraints(row10Label, cL); 284 super.add(row10Label); 285 286 cR.gridy = cL.gridy; 287 decoderModel.setMinimumSize(minFieldDim); 288 gbLayout.setConstraints(decoderModel, cR); 289 super.add(decoderModel); 290 291 cL.gridy++; 292 JLabel row11Label = new JLabel(Bundle.getMessage("FieldDecoderModes") + ":"); 293 decoderProgModes.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldDecoderModes")); 294 gbLayout.setConstraints(row11Label, cL); 295 super.add(row11Label); 296 297 cR.gridy = cL.gridy; 298 decoderProgModes.setMinimumSize(minFieldDim); 299 gbLayout.setConstraints(decoderProgModes, cR); 300 super.add(decoderProgModes); 301 302 cL.gridy++; 303 JLabel row12Label = new JLabel(Bundle.getMessage("FieldDecoderComment") + ":"); 304 // ensure same font on textarea as textfield 305 // as this is not true in all GUI types. 306 decoderComment.setFont(owner.getFont()); 307 decoderCommentScroller.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldDecoderComment")); 308 gbLayout.setConstraints(row12Label, cL); 309 super.add(row12Label); 310 311 cR.gridy = cL.gridy; 312 decoderCommentScroller.setMinimumSize(minScrollerDim); 313 gbLayout.setConstraints(decoderCommentScroller, cR); 314 super.add(decoderCommentScroller); 315 316 cL.gridy++; 317 JLabel row13Label = new JLabel(Bundle.getMessage("FieldDateUpdated") + ":"); 318 dateUpdated.getAccessibleContext().setAccessibleName(Bundle.getMessage("FieldDateUpdated")); 319 gbLayout.setConstraints(row13Label, cL); 320 super.add(row13Label); 321 322 cR.gridy = cL.gridy; 323 dateUpdated.setMinimumSize(minFieldDim); 324 gbLayout.setConstraints(dateUpdated, cR); 325 super.add(dateUpdated); 326 } 327 328 double maxSet; 329 330 /** 331 * Do the GUI contents agree with a RosterEntry? 332 * 333 * @param r the entry to compare 334 * @return true if entry in GUI does not match r; false otherwise 335 */ 336 public boolean guiChanged(RosterEntry r) { 337 if (!r.getRoadName().equals(roadName.getText())) { 338 return true; 339 } 340 if (!r.getRoadNumber().equals(roadNumber.getText())) { 341 return true; 342 } 343 if (!r.getMfg().equals(mfg.getText())) { 344 return true; 345 } 346 if (!r.getOwner().equals(owner.getText())) { 347 return true; 348 } 349 if (!r.getModel().equals(model.getText())) { 350 return true; 351 } 352 if (!r.getComment().equals(comment.getText())) { 353 return true; 354 } 355 if (!r.getDecoderFamily().equals(decoderFamily.getText())) { 356 return true; 357 } 358 if (!r.getDecoderModel().equals(decoderModel.getText())) { 359 return true; 360 } 361 if (!r.getProgrammingModes().equals(decoderProgModes.getText())) { 362 return true; 363 } 364 if (!r.getDecoderComment().equals(decoderComment.getText())) { 365 return true; 366 } 367 if (!r.getId().equals(id.getText())) { 368 return true; 369 } 370 maxSet = (Double) maxSpeedSpinner.getValue(); 371 if (r.getMaxSpeedPCT() != (int) Math.round(100 * maxSet)) { 372 log.debug("check: {}|{}", r.getMaxSpeedPCT(), (int) Math.round(100 * maxSet)); 373 return true; 374 } 375 DccLocoAddress a = addrSel.getAddress(); 376 if (a == null) { 377 return !r.getDccAddress().isEmpty(); 378 } else { 379 if (r.getProtocol() != a.getProtocol()) { 380 return true; 381 } 382 return !r.getDccAddress().equals("" + a.getNumber()); 383 } 384 } 385 386 /** 387 * 388 * @return true if the value in the id JTextField is a duplicate of some 389 * other RosterEntry in the roster 390 */ 391 public boolean checkDuplicate() { 392 // check it's not a duplicate 393 List<RosterEntry> l = Roster.getDefault().matchingList(null, null, null, null, null, null, id.getText()); 394 boolean oops = false; 395 for (RosterEntry rosterEntry : l) { 396 if (re != rosterEntry) { 397 oops = true; 398 break; 399 } 400 } 401 return oops; 402 } 403 404 /** 405 * Fill a RosterEntry object from GUI contents. 406 * 407 * @param r the roster entry to display 408 */ 409 public void update(RosterEntry r) { 410 r.setId(id.getText()); 411 r.setRoadName(roadName.getText()); 412 r.setRoadNumber(roadNumber.getText()); 413 r.setMfg(mfg.getText()); 414 r.setOwner(owner.getText()); 415 r.setModel(model.getText()); 416 DccLocoAddress a = addrSel.getAddress(); 417 if (a != null) { 418 r.setDccAddress("" + a.getNumber()); 419 r.setProtocol(a.getProtocol()); 420 } 421 r.setComment(comment.getText()); 422 423 maxSet = (Double) maxSpeedSpinner.getValue(); 424 log.debug("maxSet saved: {}", maxSet); 425 r.setMaxSpeedPCT((int) Math.round(100 * maxSet)); 426 log.debug("maxSet read from config: {}", r.getMaxSpeedPCT()); 427 r.setDecoderFamily(decoderFamily.getText()); 428 r.setDecoderModel(decoderModel.getText()); 429 r.setDecoderComment(decoderComment.getText()); 430 } 431 432 /** 433 * Fill GUI from roster contents. 434 * 435 * @param r the roster entry to display 436 */ 437 public void updateGUI(RosterEntry r) { 438 roadName.setText(r.getRoadName()); 439 roadNumber.setText(r.getRoadNumber()); 440 mfg.setText(r.getMfg()); 441 owner.setText(r.getOwner()); 442 model.setText(r.getModel()); 443 comment.setText(r.getComment()); 444 decoderModel.setText(r.getDecoderModel()); 445 decoderFamily.setText(r.getDecoderFamily()); 446 decoderProgModes.setText(r.getProgrammingModes()); 447 decoderComment.setText(r.getDecoderComment()); 448 dateUpdated.setText((r.getDateModified() != null) 449 ? DateFormat.getDateTimeInstance().format(r.getDateModified()) 450 : r.getDateUpdated()); 451 // retrieve MaxSpeed from r 452 double maxSpeedSet = r.getMaxSpeedPCT() / 100d; // why resets to 100? 453 log.debug("Max Speed set to: {}", maxSpeedSet); 454 maxSpeedSpinner.setValue(maxSpeedSet); 455 log.debug("Max Speed in spinner: {}", maxSpeedSpinner.getValue()); 456 } 457 458 public void setDccAddress(String a) { 459 DccLocoAddress addr = addrSel.getAddress(); 460 LocoAddress.Protocol protocol = addr.getProtocol(); 461 try { 462 addrSel.setAddress(new DccLocoAddress(Integer.parseInt(a), protocol)); 463 } catch (NumberFormatException e) { 464 log.error("Can't set DccAddress to {}", a); 465 } 466 } 467 468 public void setDccAddressLong(boolean m) { 469 DccLocoAddress addr = addrSel.getAddress(); 470 int n = 0; 471 if (addr != null) { 472 //If the protocol is already set to something other than DCC, then do not try to configure it as DCC long or short. 473 if (addr.getProtocol() != LocoAddress.Protocol.DCC_LONG 474 && addr.getProtocol() != LocoAddress.Protocol.DCC_SHORT 475 && addr.getProtocol() != LocoAddress.Protocol.DCC) { 476 return; 477 } 478 n = addr.getNumber(); 479 } 480 addrSel.setAddress(new DccLocoAddress(n, m)); 481 } 482 483 public void dispose() { 484 log.debug("dispose"); 485 } 486 487 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RosterEntryPane.class); 488 489}