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}