001package jmri.jmrit.beantable.signalmast; 002 003import java.awt.Color; 004import java.awt.Component; 005import java.awt.event.ActionEvent; 006 007import java.util.ArrayList; 008import java.util.Enumeration; 009import java.util.HashSet; 010import java.util.LinkedHashMap; 011import java.util.List; 012import java.util.Map; 013import java.util.Set; 014 015import javax.annotation.Nonnull; 016import javax.swing.BorderFactory; 017import javax.swing.BoxLayout; 018import javax.swing.JCheckBox; 019import javax.swing.JLabel; 020import javax.swing.JPanel; 021import javax.swing.JScrollPane; 022import javax.swing.border.TitledBorder; 023 024import org.openide.util.lookup.ServiceProvider; 025 026import jmri.InstanceManager; 027import jmri.SignalAppearanceMap; 028import jmri.SignalHead; 029import jmri.SignalHeadManager; 030import jmri.SignalMast; 031import jmri.SignalMastManager; 032import jmri.SignalSystem; 033import jmri.implementation.DefaultSignalAppearanceMap; 034import jmri.implementation.SignalHeadSignalMast; 035import jmri.swing.NamedBeanComboBox; 036import jmri.util.StringUtil; 037import jmri.util.javaworld.GridLayout2; 038import jmri.util.swing.JComboBoxUtil; 039import jmri.util.swing.JmriJOptionPane; 040 041/** 042 * A pane for configuring SignalHeadSignalMast objects. 043 * 044 * @see jmri.jmrit.beantable.signalmast.SignalMastAddPane 045 * @author Bob Jacobsen Copyright (C) 2018 046 * @since 4.11.2 047 */ 048public class SignalHeadSignalMastAddPane extends SignalMastAddPane { 049 050 public SignalHeadSignalMastAddPane() { 051 initPane(); 052 } 053 054 final void initPane() { 055 056 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 057 // lit/unlit controls 058 JPanel p = new JPanel(); 059 p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); 060 p.add(new JLabel(Bundle.getMessage("AllowUnLitLabel") + ": ")); 061 p.add(allowUnLit); 062 p.setAlignmentX(Component.LEFT_ALIGNMENT); 063 add(p); 064 065 TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 066 border.setTitle(Bundle.getMessage("MenuItemSignalTable")); // Signal Heads 067 signalHeadPanel.setBorder(border); 068 add(signalHeadPanel); 069 070 includeUsed.addActionListener((ActionEvent e) -> { 071 refreshHeadComboBox(); 072 }); 073 074 // disabled aspects controls 075 TitledBorder disableborder = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black)); 076 disableborder.setTitle(Bundle.getMessage("DisableAspectsLabel")); 077 JScrollPane disabledAspectsScroll = new JScrollPane(disabledAspectsPanel); 078 disabledAspectsScroll.setBorder(disableborder); 079 add(disabledAspectsScroll); 080 } 081 082 /** {@inheritDoc} */ 083 @Override 084 @Nonnull public String getPaneName() { 085 return Bundle.getMessage("HeadCtlMast"); 086 } 087 088 089 JPanel signalHeadPanel = new JPanel(); 090 ArrayList<NamedBeanComboBox<SignalHead>> headList = new ArrayList<>(5); 091 private final JCheckBox includeUsed = new JCheckBox(Bundle.getMessage("IncludeUsedHeads")); 092 093 private final JCheckBox allowUnLit = new JCheckBox(); 094 095 LinkedHashMap<String, JCheckBox> disabledAspects = new LinkedHashMap<>(NOTIONAL_ASPECT_COUNT); 096 JPanel disabledAspectsPanel = new JPanel(); 097 098 SignalHeadSignalMast currentMast = null; 099 Set<SignalHead> alreadyUsed = new HashSet<>(); 100 DefaultSignalAppearanceMap map; 101 102 /** {@inheritDoc} */ 103 @Override 104 public void setAspectNames(@Nonnull SignalAppearanceMap newMap, @Nonnull SignalSystem sigSystem) { 105 log.debug("setAspectNames(...)"); 106 107 map = (DefaultSignalAppearanceMap)newMap; 108 109 int count = map.getAspectSettings(map.getAspects().nextElement()).length; 110 log.trace(" head count is {}", count); 111 112 Enumeration<String> aspects = map.getAspects(); 113 114 headList = new ArrayList<>(count); 115 116 signalHeadPanel.removeAll(); 117 for (int i = 0; i < count; i++) { 118 NamedBeanComboBox<SignalHead> head = new NamedBeanComboBox<>(InstanceManager.getDefault(SignalHeadManager.class)); 119 head.setExcludedItems(alreadyUsed); 120 headList.add(head); 121 signalHeadPanel.add(head); 122 JComboBoxUtil.setupComboBoxMaxRows(head); 123 } 124 signalHeadPanel.add(includeUsed); 125 126 signalHeadPanel.setLayout(new GridLayout2(0, 1)); // 0 means enough 127 signalHeadPanel.revalidate(); 128 129 disabledAspects = new LinkedHashMap<>(10); 130 disabledAspectsPanel.removeAll(); 131 while (aspects.hasMoreElements()) { 132 String aspect = aspects.nextElement(); 133 JCheckBox disabled = new JCheckBox(aspect); 134 disabledAspects.put(aspect, disabled); 135 } 136 137 for (Map.Entry<String, JCheckBox> entry : disabledAspects.entrySet()) { 138 disabledAspectsPanel.add(entry.getValue()); 139 } 140 141 disabledAspectsPanel.setLayout(new GridLayout2(0, 1)); // 0 means enough 142 disabledAspectsPanel.revalidate(); 143 } 144 145 /** {@inheritDoc} */ 146 @Override 147 public boolean canHandleMast(@Nonnull SignalMast mast) { 148 return mast instanceof SignalHeadSignalMast; 149 } 150 151 /** {@inheritDoc} */ 152 @Override 153 public void setMast(SignalMast mast) { 154 log.debug("setMast({})", mast); 155 if (mast == null) { 156 currentMast = null; 157 return; 158 } 159 160 if (! (mast instanceof SignalHeadSignalMast) ) { 161 log.error("mast was wrong type: {} {}", mast.getSystemName(), mast.getClass().getName()); 162 return; 163 } 164 165 currentMast = (SignalHeadSignalMast) mast; 166 167 // can't actually edit the heads in this kind of mast 168 int count = map.getAspectSettings(map.getAspects().nextElement()).length; 169 log.trace(" head count is {}", count); 170 signalHeadPanel.removeAll(); 171 signalHeadPanel.setLayout(new GridLayout2(count + 1, 1)); 172 for (int i = 0; i < count; i++) { 173 NamedBeanComboBox<SignalHead> head = new NamedBeanComboBox<>(InstanceManager.getDefault(SignalHeadManager.class)); 174 head.setExcludedItems(alreadyUsed); 175 headList.add(head); 176 177 head.setEnabled(false); 178 head.setSelectedItem(currentMast.getHeadsUsed().get(i).getBean()); // must match NamedBeanComboBox above 179 signalHeadPanel.add(head); 180 JComboBoxUtil.setupComboBoxMaxRows(head); 181 } 182 signalHeadPanel.add(includeUsed); 183 signalHeadPanel.revalidate(); 184 185 List<String> disabled = currentMast.getDisabledAspects(); 186 if (disabled != null) { 187 for (String aspect : disabled) { 188 if (disabledAspects.containsKey(aspect)) { 189 disabledAspects.get(aspect).setSelected(true); 190 } 191 } 192 } 193 194 allowUnLit.setSelected(currentMast.allowUnLit()); 195 196 log.trace("setMast {} end", mast); 197 } 198 199 /** {@inheritDoc} */ 200 @Override 201 public boolean createMast(@Nonnull String sigsysname, @Nonnull String mastname, @Nonnull String username) { 202 log.debug("createMast({},{})", sigsysname, mastname); 203 String name; 204 if (currentMast == null) { 205 StringBuilder build = new StringBuilder(); 206 build.append("IF$shsm:").append(sigsysname).append(":").append(mastname.substring(11, mastname.length() - 4)); 207 for (NamedBeanComboBox<SignalHead> head : headList) { 208 if (head != null && head.getSelectedItemDisplayName() != null) { 209 build.append("(").append(StringUtil.parenQuote(head.getSelectedItemDisplayName())).append(")"); 210 } 211 } 212 name = build.toString(); 213 log.debug("add signal: {}", name); 214 215 // see if it exists (remember, we're not handling a current mast) 216 SignalMast m = InstanceManager.getDefault(SignalMastManager.class).getSignalMast(name); 217 if (m != null) { 218 JmriJOptionPane.showMessageDialog(this, java.text.MessageFormat.format(Bundle.getMessage("DuplicateMast"), 219 new Object[]{m.getDisplayName()}), Bundle.getMessage("DuplicateMastTitle"), JmriJOptionPane.INFORMATION_MESSAGE); 220 return false; 221 } 222 try { 223 // now create it 224 currentMast = (SignalHeadSignalMast)InstanceManager.getDefault(SignalMastManager.class).provideSignalMast(name); 225 } catch (IllegalArgumentException ex) { 226 // user input no good 227 handleCreateException(name); 228 return false; // without creating 229 } 230 } 231 232 // load a new or existing mast 233 for (Map.Entry<String, JCheckBox> entry : disabledAspects.entrySet()) { 234 if (entry.getValue().isSelected()) { 235 currentMast.setAspectDisabled(entry.getKey()); 236 } else { 237 currentMast.setAspectEnabled(entry.getKey()); 238 } 239 } 240 241 // heads are attached via the system name 242 if (!username.isEmpty()) { 243 currentMast.setUserName(username); 244 } 245 246 currentMast.setAllowUnLit(allowUnLit.isSelected()); 247 248 return true; 249 } 250 251 protected void refreshHeadComboBox() { 252 log.trace("refreshHeadComboBox"); 253 if (includeUsed.isSelected()) { 254 alreadyUsed = new HashSet<>(); 255 } else { 256 List<SignalHead> alreadyUsedHeads = SignalHeadSignalMast.getSignalHeadsUsed(); 257 alreadyUsed = new HashSet<>(); 258 log.trace(" found {}", alreadyUsedHeads.size()); 259 for (SignalHead head : alreadyUsedHeads) { 260 alreadyUsed.add(head); 261 } 262 } 263 264 for (NamedBeanComboBox<SignalHead> head : headList) { 265 head.setExcludedItems(alreadyUsed); 266 } 267 } 268 269 void handleCreateException(String sysName) { 270 JmriJOptionPane.showMessageDialog(this, 271 Bundle.getMessage("ErrorSignalMastAddFailed", sysName) + "\n" + Bundle.getMessage("ErrorAddFailedCheck"), 272 Bundle.getMessage("ErrorTitle"), 273 JmriJOptionPane.ERROR_MESSAGE); 274 } 275 276 @ServiceProvider(service = SignalMastAddPane.SignalMastAddPaneProvider.class) 277 static public class SignalMastAddPaneProvider extends SignalMastAddPane.SignalMastAddPaneProvider { 278 /** {@inheritDoc} */ 279 @Override 280 @Nonnull public String getPaneName() { 281 return Bundle.getMessage("HeadCtlMast"); 282 } 283 /** {@inheritDoc} */ 284 @Override 285 @Nonnull public SignalMastAddPane getNewPane() { 286 return new SignalHeadSignalMastAddPane(); 287 } 288 } 289 290 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalHeadSignalMastAddPane.class); 291 292}