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