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}