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}