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}