001package jmri.jmrix.openlcb.swing;
002
003import java.awt.event.ActionEvent;
004import java.awt.event.ActionListener;
005import java.awt.event.WindowEvent;
006
007import javax.swing.JButton;
008import javax.swing.JPanel;
009import javax.swing.JTextField;
010import javax.swing.Timer;
011import javax.swing.text.JTextComponent;
012
013import jmri.ShutDownTask;
014import jmri.jmrix.can.CanSystemConnectionMemo;
015import jmri.jmrix.openlcb.OlcbAddress;
016import jmri.jmrix.openlcb.OlcbEventNameStore;
017import jmri.util.JmriJFrame;
018import jmri.util.swing.WrapLayout;
019
020import org.openlcb.NodeID;
021import org.openlcb.EventID;
022import org.openlcb.OlcbInterface;
023import org.openlcb.cdi.impl.ConfigRepresentation;
024import org.openlcb.cdi.swing.CdiPanel;
025import org.openlcb.swing.EventIdTextField;
026
027/**
028 * Shared code for creating UI elements from different places in the application.
029 * <p>
030 * Created by bracz on 11/21/16.
031 */
032
033public class ClientActions {
034    private final OlcbInterface iface;
035    private final CanSystemConnectionMemo memo;
036
037    public ClientActions(OlcbInterface iface, CanSystemConnectionMemo memo) {
038        this.iface = iface;
039        this.memo = memo;
040    }
041
042    CdiPanel cdiPanel;
043    ShutDownTask shutDownTask;
044    
045    public void openCdiWindow(NodeID destNode, String description) {
046        final java.util.ArrayList<JButton> sensorButtonList = new java.util.ArrayList<>();
047        final java.util.ArrayList<JButton> turnoutButtonList = new java.util.ArrayList<>();
048
049        JmriJFrame f = new NodeSpecificFrame(destNode);
050        f.setTitle(Bundle.getMessage("CdiPanelConfigure", description));
051        f.setLayout(new javax.swing.BoxLayout(f.getContentPane(), javax.swing.BoxLayout.Y_AXIS));
052        f.addHelpMenu("package.jmri.jmrix.openlcb.swing.networktree.NetworkTreePane_CDItool", true);
053        
054        cdiPanel = new CdiPanel(){
055            // override and extend window closing behavior
056            @Override
057            protected void targetWindowClosingEvent(WindowEvent evt) { // evt is ignored here
058                log.trace("overridden targetWindowClosingEvent runs");
059                super.targetWindowClosingEvent(evt);
060            }
061            // when actually closing the window, also deregister the safety shutdown class
062            @Override
063            public void release() {
064                super.release();
065                jmri.InstanceManager.getDefault(jmri.ShutDownManager.class).deregister(shutDownTask);
066            }
067        };
068        f.add(cdiPanel);
069        cdiPanel.setEventTable(iface.getNodeStore().getSimpleNodeIdent(destNode).getUserName(),
070                iface.getEventTable());
071                
072        // Add a shutdown task to handle "Cancel" selections should there be unsaved
073        // changed at Shutdown
074        jmri.InstanceManager.getDefault(jmri.ShutDownManager.class).register(
075            shutDownTask = new ShutDownTask() {
076                @Override
077                public String getName() { return "CDI Window Check"; }
078                @Override
079                public Boolean call() {
080                    log.trace("call( checks contents)");
081                    boolean result = cdiPanel.checkOnWindowClosing(); // true to continue shutdown, false to not
082                    if (result) {
083                        // you don't want a second check on automatic window closing during shutdown
084                        cdiPanel.setWindowCloseCheckAlreadyHandled();
085                    }
086                    return result; 
087                }
088
089                @Override
090                public void propertyChange(java.beans.PropertyChangeEvent e) {
091                    // don't care if somebody else cancels
092                }
093                
094                @Override
095                public void run() {
096                    // we're shutting down, nothing to do 
097                }
098        });
099        
100        // create an object to add "New Sensor" buttons
101        CdiPanel.GuiItemFactory factory = new CdiPanel.GuiItemFactory() {
102            private boolean haveButtons = false;
103            @Override
104            public JButton handleReadButton(JButton button) {
105                return button;
106            }
107
108            @Override
109            public JButton handleWriteButton(JButton button) {
110                return button;
111            }
112
113            @Override
114            public void handleGroupPaneStart(JPanel pane) {
115                this.gpane = pane;
116                evt1 = null;
117                evt2 = null;
118                desc = null;
119            }
120
121            @Override
122            public void handleGroupPaneEnd(JPanel pane) {
123                if (gpane != null && evt1 != null && evt2 != null && desc != null) {
124                    JPanel p = new JPanel();
125                    p.setLayout(new WrapLayout());
126                    p.setAlignmentX(-1.0f);
127                    pane.add(p);
128                    JButton button = new JButton(Bundle.getMessage("CdiPanelMakeSensor"));
129                    p.add(button);
130                    sensorButtonList.add(button);
131                    button.addActionListener(new java.awt.event.ActionListener() {
132                        @Override
133                        public void actionPerformed(java.awt.event.ActionEvent e) {
134                            jmri.Sensor sensor = jmri.InstanceManager.sensorManagerInstance()
135                                    .provideSensor(memo.getSystemPrefix() + "S" + mevt1.getText() + ";" + mevt2.getText());
136                            if (mdesc.getText().length() > 0) {
137                                sensor.setUserName(mdesc.getText());
138                            }
139                            log.info("make sensor MS{};{} [{}]", mevt1.getText(), mevt2.getText(), mdesc.getText());
140                        }
141
142                        final JTextField mdesc = desc;
143                        final JTextComponent mevt1 = evt1;
144                        final JTextComponent mevt2 = evt2;
145                    });
146                    button = new JButton(Bundle.getMessage("CdiPanelMakeTurnout"));
147                    p.add(button);
148                    turnoutButtonList.add(button);
149                    button.addActionListener(new java.awt.event.ActionListener() {
150                        @Override
151                        public void actionPerformed(java.awt.event.ActionEvent e) {
152                            jmri.Turnout turnout = jmri.InstanceManager.turnoutManagerInstance()
153                                    .provideTurnout(memo.getSystemPrefix() + "T" + mevt1.getText() + ";" + mevt2.getText());
154                            if (mdesc.getText().length() > 0) {
155                                turnout.setUserName(mdesc.getText());
156                            }
157                            log.info("make turnout MT{};{} [{}]", mevt1.getText(), mevt2.getText(), mdesc.getText());
158                        }
159
160                        final JTextField mdesc = desc;
161                        final JTextComponent mevt1 = evt1;
162                        final JTextComponent mevt2 = evt2;
163                    });
164                    if (!haveButtons) {
165                        haveButtons = true;
166                        cdiPanel.addButtonToFooter(buttonForList(sensorButtonList, Bundle.getMessage("CdiPanelMakeAllSensors")));
167                        cdiPanel.addButtonToFooter(buttonForList(turnoutButtonList, Bundle.getMessage("CdiPanelMakeAllTurnouts")));
168                    }
169                    gpane = null;
170                    evt1 = null;
171                    evt2 = null;
172                    desc = null;
173                }
174            }
175
176            @Override
177            public JTextComponent handleEventIdTextField(EventIdTextField input) {
178                var field = new NamedEventIdTextField(memo);  // return our own constructed field
179
180                // What does this field do entry for?
181                if (evt1 == null) {
182                    evt1 = field;
183                } else if (evt2 == null) {
184                    evt2 = field;
185                } else {
186                    gpane = null;  // flag too many
187                }
188                return field;
189            }
190            
191            @Override
192            public JTextField handleStringValue(JTextField value) {
193                desc = value;
194                return value;
195            }
196
197            @Override
198            /**
199             * Make a sensor from a single Event ID.
200             * Set the user name from the CDI description (if available)
201             * {@inheritDoc}
202             */
203            public void makeSensor(String ev, String mdesc) {
204                jmri.Sensor sensor =
205                        jmri.InstanceManager.sensorManagerInstance()
206                                .provideSensor(memo.getSystemPrefix() + "S" + ev);
207                if (mdesc.length() > 0) {
208                    sensor.setUserName(mdesc);
209                }
210                log.debug("make sensor MS{} [{}]", ev, mdesc);
211            }
212
213            /** Convert a String into an EventID, doing any additional local
214             * dealiasing required.
215             * @param content Content to convert, e.g. from a text component
216             * @return eventID that represents the content
217             */
218             @Override
219             public EventID getEventIDFromString(String content) {
220                var address = new OlcbAddress(content, memo);
221                return address.toEventID();
222             }
223    
224            /** Convert an EventID into a String, doing any additional local
225             * aliasing required.
226             * @param event EventID to convert, e.g. from reading a node
227             * @return local representation fo that EventID, often just the dotted hex
228             */
229             @Override
230             public String getStringFromEventID(EventID event) {
231                var nameStore = memo.get(OlcbEventNameStore.class);
232                if (nameStore != null) {
233                    var name = nameStore.getEventName(event);
234                    if (name != null) {
235                        return name;
236                    }
237                }
238                return event.toShortString();  
239             }
240
241            JPanel gpane = null;
242            JTextField desc = null;
243            JTextComponent evt1 = null;
244            JTextComponent evt2 = null;
245        };
246        ConfigRepresentation rep = iface.getConfigForNode(destNode);
247
248        cdiPanel.initComponents(rep, factory);
249
250        f.pack();
251        f.setVisible(true);
252    }
253
254    JButton buttonForList(final java.util.ArrayList<JButton> list, String label) {
255        JButton b = new JButton(label);
256        b.addActionListener(e -> {
257            int delay = 0; //milliseconds
258            for (final JButton b1 : list) {
259
260                ActionListener taskPerformer = new ActionListener() {
261                    @Override
262                    public void actionPerformed(ActionEvent evt) {
263                        target.doClick();
264                    }
265                    final JButton target = b1;
266                };
267                Timer t = new Timer(delay, taskPerformer);
268                t.setRepeats(false);
269                t.start();
270                delay = delay + 150;
271            }
272        });
273        return b;
274    }
275
276    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ClientActions.class);
277}