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}