001package jmri.jmrit.withrottle;
002
003import java.awt.Dimension;
004import java.awt.GridBagConstraints;
005import java.awt.GridBagLayout;
006import java.awt.event.ActionEvent;
007import java.awt.event.ActionListener;
008import java.net.InetAddress;
009import java.net.NetworkInterface;
010import java.net.SocketException;
011import java.text.MessageFormat;
012import java.util.ArrayList;
013import java.util.Enumeration;
014import javax.swing.AbstractAction;
015import javax.swing.Action;
016import javax.swing.ImageIcon;
017import javax.swing.JComboBox;
018import javax.swing.JLabel;
019import javax.swing.JMenu;
020import javax.swing.JMenuBar;
021import javax.swing.JMenuItem;
022import javax.swing.JPanel;
023import javax.swing.JScrollPane;
024import javax.swing.JTable;
025import javax.swing.JToolBar;
026import javax.swing.WindowConstants;
027import jmri.InstanceManager;
028import jmri.UserPreferencesManager;
029import jmri.jmrit.roster.rostergroup.RosterGroupSelector;
030import jmri.jmrit.roster.swing.RosterGroupComboBox;
031import jmri.jmrit.throttle.LargePowerManagerButton;
032import jmri.jmrit.throttle.StopAllButton;
033import jmri.util.FileUtil;
034import jmri.util.JmriJFrame;
035import jmri.util.prefs.JmriPreferencesActionFactory;
036
037/**
038 * UserInterface.java Create a window for WiThrottle information and and create
039 * a FacelessServer thread to handle jmdns and device requests
040 *
041 * @author Brett Hoffman Copyright (C) 2009, 2010
042 * @author Randall Wood Copyright (C) 2013
043 * @author Paul Bender Copyright (C) 2018
044 */
045public class UserInterface extends JmriJFrame implements DeviceListener, RosterGroupSelector {
046
047    JMenuBar menuBar;
048    JMenuItem serverOnOff;
049    JPanel panel;
050    JLabel portLabel = new JLabel(Bundle.getMessage("LabelPending"));
051    JLabel manualPortLabel = new JLabel();
052    String manualPortLabelString = ""; //append IPv4 addresses as they respond to zeroconf
053    JLabel numConnected;
054    JScrollPane scrollTable;
055    JTable withrottlesList;
056    WiThrottlesListModel withrottlesListModel;
057    UserPreferencesManager userPreferences = InstanceManager.getDefault(UserPreferencesManager.class);
058    String rosterGroupSelectorPreferencesName = this.getClass().getName() + ".rosterGroupSelector";
059    RosterGroupComboBox rosterGroupSelector = new RosterGroupComboBox(userPreferences.getComboBoxLastSelection(rosterGroupSelectorPreferencesName));
060
061    //keep a reference to the actual server
062    private FacelessServer facelessServer;
063
064    // Server iVars
065    boolean isListen;
066    private final ArrayList<DeviceServer> deviceList = new ArrayList<>();
067
068    /**
069     * Save the last known size and the last known location since 4.15.4.
070     */
071    UserInterface() {
072        super(true, true);
073
074        isListen = true;
075        facelessServer = (FacelessServer) InstanceManager.getOptionalDefault(DeviceManager.class).orElseGet(() -> {
076            return InstanceManager.setDefault(DeviceManager.class, new FacelessServer());
077        });
078
079        // add ourselves as device listeners for any existing devices
080        for (DeviceServer ds : facelessServer.getDeviceList()) {
081            deviceList.add(ds);
082            ds.addDeviceListener(this);
083        }
084
085        facelessServer.addDeviceListener(this);
086
087        //update the server with the currently selected roster group
088        facelessServer.setSelectedRosterGroup(rosterGroupSelector.getSelectedItem());
089
090        //show all IPv4 addresses in window, for use by manual connections
091        addIPAddressesToUI();
092
093        createWindow();
094
095    } // End of constructor
096
097    private void addIPAddressesToUI() {
098        //get port# directly from prefs
099        int port = InstanceManager.getDefault(WiThrottlePreferences.class).getPort();
100        //list IPv4 addresses on the UI, for manual connections
101        StringBuilder as = new StringBuilder(); //build multiline string of valid addresses
102        try {
103            // This code based on ReportContext.addNetworkInfo()
104            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
105            while (networkInterfaces.hasMoreElements()) {
106                NetworkInterface networkInterface = networkInterfaces.nextElement();
107                Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
108                while (inetAddresses.hasMoreElements()) {
109                    InetAddress inetAddress = inetAddresses.nextElement();
110                    String hostAddress = inetAddress.getHostAddress();
111                    if (!hostAddress.equals("0.0.0.0") && !hostAddress.regionMatches(0, "127", 0, 3) && !hostAddress.contains(":")) {
112                        this.portLabel.setText(inetAddress.getHostName());
113                        as.append(hostAddress).append(":").append(port).append("<br/>");
114                    }
115                }
116            }
117            this.manualPortLabel.setText("<html>" + as + "</html>"); // NOI18N
118
119        } catch (SocketException ex) {
120            log.warn("Unable to enumerate Network Interfaces: {}", ex.getMessage());
121        }
122
123    }
124
125    protected void createWindow() {
126        panel = new JPanel();
127        panel.setLayout(new GridBagLayout());
128        GridBagConstraints con = new GridBagConstraints();
129        getContentPane().add(panel);
130        con.fill = GridBagConstraints.NONE;
131        con.weightx = 0.5;
132        con.weighty = 0;
133
134        JLabel label = new JLabel(MessageFormat.format(Bundle.getMessage("LabelListening"), new Object[]{DeviceServer.getWiTVersion()}));
135        con.gridx = 0;
136        con.gridy = 0;
137        con.gridwidth = 2;
138        panel.add(label, con);
139
140        con.gridx = 0;
141        con.gridy = 1;
142        con.gridwidth = 2;
143        panel.add(portLabel, con);
144
145        con.gridy = 2;
146        panel.add(manualPortLabel, con);
147
148        numConnected = new JLabel(Bundle.getMessage("LabelClients") + " " + deviceList.size());
149        con.weightx = 0;
150        con.gridx = 2;
151        con.gridy = 2;
152        con.ipadx = 5;
153        con.gridwidth = 1;
154        panel.add(numConnected, con);
155
156        JPanel rgsPanel = new JPanel();
157        rgsPanel.add(new JLabel(Bundle.getMessage("RosterGroupLabel")));
158        rgsPanel.add(rosterGroupSelector);
159        rgsPanel.setToolTipText(Bundle.getMessage("RosterGroupToolTip"));
160        JToolBar withrottleToolBar = new JToolBar();
161        withrottleToolBar.setFloatable(false);
162        withrottleToolBar.add(new StopAllButton());
163        withrottleToolBar.add(new LargePowerManagerButton());
164        withrottleToolBar.add(rgsPanel);
165        con.weightx = 0.5;
166        con.ipadx = 0;
167        con.gridx = 1;
168        con.gridy = 3;
169        con.gridwidth = 2;
170        panel.add(withrottleToolBar, con);
171
172        JLabel icon;
173        java.net.URL imageURL = FileUtil.findURL("resources/IconForWiThrottle.gif");
174
175        if (imageURL != null) {
176            ImageIcon image = new ImageIcon(imageURL);
177            icon = new JLabel(image);
178            con.weightx = 0.5;
179            con.gridx = 2;
180            con.gridy = 0;
181            con.ipady = 5;
182            con.gridheight = 2;
183            panel.add(icon, con);
184        }
185
186//  Add a list of connected devices and the address they are set to.
187        withrottlesListModel = new WiThrottlesListModel(deviceList);
188        withrottlesList = new JTable(withrottlesListModel);
189        withrottlesList.setPreferredScrollableViewportSize(new Dimension(300, 80));
190
191        withrottlesList.setRowHeight(20);
192        scrollTable = new JScrollPane(withrottlesList);
193
194        con.gridx = 0;
195        con.gridy = 4;
196        con.weighty = 1.0;
197        con.ipadx = 10;
198        con.ipady = 10;
199        con.gridheight = 3;
200        con.gridwidth = GridBagConstraints.REMAINDER;
201        con.fill = GridBagConstraints.BOTH;
202        panel.add(scrollTable, con);
203
204//  Create the menu to use with WiThrottle window. Has to be before pack() for Windows.
205        buildMenu();
206
207//  Set window size & location
208        this.setTitle("WiThrottle");
209        this.pack();
210
211        this.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
212
213        setVisible(true);
214        setMinimumSize(new Dimension(400, 250));
215
216        rosterGroupSelector.addActionListener(new ActionListener() {
217
218            @SuppressWarnings("unchecked")
219            @Override
220            public void actionPerformed(ActionEvent e) {
221                String s = (String) ((JComboBox<String>) e.getSource()).getSelectedItem();
222                userPreferences.setComboBoxLastSelection(rosterGroupSelectorPreferencesName, s);
223                facelessServer.setSelectedRosterGroup(s);
224//              Send new selected roster group to all devices
225                for (DeviceServer device : deviceList) {
226                    device.sendPacketToDevice(device.sendRoster());
227                }
228            }
229        });
230    }
231
232    protected void buildMenu() {
233        this.setJMenuBar(new JMenuBar());
234
235        JMenu menu = new JMenu(Bundle.getMessage("MenuMenu"));
236        serverOnOff = new JMenuItem(Bundle.getMessage("MenuMenuStop"));
237        serverOnOff.addActionListener(new AbstractAction() {
238
239            @Override
240            public void actionPerformed(ActionEvent event) {
241                if (isListen) { // Stop server, remove addresses from UI
242                    disableServer();
243                    serverOnOff.setText(Bundle.getMessage("MenuMenuStart"));
244                    portLabel.setText(Bundle.getMessage("LabelNone"));
245                    manualPortLabel.setText(null);
246                } else { // Restart server
247                    enableServer();
248                    serverOnOff.setText(Bundle.getMessage("MenuMenuStop"));
249                    addIPAddressesToUI();
250                }
251            }
252        });
253
254        menu.add(serverOnOff);
255
256        menu.add(new ControllerFilterAction());
257
258        Action prefsAction = InstanceManager.getDefault(JmriPreferencesActionFactory.class).getCategorizedAction(
259                Bundle.getMessage("MenuMenuPrefs"),
260                "WITHROTTLE");
261
262        menu.add(prefsAction);
263
264        this.getJMenuBar().add(menu);
265
266        // add help menu
267        addHelpMenu("package.jmri.jmrit.withrottle.UserInterface", true);
268    }
269
270    /**
271     * Provide public access to the throttle list model for Jython
272     */
273    public WiThrottlesListModel getThrottleList() {
274        return withrottlesListModel;
275    }
276
277    @Override
278    public void notifyDeviceConnected(DeviceServer device) {
279
280        deviceList.add(device);
281        if (withrottlesListModel != null) {
282            withrottlesListModel.updateDeviceList(deviceList);
283        }
284        if (numConnected != null) {
285            numConnected.setText(Bundle.getMessage("LabelClients") + " " + deviceList.size());
286        }
287    }
288
289    @Override
290    public void notifyDeviceDisconnected(DeviceServer device) {
291        if (deviceList.size() < 1) {
292            return;
293        }
294        if (!deviceList.remove(device)) {
295            return;
296        }
297
298        if (numConnected != null) {
299            numConnected.setText(Bundle.getMessage("LabelClients") + " " + deviceList.size());
300        }
301        if (withrottlesListModel != null) {
302            withrottlesListModel.updateDeviceList(deviceList);
303        }
304        device.removeDeviceListener(this);
305    }
306
307    @Override
308    public void notifyDeviceAddressChanged(DeviceServer device) {
309        if (withrottlesListModel != null) {
310            withrottlesListModel.updateDeviceList(deviceList);
311        }
312    }
313
314    /**
315     * Received an UDID, update the device list
316     * @param device the device to update for
317     */
318    @Override
319    public void notifyDeviceInfoChanged(DeviceServer device) {
320        if (withrottlesListModel != null) {
321            withrottlesListModel.updateDeviceList(deviceList);
322        }
323    }
324
325    // this is package protected so tests can trigger easily.
326    void disableServer() {
327        facelessServer.disableServer();
328        isListen = false;
329    }
330
331    //tell the server thread to start listening again
332    private void enableServer() {
333        facelessServer.listen();
334        isListen = true;
335    }
336
337    @Override
338    public String getSelectedRosterGroup() {
339        return rosterGroupSelector.getSelectedRosterGroup();
340    }
341
342    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserInterface.class);
343}