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 (ConnectionStatus.instance().isConnectionOk(null, 202 configPane.getCurrentProtocolInfo())) { 203 tabLabel.setForeground(Color.black); 204 } else { 205 tabLabel.setForeground(Color.red); 206 } 207 if (configPane.getDisabled()) { 208 tabLabel.setForeground(Color.ORANGE); 209 } 210 211 } 212 213 void addConnectionTab() { 214 this.removeTabAt(this.indexOfTab(addIcon)); 215 addConnection(configPanes.size(), JmrixConfigPane.createNewPanel()); 216 newConnectionTab(); 217 } 218 219 private void newConnectionTab() { 220 this.addTab(null, addIcon, null, rb.getString("ToolTipAddNewConnection")); 221 this.setSelectedIndex(this.getTabCount() - 2); 222 } 223 224 private void removeTab(ActionEvent e, int x) { 225 int i; 226 i = x; // copy x for handling 227 228 if (i != -1) { 229 // only prompt if triggered by action event 230 if (e != null) { 231 int n = JmriJOptionPane.showConfirmDialog(null, MessageFormat.format( 232 rb.getString("MessageDoDelete"), 233 new Object[]{this.getTitleAt(i)}), 234 rb.getString("MessageDeleteConnection"), 235 JmriJOptionPane.YES_NO_OPTION); 236 if (n != JmriJOptionPane.YES_OPTION) { 237 return; 238 } 239 } 240 241 log.debug("i = {}, this.getTabCount() = {}, configPanes.size()={}", i, this.getTabCount(), configPanes.size()); 242 JmrixConfigPane configPane = this.configPanes.get(i); 243 this.removeChangeListener(addTabListener); 244 this.remove(i); 245 log.debug("start of connection #{} disposal", i); 246 try { 247 JmrixConfigPane.dispose(configPane); 248 } catch (NullPointerException ex) { 249 log.error("Caught Null Pointer Exception while removing connection tab {}", i); 250 } 251 log.debug("connection tab #{} successfully disposed. configPanes.size() = {}", i+1, configPanes.size()); 252 this.configPanes.remove(i); // on delete of connection a loop (listener) called this method 2x 253 restartRequired = true; 254 if (this.getTabCount() == 1) { // normally not required, stays in place while adding or deleting 255 addConnectionTab(); 256 } 257 if (x != 0) { 258 this.setSelectedIndex(x - 1); 259 } else { 260 this.setSelectedIndex(0); 261 } 262 this.addChangeListener(addTabListener); 263 } 264 activeTab(); 265 } 266 267 @Override 268 public String getPreferencesItem() { 269 return "CONNECTIONS"; // NOI18N 270 } 271 272 @Override 273 public String getPreferencesItemText() { 274 return rb.getString("MenuConnections"); // NOI18N 275 } 276 277 @Override 278 public String getTabbedPreferencesTitle() { 279 return null; 280 } 281 282 @Override 283 public String getLabelKey() { 284 return null; 285 } 286 287 @Override 288 public JComponent getPreferencesComponent() { 289 return this; 290 } 291 292 @Override 293 public boolean isPersistant() { 294 return false; 295 } 296 297 @Override 298 public String getPreferencesTooltip() { 299 return null; 300 } 301 302 @Override 303 public void savePreferences() { 304 InstanceManager.getDefault(ConnectionConfigManager.class).savePreferences(ProfileManager.getDefault().getActiveProfile()); 305 } 306 307 @Override 308 public boolean isDirty() { 309 return this.configPanes.stream().anyMatch(JmrixConfigPane::isDirty); 310 } 311 312 @Override 313 public boolean isRestartRequired() { 314 return this.restartRequired 315 || this.configPanes.stream().anyMatch(JmrixConfigPane::isRestartRequired); 316 } 317 318 @Override 319 public boolean isPreferencesValid() { 320 return this.configPanes.stream().allMatch(JmrixConfigPane::isPreferencesValid); 321 } 322 323 @Override 324 public List<PreferencesPanel> getPreferencesPanels() { 325 return new ArrayList<>(this.configPanes); 326 } 327 328 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConnectionsPreferencesPanel.class); 329 330}