001package apps.gui3;
002
003import jmri.util.gui.GuiLafPreferencesManager;
004
005import java.awt.BorderLayout;
006import java.awt.Component;
007import java.awt.Cursor;
008import java.awt.Dimension;
009import java.awt.FlowLayout;
010import java.awt.Image;
011import java.awt.Toolkit;
012import java.awt.event.ActionEvent;
013import java.util.ArrayList;
014import java.util.HashMap;
015import java.util.Locale;
016
017import javax.swing.BoxLayout;
018import javax.swing.ImageIcon;
019import javax.swing.JButton;
020import javax.swing.JComboBox;
021import javax.swing.JComponent;
022import javax.swing.JLabel;
023import javax.swing.JPanel;
024import javax.swing.JTextArea;
025import javax.swing.JTextField;
026import javax.swing.border.BevelBorder;
027
028import jmri.Application;
029import jmri.ConfigureManager;
030import jmri.InstanceManager;
031import jmri.jmrit.logix.WarrantPreferences;
032import jmri.jmrit.roster.RosterConfigManager;
033import jmri.jmrix.AbstractConnectionConfig;
034import jmri.jmrix.ConnectionConfig;
035import jmri.jmrix.JmrixConfigPane;
036import jmri.jmrix.PortAdapter;
037import jmri.profile.Profile;
038import jmri.profile.ProfileManager;
039import jmri.util.FileUtil;
040import jmri.util.prefs.InitializationException;
041import jmri.util.swing.JmriJOptionPane;
042
043public class FirstTimeStartUpWizard implements Thread.UncaughtExceptionHandler {
044
045    Image splashIm;
046
047    jmri.util.JmriJFrame parent;
048    private final JmrixConfigPane connectionConfigPane = JmrixConfigPane.createNewPanel();
049
050    public FirstTimeStartUpWizard(jmri.util.JmriJFrame parent, apps.gui3.Apps3 app) {
051        this.parent = parent;
052        this.app = app;
053        mainWizardPanel.setLayout(new BorderLayout());
054        
055        localeNames = new String[Locale.getAvailableLocales().length];
056        
057        mainWizardPanel.add(createTopBanner(), BorderLayout.NORTH);
058
059        mainWizardPanel.add(createHelpPanel(), BorderLayout.WEST);
060
061        mainWizardPanel.add(createEntryPanel(), BorderLayout.CENTER);
062
063        mainWizardPanel.add(createButtonPanel(), BorderLayout.SOUTH);
064    }
065
066    JLabel header = new JLabel();
067
068    JPanel createTopBanner() {
069        JPanel top = new JPanel();
070
071        header.setText("Welcome to JMRI StartUp Wizard");
072        top.add(header);
073
074        return top;
075    }
076
077    JPanel createHelpPanel() {
078        splashIm = Toolkit.getDefaultToolkit().getImage(FileUtil.findURL("resources/logo.gif", FileUtil.Location.INSTALLED));
079        ImageIcon img = new ImageIcon(splashIm, "JMRI splash screen");
080        int imageWidth = img.getIconWidth();
081        minHelpFieldDim = new Dimension(imageWidth, 20);
082        maxHelpFieldDim = new Dimension((imageWidth + 20), 350);
083        helpPanel.setPreferredSize(maxHelpFieldDim);
084        helpPanel.setMaximumSize(maxHelpFieldDim);
085        helpPanel.setLayout(
086                new BoxLayout(helpPanel, BoxLayout.Y_AXIS));
087
088        JLabel l = new JLabel(img);
089        l.setAlignmentX(Component.CENTER_ALIGNMENT);
090        l.setOpaque(false);
091        helpPanel.add(l);
092        return helpPanel;
093    }
094
095    ArrayList<WizardPage> wizPage = new ArrayList<>();
096
097    void createScreens() {
098        firstWelcome();
099        setDefaultOwner();
100        setConnection();
101        finishAndConnect();
102    }
103
104    public void dispose() {
105        Cursor normalCursor = new Cursor(Cursor.DEFAULT_CURSOR);
106        parent.setCursor(normalCursor);
107        app.createAndDisplayFrame();
108        parent.setVisible(false);
109        parent.dispose();
110    }
111
112    apps.gui3.Apps3 app;
113
114    JPanel entryPanel = new JPanel();
115    JPanel helpPanel = new JPanel();
116
117    JComponent createEntryPanel() {
118        createScreens();
119        for (int i = 0; i < wizPage.size(); i++) {
120            entryPanel.add(wizPage.get(i).getPanel());
121            helpPanel.add(wizPage.get(i).getHelpDetails());
122        }
123        wizPage.get(0).getPanel().setVisible(true);
124        wizPage.get(0).getHelpDetails().setVisible(true);
125        header.setFont(header.getFont().deriveFont(14f));
126        return entryPanel;
127    }
128
129    void setDefaultOwner() {
130        JPanel p = new JPanel();
131
132        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
133        p.add(formatText("Select your language<br>"));
134        initalLocale = Locale.getDefault();
135        p.add(doLocale());
136
137        p.add(formatText("<br>Enter in the default owner for all your loco roster entries<p>If you are part of group or club, where different people will be accessing DecoderPro, then you can leave this blank</p>"));
138        JPanel p2 = new JPanel();
139        p2.setLayout(new FlowLayout());
140        p2.add(new JLabel(/*rb.getString("LabelDefaultOwner")*/"Default Owner"));
141
142        owner.setText(InstanceManager.getDefault(RosterConfigManager.class).getDefaultOwner());
143        if (owner.getText().isEmpty()) {
144            owner.setText(System.getProperty("user.name"));
145        }
146        p2.add(owner);
147        p.add(p2);
148
149        wizPage.add(new WizardPage(p, new JPanel(), "Set the Default Language and Owner"));
150    }
151
152    void setConnection() {
153
154        JPanel h = new JPanel();
155        h.setLayout(new BoxLayout(h, BoxLayout.Y_AXIS));
156        h.setMaximumSize(maxHelpFieldDim);
157
158        JTextArea text = new JTextArea("First select the manufacturer of your DCC system\n\nFollowed by the type of connection being used.\n\nFinally select the serial port or enter in the IP address of the device");
159        text.setEditable(false);
160        text.setLineWrap(true);
161        text.setWrapStyleWord(true);
162        text.setOpaque(false);
163        text.setMinimumSize(minHelpFieldDim);
164        text.setMaximumSize(maxHelpFieldDim);
165        h.add(text);
166
167        wizPage.add(new WizardPage(this.connectionConfigPane, h, "Select your DCC Connection"));
168    }
169
170    void firstWelcome() {
171        JPanel p = new JPanel();
172        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
173        p.add(formatText("Welcome to JMRI's " + Application.getApplicationName() + "<p><br>This little wizard will help to guide you through setting up " + Application.getApplicationName() + " for the first time"));
174
175        wizPage.add(new WizardPage(p, new JPanel(), "Welcome to JMRI StartUp Wizard"));
176    }
177
178    Dimension minHelpFieldDim = new Dimension(160, 20);
179    Dimension maxHelpFieldDim = new Dimension(160, 300);
180
181    void finishAndConnect() {
182        JPanel p = new JPanel();
183        p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
184        p.add(formatText("Configuration is now all complete, press finish below to connect to your system and start using " + Application.getApplicationName() + "\n\nIf at any time you need to change your settings, you can find the preference setting under the Edit Menu"));
185        wizPage.add(new WizardPage(p, new JPanel(), "Finish and Connect"));
186    }
187
188    JTextField owner = new JTextField(20);
189
190    int currentScreen = 0;
191
192    JPanel createButtonPanel() {
193        JPanel buttonPanel = new JPanel();
194        buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
195        buttonPanel.setBorder(new BevelBorder(BevelBorder.LOWERED));
196        final JButton previous = new JButton("< Back");
197        final JButton next = new JButton("Next >");
198        final JButton finish = new JButton("Finish");
199        finish.setVisible(false);
200        JButton cancel = new JButton("Cancel");
201        cancel.addActionListener((java.awt.event.ActionEvent e) -> {
202            Locale.setDefault(initalLocale);
203            dispose();
204        });
205
206        previous.addActionListener((java.awt.event.ActionEvent e) -> {
207            if (currentScreen < wizPage.size()) {
208                wizPage.get(currentScreen).getPanel().setVisible(false);
209                wizPage.get(currentScreen).getHelpDetails().setVisible(false);
210            }
211            finish.setVisible(false);
212
213            currentScreen = currentScreen - 1;
214            if (currentScreen != -1) {
215                wizPage.get(currentScreen).getPanel().setVisible(true);
216                wizPage.get(currentScreen).getHelpDetails().setVisible(true);
217                header.setText(wizPage.get(currentScreen).getHeaderText());
218                header.setFont(header.getFont().deriveFont(14f));
219
220                if (currentScreen == 0) {
221                    previous.setEnabled(false);
222                }
223                next.setEnabled(true);
224                next.setVisible(true);
225            } else {
226                currentScreen = 0;
227                previous.setEnabled(false);
228            }
229        });
230        next.addActionListener((java.awt.event.ActionEvent e) -> {
231            wizPage.get(currentScreen).getPanel().setVisible(false);
232            wizPage.get(currentScreen).getHelpDetails().setVisible(false);
233            currentScreen++;
234            if (currentScreen < wizPage.size()) {
235                wizPage.get(currentScreen).getPanel().setVisible(true);
236                wizPage.get(currentScreen).getHelpDetails().setVisible(true);
237                header.setText(wizPage.get(currentScreen).getHeaderText());
238                header.setFont(header.getFont().deriveFont(14f));
239                previous.setEnabled(true);
240                if (currentScreen == (wizPage.size() - 1)) {
241                    next.setEnabled(false);
242                    next.setVisible(false);
243                    finish.setVisible(true);
244                }
245            }
246        });
247
248        finish.addActionListener((java.awt.event.ActionEvent e) -> {
249            Runnable r = new Connect();
250            Thread connectThread = new Thread(r);
251            connectThread.start();
252            connectThread.setName("Start-Up Wizard Connect");
253            connectThread.setUncaughtExceptionHandler(this);
254        });
255
256        buttonPanel.add(previous);
257        buttonPanel.add(next);
258        buttonPanel.add(new JLabel("     ")); // filler
259        buttonPanel.add(finish);
260        buttonPanel.add(cancel);
261        previous.setEnabled(false);
262
263        return buttonPanel;
264    }
265
266    @Override
267    public void uncaughtException(Thread t, Throwable e) {
268        showDialogue(e);
269    }
270
271    private void showDialogue(Throwable ex) {
272        log.error("Exception: ", ex);
273        Cursor normalCursor = new Cursor(Cursor.DEFAULT_CURSOR);
274        parent.setCursor(normalCursor);
275        var conn = connectionConfigPane.getCurrentObject();
276        String connName = ( conn == null ? "No Connection " : conn.getConnectionName()  );
277        jmri.util.ThreadingUtil.runOnGUI(() -> {
278            JmriJOptionPane.showMessageDialog(parent,
279                "<html>An error occurred while trying to connect to " + connName
280                    + ", <br>press the back button and check the connection details.<br>"
281                    + ex.getLocalizedMessage() + "</html>",
282                "Error Opening Connection",
283                JmriJOptionPane.ERROR_MESSAGE);
284        });
285    }
286
287    //The connection process is placed into its own thread so that it doens't hog the swingthread while waiting for the connections to open.
288    protected class Connect implements Runnable {
289
290        @Override
291        public void run() {
292            Cursor hourglassCursor = new Cursor(Cursor.WAIT_CURSOR);
293            parent.setCursor(hourglassCursor);
294            ConnectionConfig connect = connectionConfigPane.getCurrentObject();
295            ConfigureManager cm = InstanceManager.getNullableDefault(jmri.ConfigureManager.class);
296            if (cm != null) {
297                cm.registerPref(connect);
298            }
299            if (connect instanceof jmri.jmrix.AbstractConnectionConfig) {
300                ((AbstractConnectionConfig) connect).updateAdapter();
301                PortAdapter adp = connect.getAdapter();
302                try {
303                    adp.connect();
304                    adp.configure();
305                } catch (Exception ex) {
306                    showDialogue(ex);
307                    return;
308                }
309            }
310            Profile project = ProfileManager.getDefault().getActiveProfile();
311            InstanceManager.getDefault(RosterConfigManager.class).setDefaultOwner(project, owner.getText());
312            InstanceManager.getDefault(GuiLafPreferencesManager.class).setLocale(Locale.getDefault());
313            InstanceManager.getDefault(GuiLafPreferencesManager.class).setDefaultFontSize();
314            InstanceManager.getDefault(GuiLafPreferencesManager.class).setFontSize(
315                InstanceManager.getDefault(GuiLafPreferencesManager.class).getDefaultFontSize());
316            InstanceManager.getDefault(RosterConfigManager.class).savePreferences(project);
317            InstanceManager.getDefault(GuiLafPreferencesManager.class).savePreferences(project);
318            connectionConfigPane.savePreferences();
319            try {
320                InstanceManager.getDefault(WarrantPreferences.class).initialize(project);
321            } catch ( InitializationException ex ){
322                log.error("Exception Initialising warrant preferences: ", ex);
323            }
324            InstanceManager.getDefault(ConfigureManager.class).storePrefs();
325            
326            dispose();
327        }
328    }
329
330    public JPanel doLocale() {
331        JPanel panel = new JPanel();
332        // add JComboBoxen for language and country
333        panel.setLayout(new FlowLayout());
334        localeBox = new JComboBox<>(new String[]{
335            Locale.getDefault().getDisplayName(),
336            "(Please Wait)"});
337        panel.add(localeBox);
338
339        // create object to find locales in new Thread
340        Runnable r = () -> {
341            Locale[] locales = Locale.getAvailableLocales();
342            locale = new HashMap<>();
343            for (int i = 0; i < locales.length; i++) {
344                locale.put(locales[i].getDisplayName(), locales[i]);
345                localeNames[i] = locales[i].getDisplayName();
346            }
347            java.util.Arrays.sort(localeNames);
348            Runnable update = () -> {
349                localeBox.setModel(new javax.swing.DefaultComboBoxModel<>(localeNames));
350                localeBox.setSelectedItem(Locale.getDefault().getDisplayName());
351            };
352            javax.swing.SwingUtilities.invokeLater(update);
353        };
354        new Thread(r).start();
355
356        localeBox.addActionListener((ActionEvent a) -> {
357            if (localeBox == null || locale == null) {
358                return;
359            }
360            String desired = (String) localeBox.getSelectedItem();
361            Locale.setDefault(locale.get(desired));
362        });
363
364        return panel;
365
366    }
367
368    Locale initalLocale;
369
370    JLabel formatText(String text) {
371        JLabel label = new JLabel();
372        label.setText("<html><body width='450'>" + text + "</html>");
373        return label;
374    }
375
376    JComboBox<String> localeBox;
377    HashMap<String, Locale> locale;
378    final String[] localeNames;
379
380    JPanel mainWizardPanel = new JPanel();
381
382    public JPanel getPanel() {
383        return mainWizardPanel;
384    }
385
386    static class WizardPage {
387
388        static Dimension defaultInfoSize = new Dimension(500, 300);
389        JComponent panel;
390        JPanel helpDetails = new JPanel();
391        String headerText = "";
392
393        WizardPage(JComponent mainPanel, JPanel helpDetails, String headerText) {
394            this.panel = mainPanel;
395
396            if (helpDetails != null) {
397                this.helpDetails = helpDetails;
398                this.helpDetails.setLayout(
399                        new BoxLayout(this.helpDetails, BoxLayout.Y_AXIS));
400            }
401            if (this.panel != null) {
402                this.panel.setPreferredSize(defaultInfoSize);
403                this.panel.setVisible(false);
404            }
405            this.helpDetails.setVisible(false);
406            this.headerText = headerText;
407        }
408
409        JComponent getPanel() {
410            return panel;
411        }
412
413        JPanel getHelpDetails() {
414            return helpDetails;
415        }
416
417        String getHeaderText() {
418            return headerText;
419        }
420
421    }
422
423    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FirstTimeStartUpWizard.class);
424
425}