001package jmri.jmrit.symbolicprog; 002 003import java.awt.event.ActionListener; 004import java.util.ArrayList; 005import java.util.List; 006import javax.swing.BoxLayout; 007import javax.swing.JComboBox; 008import javax.swing.JLabel; 009import javax.swing.JList; 010import javax.swing.JPanel; 011import javax.swing.JScrollPane; 012import javax.swing.JToggleButton; 013import javax.swing.ListSelectionModel; 014import javax.swing.event.ListSelectionEvent; 015import javax.swing.event.ListSelectionListener; 016import jmri.GlobalProgrammerManager; 017import jmri.InstanceManager; 018import jmri.Programmer; 019import jmri.jmrit.decoderdefn.DecoderFile; 020import jmri.jmrit.decoderdefn.DecoderIndexFile; 021import jmri.jmrit.progsupport.ProgModeSelector; 022import jmri.jmrit.roster.Roster; 023import jmri.jmrit.roster.RosterEntry; 024import org.slf4j.Logger; 025import org.slf4j.LoggerFactory; 026 027/** 028 * Provide GUI controls to select a known loco and/or new decoder. 029 * <p> 030 * This is an extension of the CombinedLocoSelPane class to use a JList instead 031 * of a JComboBox for the decoder selection. Also, this uses separate JLists for 032 * manufacturer and decoder model. The loco selection (Roster manipulation) 033 * parts are unchanged. 034 * <p> 035 * The JComboBox implementation always had to have selected entries, so we added 036 * dummy "select from .." items at the top and used those to indicate 037 * that there was no selection in that box. Here, the lack of a selection 038 * indicates there's no selection. 039 * 040 * @author Bob Jacobsen Copyright (C) 2001, 2002 041 */ 042public class CombinedLocoSelListPane extends CombinedLocoSelPane { 043 044 public CombinedLocoSelListPane(JLabel s, ProgModeSelector selector) { 045 super(s, selector); 046 } 047 048 /** 049 * Create the panel used to select the decoder 050 */ 051 @Override 052 protected JPanel layoutDecoderSelection() { 053 JPanel pane1a = new JPanel(); 054 pane1a.setLayout(new BoxLayout(pane1a, BoxLayout.X_AXIS)); 055 pane1a.add(new JLabel("Decoder installed: ")); 056 // create the list of manufacturers 057 mMfgList = new JList<>(); 058 updateMfgListContents(null); 059 mMfgList.clearSelection(); 060 mMfgList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 061 mMfgListener = new ListSelectionListener() { 062 @Override 063 public void valueChanged(ListSelectionEvent e) { 064 if (!mMfgList.isSelectionEmpty()) { 065 // manufacturer selected, update decoder list 066 String vMfg = mMfgList.getSelectedValue(); 067 try { 068 int vMfgID = Integer.parseInt( 069 InstanceManager.getDefault(DecoderIndexFile.class).mfgIdFromName(vMfg)); 070 071 listDecodersFromMfg(vMfgID, vMfg); 072 } catch (java.lang.NumberFormatException ex) { 073 // mfg number lookup failed for some reason 074 } 075 } else { 076 // no manufacturer selected, do nothing 077 } 078 } 079 }; 080 mMfgList.addListSelectionListener(mMfgListener); 081 082 mDecoderList = new JList<String>(InstanceManager.getDefault(DecoderIndexFile.class) 083 .matchingComboBox(null, null, null, null, null, null).getModel()); 084 mDecoderList.clearSelection(); 085 mDecoderList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 086 mDecoderListener = new ListSelectionListener() { 087 @Override 088 public void valueChanged(ListSelectionEvent e) { 089 if (!mDecoderList.isSelectionEmpty()) { 090 // decoder selected - reset and disable loco selection 091 locoBox.setSelectedIndex(0); 092 go2.setEnabled(true); 093 go2.setToolTipText(Bundle.getMessage("TipClickToOpen")); 094 updateMfgListToSelectedDecoder(); 095 } else { 096 // decoder not selected - require one 097 go2.setEnabled(false); 098 go2.setToolTipText(Bundle.getMessage("TipSelectLoco")); 099 } 100 } 101 }; 102 mDecoderList.addListSelectionListener(mDecoderListener); 103 104 pane1a.add(new JScrollPane(mMfgList)); 105 pane1a.add(new JScrollPane(mDecoderList)); 106 iddecoder = new JToggleButton("Ident"); 107 iddecoder.setToolTipText("Read the decoders mfg and version, then attempt to select its type"); 108 if (InstanceManager.getNullableDefault(GlobalProgrammerManager.class) != null) { 109 Programmer p = InstanceManager.getDefault(GlobalProgrammerManager.class).getGlobalProgrammer(); 110 if (p != null && !p.getCanRead()) { 111 // can't read, disable the button 112 iddecoder.setEnabled(false); 113 iddecoder.setToolTipText("Button disabled because configured command station can't read CVs"); 114 } 115 } 116 iddecoder.addActionListener(new ActionListener() { 117 @Override 118 public void actionPerformed(java.awt.event.ActionEvent e) { 119 if (log.isDebugEnabled()) { 120 log.debug("identify decoder pressed"); 121 } 122 startIdentifyDecoder(); 123 } 124 }); 125 pane1a.add(iddecoder); 126 pane1a.setAlignmentX(JLabel.RIGHT_ALIGNMENT); 127 return pane1a; 128 } 129 130 /** 131 * Update the contents of the manufacturer list to make sure it contains a 132 * specific value. Normally the list does not contain mfgs with no defined 133 * decoders; this allows you to also show a specific mfg that's of interest, 134 * even though there's no definitions for it. This is protected against 135 * invoking any listeners, as the change is meant to be transparent; the 136 * original selection is set back. 137 * @param specific The value to update 138 */ 139 void updateMfgListContents(String specific) { 140 if (mMfgListener != null) { 141 mMfgList.removeListSelectionListener(mMfgListener); 142 } 143 String currentValue = mMfgList.getSelectedValue(); 144 145 List<String> allMfgList = InstanceManager.getDefault(DecoderIndexFile.class).getMfgNameList(); 146 List<String> theMfgList = new ArrayList<>(); 147 148 for (int i = 0; i < allMfgList.size(); i++) { 149 // see if this qualifies; either a non-zero set of decoders, or 150 // matches the specific name 151 if ((specific != null && (allMfgList.get(i).equals(specific))) 152 || (0 != InstanceManager.getDefault(DecoderIndexFile.class) 153 .matchingDecoderList(allMfgList.get(i), null, null, null, null, null) 154 .size())) { 155 theMfgList.add(allMfgList.get(i)); 156 } 157 } 158 mMfgList.setListData(theMfgList.toArray(new String[0])); 159 160 mMfgList.setSelectedValue(currentValue, true); 161 if (mMfgListener != null) { 162 mMfgList.addListSelectionListener(mMfgListener); 163 } 164 165 } 166 167 /** 168 * Force the manufacturer list to select the mfg of the currently selected 169 * decoder. Note that this is complicated by the need to not trigger an 170 * update of the decoder list. 171 */ 172 void updateMfgListToSelectedDecoder() { 173 // update to point at this mfg, _without_ changing the decoder list 174 DecoderFile df = InstanceManager.getDefault(DecoderIndexFile.class) 175 .fileFromTitle(mDecoderList.getSelectedValue()); 176 if (log.isDebugEnabled()) { 177 log.debug("decoder selection changed to {}", mDecoderList.getSelectedValue()); 178 } 179 if (df != null) { 180 if (log.isDebugEnabled()) { 181 log.debug("matching mfg is {}", df.getMfg()); 182 } 183 updateMfgListWithoutTrigger(df.getMfg()); 184 } 185 } 186 187 /** 188 * Set a selection in the manufacturer list, without triggering an update of 189 * the decoder panel. 190 * @param mfg Selected manufacturer code 191 */ 192 void updateMfgListWithoutTrigger(String mfg) { 193 mMfgList.removeListSelectionListener(mMfgListener); 194 mMfgList.setSelectedValue(mfg, true); 195 mMfgList.addListSelectionListener(mMfgListener); 196 } 197 198 /** 199 * Decoder identify has matched one or more specific types 200 */ 201 @Override 202 void updateForDecoderTypeID(List<DecoderFile> pModelList) { 203 // use a DefaultComboBoxModel to get the efficient ctor 204 mDecoderList.setModel(DecoderIndexFile.jComboBoxModelFromList(pModelList)); 205 mDecoderList.setSelectedIndex(0); 206 } 207 208 /** 209 * Decoder identify has not matched specific types, but did find 210 * manufacturer match 211 * 212 * @param pMfg Manufacturer name. This is passed to save time, as it has 213 * already been determined once. 214 * @param pMfgID Manufacturer ID number (CV8) 215 * @param pModelID Model ID number (CV7) 216 */ 217 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST", 218 justification="String also built for display in _statusLabel") 219 @Override 220 void updateForDecoderMfgID(String pMfg, int pMfgID, int pModelID) { 221 String msg = "Found mfg " + pMfgID + " (" + pMfg + ") version " + pModelID + "; no such decoder defined"; 222 log.warn(msg); 223 _statusLabel.setText(msg); 224 // ensure manufacturer shows & is selected 225 updateMfgListContents(pMfg); 226 updateMfgListWithoutTrigger(pMfg); 227 // list all other decoders available, without a selection 228 listDecodersFromMfg(pMfgID, pMfg); 229 } 230 231 void listDecodersFromMfg(int pMfgID, String pMfg) { 232 // try to select all decoders from that MFG 233 JComboBox<String> temp = InstanceManager.getDefault(DecoderIndexFile.class).matchingComboBox(null, null, Integer.toString(pMfgID), null, null, null); 234 if (log.isDebugEnabled()) { 235 log.debug("mfg-only selectDecoder found {} matches", temp.getItemCount()); 236 } 237 // install all those in the JComboBox in place of the longer, original list 238 mDecoderList.setModel(temp.getModel()); 239 mDecoderList.clearSelection(); 240 updateMfgListWithoutTrigger(pMfg); 241 } 242 243 /** 244 * Decoder identify did not match anything, warn and show all 245 */ 246 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value="SLF4J_FORMAT_SHOULD_BE_CONST", 247 justification="String also built for display in _statusLabel") 248 @Override 249 void updateForDecoderNotID(int pMfgID, int pModelID) { 250 String msg = "Found mfg " + pMfgID + " version " + pModelID + "; no such manufacterer defined"; 251 log.warn(msg); 252 _statusLabel.setText(msg); 253 mMfgList.setSelectedIndex(1); 254 mMfgList.clearSelection(); 255 JComboBox<String> temp = InstanceManager.getDefault(DecoderIndexFile.class).matchingComboBox(null, null, null, null, null, null); 256 mDecoderList.setModel(temp.getModel()); 257 mDecoderList.clearSelection(); 258 } 259 260 /** 261 * Set the decoder selection to a specific decoder from a selected Loco 262 */ 263 @Override 264 void setDecoderSelectionFromLoco(String loco) { 265 // if there's a valid loco entry... 266 RosterEntry locoEntry = Roster.getDefault().entryFromTitle(loco); 267 if (locoEntry == null) { 268 return; 269 } 270 // get the decoder type, it has to be there (assumption!), 271 String modelString = locoEntry.getDecoderModel(); 272 // find the decoder mfg 273 String mfgString = InstanceManager.getDefault(DecoderIndexFile.class).fileFromTitle(modelString) 274 .getMfg(); 275 276 // then select it 277 updateMfgListWithoutTrigger(mfgString); 278 279 // decoder has to be there (assumption!) 280 // so load it into the list directly 281 String[] tempArray = new String[1]; 282 tempArray[0] = modelString; 283 mDecoderList.setListData(tempArray); 284 // select the entry you just put in, but don't trigger anything! 285 mDecoderList.removeListSelectionListener(mDecoderListener); 286 mDecoderList.setSelectedIndex(0); 287 mDecoderList.addListSelectionListener(mDecoderListener); 288 } 289 290 /** 291 * Has the user selected a decoder type, either manually or via a successful 292 * event? 293 * 294 * @return true if a decoder type is selected 295 */ 296 @Override 297 boolean isDecoderSelected() { 298 return !mDecoderList.isSelectionEmpty(); 299 } 300 301 /** 302 * Convert the decoder selection UI result into a name. 303 * 304 * @return The selected decoder type name, or null if none selected. 305 */ 306 @Override 307 protected String selectedDecoderType() { 308 if (!isDecoderSelected()) { 309 return null; 310 } else { 311 return mDecoderList.getSelectedValue(); 312 } 313 } 314 315 JList<String> mDecoderList; 316 ListSelectionListener mDecoderListener; 317 318 JList<String> mMfgList; 319 ListSelectionListener mMfgListener; 320 321 private final static Logger log = LoggerFactory.getLogger(CombinedLocoSelListPane.class); 322 323}