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