001package jmri.jmrix.swing; 002 003import java.awt.BorderLayout; 004import java.awt.Color; 005import java.awt.Component; 006import java.awt.Dimension; 007import java.awt.FlowLayout; 008import java.awt.event.ActionEvent; 009import java.beans.IndexedPropertyChangeEvent; 010import java.beans.PropertyChangeEvent; 011import java.text.MessageFormat; 012import java.util.ArrayList; 013import java.util.List; 014import java.util.ResourceBundle; 015 016import javax.swing.Icon; 017import javax.swing.ImageIcon; 018import javax.swing.JButton; 019import javax.swing.JCheckBox; 020import javax.swing.JComponent; 021import javax.swing.JLabel; 022import javax.swing.JPanel; 023import javax.swing.JTabbedPane; 024import javax.swing.event.ChangeEvent; 025import javax.swing.event.ChangeListener; 026 027import jmri.InstanceManager; 028import jmri.jmrix.ConnectionConfig; 029import jmri.jmrix.ConnectionConfigManager; 030import jmri.jmrix.ConnectionStatus; 031import jmri.jmrix.JmrixConfigPane; 032import jmri.profile.ProfileManager; 033import jmri.swing.ManagingPreferencesPanel; 034import jmri.swing.PreferencesPanel; 035import jmri.util.FileUtil; 036import jmri.util.swing.JmriJOptionPane; 037 038import org.openide.util.lookup.ServiceProvider; 039 040/** 041 * 042 * @author Randall Wood randall.h.wood@alexandriasoftware.com 043 */ 044@ServiceProvider(service = PreferencesPanel.class) 045public class ConnectionsPreferencesPanel extends JTabbedPane implements ManagingPreferencesPanel { 046 047 private static final ResourceBundle rb = ResourceBundle.getBundle("apps.AppsConfigBundle"); // for some items // NOI18N 048 049 050 private final ImageIcon deleteIcon; 051 private final ImageIcon deleteIconRollOver; 052 private final Dimension deleteButtonSize; 053 private ImageIcon addIcon; 054 private boolean restartRequired = false; 055 056 private ArrayList<JmrixConfigPane> configPanes = new ArrayList<>(); 057 058 public ConnectionsPreferencesPanel() { 059 super(); 060 deleteIconRollOver = new ImageIcon( 061 FileUtil.findURL("program:resources/icons/misc/gui3/Delete16x16.png")); 062 deleteIcon = new ImageIcon( 063 FileUtil.findURL("program:resources/icons/misc/gui3/Delete-bw16x16.png")); 064 deleteButtonSize = new Dimension( 065 deleteIcon.getIconWidth() + 2, 066 deleteIcon.getIconHeight() + 2); 067 addIcon = new ImageIcon( 068 FileUtil.findURL("program:resources/icons/misc/gui3/Add16x16.png")); 069 ConnectionConfigManager ccm = InstanceManager.getDefault(ConnectionConfigManager.class); 070 ConnectionConfig[] connections = ccm.getConnections(); 071 if (connections.length != 0) { 072 for (int i = 0; i < connections.length; i++) { 073 addConnection(i, JmrixConfigPane.createPanel(i)); 074 } 075 } else { 076 addConnection(0, JmrixConfigPane.createNewPanel()); 077 } 078 ccm.addPropertyChangeListener(ConnectionConfigManager.CONNECTIONS, (PropertyChangeEvent evt) -> { 079 int i = ((IndexedPropertyChangeEvent) evt).getIndex(); 080 log.debug("PrefPanel ChangeListener of tab index i = {} of {} list", i+1, configPanes.size()); 081 //if (evt.getNewValue() == null 082 // && i < configPanes.size() 083 // && evt.getOldValue().equals(configPanes.get(i).getCurrentObject())) { 084 // //removeTab(null, i); // called same method removeTab again, conn tab was already removed by the user click 085 //} else 086 if ((evt.getOldValue() == null) && (evt.getNewValue() != null)) { 087 for (JmrixConfigPane pane : this.configPanes) { 088 if (pane.getCurrentObject() == null ) { 089 log.error("did not expect pane.getCurrentObject()==null here for {} {} {}", i, evt.getNewValue(), configPanes); 090 } else if (pane.getCurrentObject().equals(evt.getNewValue())) { 091 return; // don't add the connection again 092 } 093 } 094 addConnection(i, JmrixConfigPane.createPanel(i)); 095 } 096 }); 097 this.addChangeListener(addTabListener); 098 newConnectionTab(); 099 this.setSelectedIndex(0); 100 } 101 102 transient ChangeListener addTabListener = (ChangeEvent evt) -> { 103 // This method is called whenever the selected tab changes 104 JTabbedPane pane = (JTabbedPane) evt.getSource(); 105 int sel = pane.getSelectedIndex(); 106 if (sel == -1) { 107 addConnectionTab(); 108 return; 109 } else { 110 Icon icon = pane.getIconAt(sel); 111 if (icon == addIcon) { 112 addConnectionTab(); 113 return; 114 } 115 } 116 activeTab(); 117 }; 118 119 private void activeTab() { 120 for (int i = 0; i < this.getTabCount() - 1; i++) { 121 JPanel panel = (JPanel) this.getTabComponentAt(i); 122 if (panel != null) { 123 panel.invalidate(); 124 Component[] comp = panel.getComponents(); 125 for (Component c : comp) { 126 if (c instanceof JButton) { 127 if (i == this.getSelectedIndex()) { 128 c.setVisible(true); 129 } else { 130 c.setVisible(false); 131 } 132 } 133 } 134 } 135 } 136 } 137 138 private void addConnection(int tabPosition, final JmrixConfigPane configPane) { 139 JPanel p = new JPanel(); 140 p.setLayout(new BorderLayout()); 141 p.add(configPane, BorderLayout.CENTER); 142 143 JButton tabCloseButton = new JButton(deleteIcon); 144 tabCloseButton.setPreferredSize(deleteButtonSize); 145 tabCloseButton.setBorderPainted(false); 146 tabCloseButton.setRolloverIcon(deleteIconRollOver); 147 tabCloseButton.setVisible(false); 148 149 JPanel c = new JPanel(); 150 c.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); 151 final JCheckBox disable = new JCheckBox(rb.getString("ButtonDisableConnection")); 152 disable.setSelected(configPane.getDisabled()); 153 disable.addActionListener((ActionEvent e) -> { 154 configPane.setDisabled(disable.isSelected()); 155 }); 156 c.add(disable); 157 p.add(c, BorderLayout.SOUTH); 158 String title; 159 160 if (configPane.getConnectionName() != null) { 161 title = configPane.getConnectionName(); 162 } else if ((configPane.getCurrentProtocolName() != null) 163 && (!configPane.getCurrentProtocolName().equals( 164 JmrixConfigPane.NONE))) { 165 title = configPane.getCurrentProtocolName(); 166 } else { 167 title = rb.getString("TabbedLayoutConnection") + (tabPosition + 1); 168 if (this.indexOfTab(title) != -1) { 169 for (int x = 2; x < 12; x++) { 170 title = rb.getString("TabbedLayoutConnection") 171 + (tabPosition + 2); 172 if (this.indexOfTab(title) != -1) { 173 break; 174 } 175 } 176 } 177 } 178 179 final JPanel tabTitle = new JPanel(new BorderLayout(5, 0)); 180 tabTitle.setOpaque(false); 181 p.setName(title); 182 183 if (configPane.getDisabled()) { 184 title = "(" + title + ")"; 185 } 186 187 JLabel tabLabel = new JLabel(title, JLabel.LEFT); 188 tabTitle.add(tabLabel, BorderLayout.WEST); 189 tabTitle.add(tabCloseButton, BorderLayout.EAST); 190 191 this.configPanes.add(configPane); 192 this.add(p); 193 this.setTabComponentAt(tabPosition, tabTitle); 194 195 tabCloseButton.addActionListener((ActionEvent e) -> { // initial user Delete conn click 196 removeTab(e, this.indexOfTabComponent(tabTitle)); 197 }); 198 199 this.setToolTipTextAt(tabPosition, title); 200 201 if (configPane.getCurrentObject() != null) { 202 if (ConnectionStatus.instance().isConnectionOk( 203 configPane.getCurrentObject().getAdapter().getSystemConnectionMemo())) { 204 tabLabel.setForeground(Color.black); 205 } else { 206 tabLabel.setForeground(Color.red); 207 } 208 } 209 if (configPane.getDisabled()) { 210 tabLabel.setForeground(Color.ORANGE); 211 } 212 213 } 214 215 void addConnectionTab() { 216 this.removeTabAt(this.indexOfTab(addIcon)); 217 addConnection(configPanes.size(), JmrixConfigPane.createNewPanel()); 218 newConnectionTab(); 219 } 220 221 private void newConnectionTab() { 222 this.addTab(null, addIcon, null, rb.getString("ToolTipAddNewConnection")); 223 this.setSelectedIndex(this.getTabCount() - 2); 224 } 225 226 private void removeTab(ActionEvent e, int x) { 227 int i; 228 i = x; // copy x for handling 229 230 if (i != -1) { 231 // only prompt if triggered by action event 232 if (e != null) { 233 int n = JmriJOptionPane.showConfirmDialog(null, MessageFormat.format( 234 rb.getString("MessageDoDelete"), 235 new Object[]{this.getTitleAt(i)}), 236 rb.getString("MessageDeleteConnection"), 237 JmriJOptionPane.YES_NO_OPTION); 238 if (n != JmriJOptionPane.YES_OPTION) { 239 return; 240 } 241 } 242 243 log.debug("i = {}, this.getTabCount() = {}, configPanes.size()={}", i, this.getTabCount(), configPanes.size()); 244 JmrixConfigPane configPane = this.configPanes.get(i); 245 this.removeChangeListener(addTabListener); 246 this.remove(i); 247 log.debug("start of connection #{} disposal", i); 248 try { 249 JmrixConfigPane.dispose(configPane); 250 } catch (NullPointerException ex) { 251 log.error("Caught Null Pointer Exception while removing connection tab {}", i); 252 } 253 log.debug("connection tab #{} successfully disposed. configPanes.size() = {}", i+1, configPanes.size()); 254 this.configPanes.remove(i); // on delete of connection a loop (listener) called this method 2x 255 restartRequired = true; 256 if (this.getTabCount() == 1) { // normally not required, stays in place while adding or deleting 257 addConnectionTab(); 258 } 259 if (x != 0) { 260 this.setSelectedIndex(x - 1); 261 } else { 262 this.setSelectedIndex(0); 263 } 264 this.addChangeListener(addTabListener); 265 } 266 activeTab(); 267 } 268 269 @Override 270 public String getPreferencesItem() { 271 return "CONNECTIONS"; // NOI18N 272 } 273 274 @Override 275 public String getPreferencesItemText() { 276 return rb.getString("MenuConnections"); // NOI18N 277 } 278 279 @Override 280 public String getTabbedPreferencesTitle() { 281 return null; 282 } 283 284 @Override 285 public String getLabelKey() { 286 return null; 287 } 288 289 @Override 290 public JComponent getPreferencesComponent() { 291 return this; 292 } 293 294 @Override 295 public boolean isPersistant() { 296 return false; 297 } 298 299 @Override 300 public String getPreferencesTooltip() { 301 return null; 302 } 303 304 @Override 305 public void savePreferences() { 306 InstanceManager.getDefault(ConnectionConfigManager.class).savePreferences(ProfileManager.getDefault().getActiveProfile()); 307 } 308 309 @Override 310 public boolean isDirty() { 311 return this.configPanes.stream().anyMatch(JmrixConfigPane::isDirty); 312 } 313 314 @Override 315 public boolean isRestartRequired() { 316 return this.restartRequired 317 || this.configPanes.stream().anyMatch(JmrixConfigPane::isRestartRequired); 318 } 319 320 @Override 321 public boolean isPreferencesValid() { 322 return this.configPanes.stream().allMatch(JmrixConfigPane::isPreferencesValid); 323 } 324 325 @Override 326 public List<PreferencesPanel> getPreferencesPanels() { 327 return new ArrayList<>(this.configPanes); 328 } 329 330 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConnectionsPreferencesPanel.class); 331 332}