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