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