001package jmri.jmrix.openlcb.swing;
002
003import jmri.*;
004import jmri.jmrit.beantable.signalmast.SignalMastAddPane;
005import jmri.jmrit.catalog.NamedIcon;
006import jmri.SystemConnectionMemo;
007import jmri.util.ConnectionNameFromSystemName;
008
009import jmri.jmrix.openlcb.*;
010import jmri.jmrix.ConnectionConfig;
011import jmri.jmrix.ConnectionConfigManager;
012
013import java.awt.*;
014import java.text.DecimalFormat;
015import java.util.*;
016import java.util.ArrayList;
017import java.util.List;
018
019import javax.swing.*;
020import javax.swing.border.TitledBorder;
021import javax.annotation.Nonnull;
022
023import org.openide.util.lookup.ServiceProvider;
024import org.openlcb.swing.EventIdTextField;
025
026/**
027 * A pane for configuring OlcbSignalMast objects
028 *
029 * @see jmri.jmrit.beantable.signalmast.SignalMastAddPane
030 * @author Bob Jacobsen Copyright (C) 2018
031 * @since 4.11.2
032 */
033public class OlcbSignalMastAddPane extends SignalMastAddPane {
034
035    public OlcbSignalMastAddPane() {
036        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
037
038        litEventID.setText("00.00.00.00.00.00.00.00");
039        notLitEventID.setText("00.00.00.00.00.00.00.00");
040        heldEventID.setText("00.00.00.00.00.00.00.00");
041        notHeldEventID.setText("00.00.00.00.00.00.00.00");
042
043        // populate the OpenLCB connections list before creating GUI components.
044        getOlcbConnections();
045
046        // If the connections list has less than 2 items, don't show a selector.
047        // This maintains backward compatibility with previous versions.
048        if (olcbConnections != null && olcbConnections.size() > 1) {
049            // Connection selector
050            JPanel p = new JPanel();
051            TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black));
052            border.setTitle(Bundle.getMessage("OlcbConnection"));
053            p.setBorder(border);
054            p.setLayout(new jmri.util.javaworld.GridLayout2(3, 1));
055
056            p.add(connSelectionBox);
057            add(p);
058        }
059
060        // lit/unlit controls
061        JPanel p = new JPanel();
062        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
063        p.add(new JLabel(Bundle.getMessage("AllowUnLitLabel") + ": "));
064        p.add(allowUnLit);
065        p.setAlignmentX(Component.LEFT_ALIGNMENT);
066        add(p);
067        
068        // aspects controls
069        TitledBorder aspectsBorder = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black));
070        aspectsBorder.setTitle(Bundle.getMessage("EnterAspectsLabel"));
071        JScrollPane allAspectsScroll = new JScrollPane(allAspectsPanel);
072        allAspectsScroll.setBorder(aspectsBorder);
073        add(allAspectsScroll);
074        
075        JPanel p5;
076
077        // Lit
078        TitledBorder litborder = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black));
079        litborder.setTitle(Bundle.getMessage("LitUnLit"));
080        JPanel pLit = new JPanel();
081        pLit.setBorder(litborder);
082        pLit.setLayout(new BoxLayout(pLit, BoxLayout.Y_AXIS));
083        
084        p5 = new JPanel();
085        p5.setLayout(new BoxLayout(p5, BoxLayout.X_AXIS));
086        p5.add(new JLabel(Bundle.getMessage("LitLabel")));
087        p5.add(Box.createHorizontalGlue());
088        pLit.add(p5);
089        pLit.add(litEventID);
090        
091        p5 = new JPanel();
092        p5.setLayout(new BoxLayout(p5, BoxLayout.X_AXIS));
093        p5.add(new JLabel(Bundle.getMessage("NotLitLabel")));
094        p5.add(Box.createHorizontalGlue());
095        pLit.add(p5);
096        pLit.add(notLitEventID);
097        
098        add(pLit);
099       
100        // Held
101        TitledBorder heldborder = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black));
102        heldborder.setTitle(Bundle.getMessage("HeldUnHeld"));
103        JPanel pHeld= new JPanel();
104        pHeld.setBorder(heldborder);
105        pHeld.setLayout(new BoxLayout(pHeld, BoxLayout.Y_AXIS));
106        
107        p5 = new JPanel();
108        p5.setLayout(new BoxLayout(p5, BoxLayout.X_AXIS));
109        p5.add(new JLabel(Bundle.getMessage("HeldLabel")));
110        p5.add(Box.createHorizontalGlue());
111        pHeld.add(p5);
112        pHeld.add(heldEventID);
113        
114        p5 = new JPanel();
115        p5.setLayout(new BoxLayout(p5, BoxLayout.X_AXIS));
116        p5.add(new JLabel(Bundle.getMessage("NotHeldLabel")));
117        p5.add(Box.createHorizontalGlue());
118        pHeld.add(p5);
119        pHeld.add(notHeldEventID);
120        
121        add(pHeld);
122
123        // set up selection of connections, if needed
124        populateConnSelectionBox();
125
126    }
127
128    /** {@inheritDoc} */
129    @Override
130    @Nonnull public String getPaneName() {
131        return Bundle.getMessage("OlcbSignalMastPane");
132    }
133
134    final JCheckBox allowUnLit = new JCheckBox();
135
136    // This used to be called "disabledAspects", but that's misleading: it's actually a map of
137    // ALL aspects' "disabled" checkboxes, regardless of their enabled/disabled state.
138    LinkedHashMap<String, JCheckBox> allAspectsCheckBoxes = new LinkedHashMap<>(NOTIONAL_ASPECT_COUNT);
139    final LinkedHashMap<String, EventIdTextField> aspectEventIDs = new LinkedHashMap<>(NOTIONAL_ASPECT_COUNT);
140    final JPanel allAspectsPanel = new JPanel();
141    final EventIdTextField litEventID = new EventIdTextField();
142    final EventIdTextField notLitEventID = new EventIdTextField();
143    final EventIdTextField heldEventID = new EventIdTextField();
144    final EventIdTextField notHeldEventID = new EventIdTextField();
145
146    JComboBox<String> connSelectionBox = new JComboBox<String>();
147
148    OlcbSignalMast currentMast = null;
149
150    // Support for multiple OpenLCB connections with different prefixes
151    ArrayList<String> olcbConnections = null;
152
153    /** {@inheritDoc} */
154    @Override
155    public void setAspectNames(@Nonnull SignalAppearanceMap map, @Nonnull SignalSystem sigSystem) {
156        Enumeration<String> aspectNames = map.getAspects();
157        // update immediately
158        allAspectsCheckBoxes = new LinkedHashMap<>(NOTIONAL_ASPECT_COUNT);
159        allAspectsPanel.removeAll();
160        while (aspectNames.hasMoreElements()) {
161            String aspectName = aspectNames.nextElement();
162            JCheckBox disabled = new JCheckBox(aspectName);
163            allAspectsCheckBoxes.put(aspectName, disabled);
164            EventIdTextField eventID = new EventIdTextField();
165            eventID.setText("00.00.00.00.00.00.00.00");
166            aspectEventIDs.put(aspectName, eventID);
167        }
168        allAspectsPanel.setLayout(new BoxLayout(allAspectsPanel, BoxLayout.Y_AXIS));
169        for (Map.Entry<String, JCheckBox> entry : allAspectsCheckBoxes.entrySet()) {
170            JPanel p1 = new JPanel();
171            TitledBorder p1border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black));
172            p1border.setTitle(entry.getKey()); // Note this is not I18N'd: as-is from xml file
173            p1.setBorder(p1border);
174            p1.setLayout(new BoxLayout(p1, BoxLayout.X_AXIS));
175
176            // Attempt to load an icon for display
177            String iconLink = map.getImageLink(entry.getKey(), "default");
178            if (iconLink == null || iconLink.isEmpty()) {
179                log.debug("Got empty image link for {}", entry.getKey());
180            } else {
181                log.debug("Image link for {} is {}", entry.getKey(), iconLink);
182                if (!iconLink.contains("preference:")) {
183                    // This logic copied from SignalMastItemPanel.java
184                    iconLink = iconLink.substring(iconLink.indexOf("resources"));
185                }
186                NamedIcon n = null;
187                try {
188                    n = new NamedIcon(iconLink, iconLink);
189                    log.debug("Loaded icon {}", iconLink);
190                } catch (Exception e) {
191                    log.debug("Got exception trying to load icon link {}: {}", iconLink, e.getMessage());
192                }
193                // display icon
194                if (n != null) {
195                    p1.add(new JLabel(n));
196                }
197            }
198
199            JPanel p2 = new JPanel();
200            p2.setLayout(new BoxLayout(p2, BoxLayout.Y_AXIS));
201            // event ID text box
202            p2.add(aspectEventIDs.get(entry.getKey()));
203            // "Disable" checkbox
204            p2.add(entry.getValue());
205            entry.getValue().setName(entry.getKey());
206            entry.getValue().setText(Bundle.getMessage("DisableAspect"));
207            p1.add(p2);
208            allAspectsPanel.add(p1);
209        }
210
211        populateConnSelectionBox();
212
213        litEventID.setText("00.00.00.00.00.00.00.00");
214        notLitEventID.setText("00.00.00.00.00.00.00.00");
215        heldEventID.setText("00.00.00.00.00.00.00.00");
216        notHeldEventID.setText("00.00.00.00.00.00.00.00");
217
218        allAspectsPanel.revalidate();
219    }
220
221    /*
222     * Populate the list of valid OpenLCB connection names.
223     * Only connections that have "OpenLCB" or "LCC" as their manufacturer are added.
224     */
225    private void getOlcbConnections() {
226        olcbConnections = null;
227        ConnectionConfig[] conns = null;
228        try {
229            conns = InstanceManager.getDefault(ConnectionConfigManager.class).getConnections();
230        } catch (Exception e) {
231            log.info("No ConnectionConfigManager installed: Using default Olcb Connections");
232        }
233        if (conns == null || conns.length == 0) {
234            log.debug("Found null or empty connections list");
235            return;
236        }
237        for (int x = 0; x < conns.length; x++) {
238            ConnectionConfig cc = conns[x];
239            log.debug("conns[{}]: name={} info={} adapter={} conn={}  man={}",
240                      x, cc.name(), cc.getInfo(), cc.getAdapter(),
241                      cc.getConnectionName(), cc.getManufacturer());
242            /* As this is the Olcb signal mast add pane, only show OpenLCB/LCC connections */
243            String man = cc.getManufacturer();
244            String name = cc.getConnectionName();
245            if (man != null && name != null && !name.isEmpty() &&
246                (man.equals("OpenLCB") || man.equals("LCC"))) {
247                if (olcbConnections == null) {
248                    olcbConnections = new ArrayList<String>();
249                }
250                olcbConnections.add(name);
251            }
252        }
253    }
254
255    /*
256     * Populate the GUI connection selection box, if there are
257     * multiple OpenLCB connections.
258     */
259    private void populateConnSelectionBox() {
260        connSelectionBox.removeAllItems();
261        if (olcbConnections == null || olcbConnections.size() < 2) {
262            return;
263        }
264        for (String conn : olcbConnections) {
265            connSelectionBox.addItem(conn);
266        }
267        if (currentMast == null) {
268            connSelectionBox.setEnabled(true);
269            return;
270        }
271        // set the selected connection based on the current mast
272        String mastPrefix = currentMast.getSystemPrefix();
273        if (mastPrefix != null) {
274            for (String conn : olcbConnections) {
275                String connectionPrefix = ConnectionNameFromSystemName.getPrefixFromName(conn);
276                if (connectionPrefix != null && connectionPrefix.equals(mastPrefix)) {
277                    connSelectionBox.setSelectedItem(conn);
278                    break;
279                }
280            }
281        }
282        // Can't change connection on existing masts
283        connSelectionBox.setEnabled(false);
284    }
285
286    /** {@inheritDoc} */
287    @Override
288    public boolean canHandleMast(@Nonnull SignalMast mast) {
289        return mast instanceof OlcbSignalMast;
290    }
291
292    /** {@inheritDoc} */
293    @Override
294    public void setMast(SignalMast mast) { 
295        if (mast == null) { 
296            currentMast = null; 
297            // re-enable connection selector
298            populateConnSelectionBox();
299            return; 
300        }
301        
302        if (! (mast instanceof OlcbSignalMast) ) {
303            log.error("mast was wrong type: {} {}", mast.getSystemName(), mast.getClass().getName());
304            return;
305        }
306
307        currentMast = (OlcbSignalMast) mast;
308        List<String> disabledList = currentMast.getDisabledAspects();
309        if (disabledList != null) {
310            for (String aspect : disabledList) {
311                if (allAspectsCheckBoxes.containsKey(aspect)) {
312                    allAspectsCheckBoxes.get(aspect).setSelected(true);
313                }
314            }
315         }
316        for (String aspect : currentMast.getAllKnownAspects()) {
317            if (aspectEventIDs.get(aspect) == null) {
318                EventIdTextField eventID = new EventIdTextField();
319                eventID.setText("00.00.00.00.00.00.00.00");
320                aspectEventIDs.put(aspect, eventID);
321            }
322            if (currentMast.isOutputConfigured(aspect)) {
323                aspectEventIDs.get(aspect).setText(currentMast.getOutputForAppearance(aspect));
324            } else {
325                aspectEventIDs.get(aspect).setText("00.00.00.00.00.00.00.00");
326            }
327        }
328
329        litEventID.setText(currentMast.getLitEventId());
330        notLitEventID.setText(currentMast.getNotLitEventId());
331        heldEventID.setText(currentMast.getHeldEventId());
332        notHeldEventID.setText(currentMast.getNotHeldEventId());        
333
334        allowUnLit.setSelected(currentMast.allowUnLit());
335
336        // show current connection in selector
337        populateConnSelectionBox();
338
339        log.debug("setMast({})", mast);
340    }
341
342    final DecimalFormat paddedNumber = new DecimalFormat("0000");
343
344    /** {@inheritDoc} */
345    @Override
346    public boolean createMast(@Nonnull String sigsysname,
347                              @Nonnull String mastname,
348                              @Nonnull String username) {
349        if (currentMast == null) {
350            // create a mast
351            String selItem = (String)connSelectionBox.getSelectedItem();
352            String connectionPrefix = ConnectionNameFromSystemName.getPrefixFromName(selItem);
353            log.debug("selected name={}, prefix={}", selItem, connectionPrefix);
354            // if prefix is null, use default
355            if (connectionPrefix == null || connectionPrefix.isEmpty()) {
356                connectionPrefix = "M";
357            }
358
359            String type = mastname.substring(11, mastname.length() - 4);
360            String name = connectionPrefix + "F$olm:" + sigsysname + ":" + type;
361            name += "($" + (paddedNumber.format(OlcbSignalMast.getLastRef() + 1)) + ")";
362            log.debug("Creating mast: {}", name);
363            currentMast = new OlcbSignalMast(name);
364            if (!username.equals("")) {
365                currentMast.setUserName(username);
366            }
367            currentMast.setMastType(type);
368            InstanceManager.getDefault(jmri.SignalMastManager.class).register(currentMast);
369        }
370        
371        // load a new or existing mast
372        for (Map.Entry<String, JCheckBox> entry : allAspectsCheckBoxes.entrySet()) {
373            if (entry.getValue().isSelected()) {
374                currentMast.setAspectDisabled(entry.getKey());
375            } else {
376                currentMast.setAspectEnabled(entry.getKey());
377            }
378            currentMast.setOutputForAppearance(entry.getKey(), aspectEventIDs.get(entry.getKey()).getText());
379        }
380        
381        currentMast.setLitEventId(litEventID.getText());
382        currentMast.setNotLitEventId(notLitEventID.getText());
383        currentMast.setHeldEventId(heldEventID.getText());
384        currentMast.setNotHeldEventId(notHeldEventID.getText());
385
386        currentMast.setAllowUnLit(allowUnLit.isSelected());
387        return true;
388    }
389
390
391    @ServiceProvider(service = SignalMastAddPane.SignalMastAddPaneProvider.class)
392    static public class SignalMastAddPaneProvider extends SignalMastAddPane.SignalMastAddPaneProvider {
393
394        /**
395         * {@inheritDoc}
396         * Requires a valid OpenLCB connection
397         */
398        @Override
399        public boolean isAvailable() {
400            for (SystemConnectionMemo memo : InstanceManager.getList(SystemConnectionMemo.class)) {
401                if (memo instanceof jmri.jmrix.can.CanSystemConnectionMemo) {
402                    return true;
403                }
404            }
405            return false;
406        }
407
408        /** {@inheritDoc} */
409        @Override
410        @Nonnull public String getPaneName() {
411            return Bundle.getMessage("OlcbSignalMastPane");
412        }
413
414        /** {@inheritDoc} */
415        @Override
416        @Nonnull public SignalMastAddPane getNewPane() {
417            return new OlcbSignalMastAddPane();
418        }
419    }
420
421    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OlcbSignalMastAddPane.class);
422
423}