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