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