001package jmri.jmrit.z21server;
002
003import jmri.jmrit.throttle.LargePowerManagerButton;
004import jmri.jmrit.throttle.StopAllButton;
005import jmri.util.FileUtil;
006import jmri.util.JmriJFrame;
007
008import javax.swing.*;
009import java.awt.*;
010import java.awt.event.ActionEvent;
011import java.beans.PropertyChangeListener;
012import java.beans.PropertyChangeEvent;
013import java.net.InetAddress;
014import java.net.NetworkInterface;
015import java.net.SocketException;
016import java.util.Enumeration;
017
018/**
019 * User interface.
020 * Create a window with a menu bar and some controls:
021 * - IP name, address and port number of the running Z21 Server
022 * - a power on/off button
023 * - an all locos off button
024 * - menu entry for Z21 Server start/stop
025 * - menu entry for opening a mapping table window for turnout numbers
026 * - a table showing connected clients and their last used loco number
027 * 
028 * @author Jean-Yves Roda (C) 2023
029 * @author Eckart Meyer (C) 2025 (enhancements, WlanMaus support)
030 */
031
032public class UserInterface extends JmriJFrame implements PropertyChangeListener {
033
034
035    JMenuBar menuBar;
036    JMenuItem serverOnOff;
037    JPanel panel;
038    JLabel portLabel = new JLabel(Bundle.getMessage("LabelPending"));
039    JLabel manualPortLabel = new JLabel();
040    JLabel numConnected;
041    JScrollPane scrollTable;
042    JTable z21ClientsList;
043    Z21ClientsListModel z21ClientsListModel;
044
045    //keep a reference to the actual server
046    private FacelessServer facelessServer;
047
048    // Server iVars
049    boolean isListen;
050
051    /**
052     * Save the last known size and the last known location since 4.15.4.
053     */
054    UserInterface() {
055        super(true, true);
056
057        isListen = true;
058        facelessServer = FacelessServer.getInstance();
059        
060        ClientManager.getInstance().setClientListener(this);
061        
062        //show all IPv4 addresses in window, for use by manual connections
063        addIPAddressesToUI();
064
065        createWindow();
066
067    } // End of constructor
068
069    private void addIPAddressesToUI() {
070        //get port# directly from prefs
071        //int port = InstanceManager.getDefault(xxxPreferences.class).getPort();
072        int port = MainServer.port;// currently fixed
073        //list IPv4 addresses on the UI, for manual connections
074        StringBuilder as = new StringBuilder(); //build multiline string of valid addresses
075        try {
076            // This code based on ReportContext.addNetworkInfo()
077            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
078            while (networkInterfaces.hasMoreElements()) {
079                NetworkInterface networkInterface = networkInterfaces.nextElement();
080                Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
081                while (inetAddresses.hasMoreElements()) {
082                    InetAddress inetAddress = inetAddresses.nextElement();
083                    String hostAddress = inetAddress.getHostAddress();
084                    if (!hostAddress.equals("0.0.0.0") && !hostAddress.regionMatches(0, "127", 0, 3) && !hostAddress.contains(":")) {
085                        this.portLabel.setText(inetAddress.getHostName());
086                        as.append(hostAddress).append(":").append(port).append("<br/>");
087                    }
088                }
089            }
090            this.manualPortLabel.setText("<html>" + as + "</html>"); // NOI18N
091
092        } catch (SocketException ex) {
093            log.warn("Unable to enumerate Network Interfaces: {}", ex.getMessage());
094        }
095
096    }
097
098
099    protected void createWindow() {
100        panel = new JPanel();
101        panel.setLayout(new GridBagLayout());
102        GridBagConstraints con = new GridBagConstraints();
103        getContentPane().add(panel);
104        con.fill = GridBagConstraints.NONE;
105        con.weightx = 0.5;
106        con.weighty = 0;
107
108        JLabel label = new JLabel(Bundle.getMessage("LabelListening"));
109        con.gridx = 0;
110        con.gridy = 0;
111        con.gridwidth = 2;
112        panel.add(label, con);
113
114        con.gridx = 0;
115        con.gridy = 1;
116        con.gridwidth = 2;
117        panel.add(portLabel, con);
118
119        con.gridy = 2;
120        panel.add(manualPortLabel, con);
121
122        numConnected = new JLabel(Bundle.getMessage("LabelClients") + " " + ClientManager.getInstance().getRegisteredClients().size());
123        con.weightx = 0;
124        con.gridx = 2;
125        con.gridy = 2;
126        con.ipadx = 5;
127        con.gridwidth = 1;
128        panel.add(numConnected, con);
129        
130        JToolBar toolBar = new JToolBar();
131        toolBar.setFloatable(false);
132        toolBar.add(new StopAllButton());
133        toolBar.add(new LargePowerManagerButton());
134        con.weightx = 0.5;
135        con.ipadx = 0;
136        con.gridx = 1;
137        con.gridy = 3;
138        con.gridwidth = 2;
139        panel.add(toolBar, con);
140
141        JLabel icon;
142        java.net.URL imageURL = FileUtil.findURL("resources/z21appIcon.png");
143
144        if (imageURL != null) {
145            ImageIcon image = new ImageIcon(imageURL);
146            image.setImage(image.getImage().getScaledInstance(32, 32, Image.SCALE_DEFAULT)); 
147            icon = new JLabel(image);
148            con.weightx = 0.5;
149            con.gridx = 2;
150            con.gridy = 0;
151            con.ipady = 5;
152            con.gridheight = 2;
153            panel.add(icon, con);
154        }
155
156//  Add a list of connected devices and the address they are set to.
157        z21ClientsListModel = new Z21ClientsListModel();
158        z21ClientsList = new JTable(z21ClientsListModel);
159        z21ClientsList.setPreferredScrollableViewportSize(new Dimension(300, 80));
160
161        z21ClientsList.setRowHeight(20);
162        scrollTable = new JScrollPane(z21ClientsList);
163
164        con.gridx = 0;
165        con.gridy = 4;
166        con.weighty = 1.0;
167        con.ipadx = 10;
168        con.ipady = 10;
169        con.gridheight = 3;
170        con.gridwidth = GridBagConstraints.REMAINDER;
171        con.fill = GridBagConstraints.BOTH;
172        panel.add(scrollTable, con);
173
174        
175        //  Create the menu to use with the window. Has to be before pack() for Windows.
176        buildMenu();
177
178        //  Set window size & location
179        this.setTitle("Z21 App Server");
180        this.pack();
181
182        this.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
183
184        setVisible(true);
185        setMinimumSize(new Dimension(400, 100));
186
187    }
188
189    protected void buildMenu() {
190        this.setJMenuBar(new JMenuBar());
191
192        JMenu menu = new JMenu(Bundle.getMessage("MenuMenu"));
193        serverOnOff = new JMenuItem(Bundle.getMessage("MenuMenuStop"));
194        serverOnOff.addActionListener(new AbstractAction() {
195
196            @Override
197            public void actionPerformed(ActionEvent event) {
198                if (isListen) { // Stop server, remove addresses from UI
199                    disableServer();
200                    serverOnOff.setText(Bundle.getMessage("MenuMenuStart"));
201                    manualPortLabel.setText(null);
202                } else { // Restart server
203                    enableServer();
204                    serverOnOff.setText(Bundle.getMessage("MenuMenuStop"));
205                    String host = "";
206                    try {
207                        host = InetAddress.getLocalHost().getHostAddress();
208                    } catch (Exception e) { host = "unknown ip"; }
209                    manualPortLabel.setText("<html>" + host + "</html>");
210                }
211            }
212        });
213
214        menu.add(serverOnOff);
215
216        menu.add(new NumberMapAction());
217
218//        Action prefsAction = InstanceManager.getDefault(JmriPreferencesActionFactory.class).getCategorizedAction(
219//                Bundle.getMessage("MenuMenuPrefs"),
220//                "Z21 App Server");
221//
222//        menu.add(prefsAction);
223
224        this.getJMenuBar().add(menu);
225
226        // add help menu
227        addHelpMenu("package.jmri.jmrit.z21server.z21server", true);
228    }
229
230    /**
231     * Provide public access to the throttle list model for Jython
232     */
233    public Z21ClientsListModel getThrottleList() {
234        return z21ClientsListModel;
235    }
236    
237    @Override
238    public void propertyChange(PropertyChangeEvent pce) {
239        numConnected.setText(Bundle.getMessage("LabelClients") + " " + ClientManager.getInstance().getRegisteredClients().size());
240        if (isListen) {
241            addIPAddressesToUI();
242        }
243        else {
244            portLabel.setText(Bundle.getMessage("LabelNone"));
245            manualPortLabel.setText("");
246        }
247        z21ClientsListModel.updateClientList();
248    }
249
250    void disableServer() {
251        facelessServer.stop();
252        isListen = false;
253        propertyChange(null);
254        ClientManager.getInstance().handleExpiredClients(true);
255    }
256
257    private void enableServer() {
258        facelessServer.start();
259        isListen = true;
260        propertyChange(null);
261    }
262    
263    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserInterface.class);
264
265}