001package apps;
002
003import static jmri.util.gui.GuiLafPreferencesManager.MIN_FONT_SIZE;
004
005import java.awt.FlowLayout;
006import java.awt.event.ActionEvent;
007import java.awt.event.ItemEvent;
008import java.text.MessageFormat;
009import java.util.Arrays;
010import java.util.HashMap;
011import java.util.Locale;
012
013import javax.swing.BoxLayout;
014import javax.swing.ButtonGroup;
015import javax.swing.DefaultComboBoxModel;
016import javax.swing.JButton;
017import javax.swing.JCheckBox;
018import javax.swing.JComboBox;
019import javax.swing.JComponent;
020import javax.swing.JLabel;
021import javax.swing.JPanel;
022import javax.swing.JRadioButton;
023import javax.swing.JSpinner;
024import javax.swing.SpinnerNumberModel;
025import javax.swing.SwingUtilities;
026import javax.swing.UIManager;
027import javax.swing.event.ChangeEvent;
028
029import jmri.InstanceManager;
030import jmri.profile.Profile;
031import jmri.profile.ProfileManager;
032import jmri.swing.PreferencesPanel;
033import jmri.util.gui.GuiLafPreferencesManager;
034import jmri.util.swing.JComboBoxUtil;
035
036import org.openide.util.lookup.ServiceProvider;
037
038/**
039 * Provide GUI to configure Swing GUI LAF defaults
040 * <p>
041 * Provides GUI configuration for SWING LAF by displaying radio buttons for each
042 * LAF implementation available. This information is then persisted separately
043 * by the {@link jmri.util.gui.GuiLafPreferencesManager}.
044 * <p>
045 * Locale default language and country is also considered a GUI (and perhaps
046 * LAF) configuration item.
047 *
048 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2010
049 * @since 2.9.5 (Previously in jmri package)
050 */
051@ServiceProvider(service = PreferencesPanel.class)
052public final class GuiLafConfigPane extends JPanel implements PreferencesPanel {
053
054    public static final int MAX_TOOLTIP_TIME = 3600;
055    public static final int MIN_TOOLTIP_TIME = 1;
056
057    /**
058     * Smallest font size shown to a user ({@value}).
059     *
060     * @see GuiLafPreferencesManager#MIN_FONT_SIZE
061     */
062    public static final int MIN_DISPLAYED_FONT_SIZE = MIN_FONT_SIZE;
063    /**
064     * Largest font size shown to a user ({@value}).
065     *
066     * @see GuiLafPreferencesManager#MAX_FONT_SIZE
067     */
068    public static final int MAX_DISPLAYED_FONT_SIZE = 20;
069
070    private final JComboBox<String> localeBox = new JComboBox<>(new String[]{
071        Locale.getDefault().getDisplayName(),
072        "(Please Wait)"});
073    private final HashMap<String, Locale> locale = new HashMap<>();
074    private final ButtonGroup LAFGroup = new ButtonGroup();
075    public JCheckBox mouseEvent;
076    private JComboBox<Integer> fontSizeComboBox;
077    public JCheckBox graphicStateDisplay;
078    public JCheckBox tabbedOblockEditor;
079    public JCheckBox editorUseOldLocSizeDisplay;
080    public ButtonGroup fileChooserGroup= new ButtonGroup();
081    public JCheckBox force100percentScaling;
082
083    public GuiLafConfigPane() {
084        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
085        JPanel p;
086        doLAF(p = new JPanel());
087        add(p);
088        doFontSize(p = new JPanel());
089        add(p);
090        doFileChooserLayoutType(p = new JPanel());
091        add(p);
092        doClickSelection(p = new JPanel());
093        add(p);
094        doGraphicState(p = new JPanel());
095        add(p);
096        doTabbedOblockEditor(p = new JPanel());
097        add(p);
098        doEditorUseOldLocSize(p = new JPanel());
099        add(p);
100        doForce100percentScaling(p = new JPanel());
101        add(p);
102        doMaxComboRows(p = new JPanel());
103        add(p);
104        doToolTipDismissDelay(p = new JPanel());
105        add(p);
106    }
107
108    void doClickSelection(JPanel panel) {
109        panel.setLayout(new FlowLayout());
110        mouseEvent = new JCheckBox(ConfigBundle.getMessage("GUIButtonNonStandardRelease"));
111        mouseEvent.addItemListener((ItemEvent e) -> {
112            InstanceManager.getDefault(GuiLafPreferencesManager.class).setNonStandardMouseEvent(mouseEvent.isSelected());
113        });
114        panel.add(mouseEvent);
115    }
116
117    void doGraphicState(JPanel panel) {
118        panel.setLayout(new FlowLayout());
119        graphicStateDisplay = new JCheckBox(ConfigBundle.getMessage("GUIGraphicTableState"));
120        graphicStateDisplay.setSelected(InstanceManager.getDefault(GuiLafPreferencesManager.class).isGraphicTableState());
121        graphicStateDisplay.addItemListener((ItemEvent e) -> {
122            InstanceManager.getDefault(GuiLafPreferencesManager.class).setGraphicTableState(graphicStateDisplay.isSelected());
123        });
124        panel.add(graphicStateDisplay);
125    }
126
127    void doTabbedOblockEditor(JPanel panel) {
128        panel.setLayout(new FlowLayout());
129        tabbedOblockEditor = new JCheckBox(ConfigBundle.getMessage("GUITabbedOblockEditor"));
130        tabbedOblockEditor.setSelected(InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed());
131        tabbedOblockEditor.setToolTipText(ConfigBundle.getMessage("GUIToolTipTabbedEdit"));
132        tabbedOblockEditor.addItemListener((ItemEvent e) -> {
133            InstanceManager.getDefault(GuiLafPreferencesManager.class).setOblockEditTabbed(tabbedOblockEditor.isSelected());
134        });
135        panel.add(tabbedOblockEditor);
136    }
137
138    void doEditorUseOldLocSize(JPanel panel) {
139        panel.setLayout(new FlowLayout());
140        editorUseOldLocSizeDisplay = new JCheckBox(ConfigBundle.getMessage("GUIUseOldLocSize"));
141        editorUseOldLocSizeDisplay.setSelected(InstanceManager.getDefault(GuiLafPreferencesManager.class).isEditorUseOldLocSize());
142        editorUseOldLocSizeDisplay.addItemListener((ItemEvent e) -> {
143            InstanceManager.getDefault(GuiLafPreferencesManager.class).setEditorUseOldLocSize(editorUseOldLocSizeDisplay.isSelected());
144        });
145        panel.add(editorUseOldLocSizeDisplay);
146    }
147
148    void doFileChooserLayoutType(JPanel panel) {
149        panel.setLayout(new FlowLayout());
150        JLabel fileChooserLabel = new JLabel(ConfigBundle.getMessage("GUIJChooserUseOption"));
151        panel.add(fileChooserLabel);
152                // make the radio buttons
153        JRadioButton jButDefault = new JRadioButton(ConfigBundle.getMessage("GUIJChooserUseDefault"));
154        panel.add(jButDefault);
155        fileChooserGroup.add(jButDefault);
156        jButDefault.addActionListener((ActionEvent e) -> {
157            if (((JRadioButton)e.getSource()).isSelected() ) {
158                InstanceManager.getDefault(GuiLafPreferencesManager.class).setJFileChooserFormat(0);
159            }
160        });
161        JRadioButton jButList = new JRadioButton(ConfigBundle.getMessage("GUIJChooserUseList"));
162        panel.add(jButList);
163        fileChooserGroup.add(jButList);
164        jButList.addActionListener((ActionEvent e) -> {
165            if (((JRadioButton)e.getSource()).isSelected() ) {
166                InstanceManager.getDefault(GuiLafPreferencesManager.class).setJFileChooserFormat(1);
167            }
168        });
169        JRadioButton jButDetail = new JRadioButton(ConfigBundle.getMessage("GUIJChooserUseDetail"));
170        panel.add(jButDetail);
171        fileChooserGroup.add(jButDetail);
172        jButDetail.addActionListener((ActionEvent e) -> {
173            if (((JRadioButton)e.getSource()).isSelected() ) {
174                InstanceManager.getDefault(GuiLafPreferencesManager.class).setJFileChooserFormat(2);
175            }
176        });
177        switch (InstanceManager.getDefault(GuiLafPreferencesManager.class).getJFileChooserFormat()) {
178            case 0:
179                jButDefault.setSelected(true);
180                break;
181            case 1:
182                jButList.setSelected(true);
183                break;
184            case 2:
185                jButDetail.setSelected(true);
186                break;
187            default:
188                jButDefault.setSelected(true);
189        }
190    }
191
192    void doForce100percentScaling(JPanel panel) {
193        jmri.util.EarlyInitializationPreferences eip =
194                jmri.util.EarlyInitializationPreferences.getInstance();
195
196        panel.setLayout(new FlowLayout());
197        force100percentScaling = new JCheckBox(ConfigBundle.getMessage("GUIForce100percentScaling"));
198        force100percentScaling.setSelected(eip.getGUIForce100percentScaling());
199        force100percentScaling.addItemListener((ItemEvent e) -> {
200            eip.setGUIForce100percentScaling(force100percentScaling.isSelected());
201        });
202        panel.add(force100percentScaling);
203    }
204
205    void doLAF(JPanel panel) {
206        // find L&F definitions from Swing
207        panel.setLayout(new FlowLayout());
208        UIManager.LookAndFeelInfo[] plafs = UIManager.getInstalledLookAndFeels();
209        HashMap<String, String> installedLAFs = new HashMap<>(plafs.length);
210        for (UIManager.LookAndFeelInfo plaf : plafs) {
211            installedLAFs.put(plaf.getName(), plaf.getClassName());
212        }
213        
214        // explicitly add the desired DarkLaf LaFs
215        installedLAFs.put("DarkLAF HC", "com.github.weisj.darklaf.theme.HighContrastDarkTheme");
216        // The LaFs available are
217        //  com/github/weisj/darklaf/theme/DarculaTheme
218        //  com/github/weisj/darklaf/theme/HighContrastDarkTheme
219        //  com/github/weisj/darklaf/theme/HighContrastLightTheme
220        //  com/github/weisj/darklaf/theme/IntelliJTheme
221        //  com/github/weisj/darklaf/theme/OneDarkTheme
222        //  com/github/weisj/darklaf/theme/SolarizedDarkTheme
223        //  com/github/weisj/darklaf/theme/SolarizedLightTheme
224        // But we're only listing a subset to avoid overwhelming the user
225        // See GuiLafPreferencesManager.applyLookAndFeel(..) for matching code
226
227        // make the radio buttons
228        for (java.util.Map.Entry<String, String> entry : installedLAFs.entrySet()) {
229            String name = entry.getKey();
230            JRadioButton jmi = new JRadioButton(name);
231            panel.add(jmi);
232            LAFGroup.add(jmi);
233            jmi.setActionCommand(name);
234            jmi.addActionListener((ActionEvent e) -> {
235                InstanceManager.getDefault(GuiLafPreferencesManager.class).setLookAndFeel(installedLAFs.get(name));
236            });
237            if ( entry.getValue().equals(UIManager.getLookAndFeel().getClass().getName())
238                // matching com.github.weisj.darklaf.theme.HighContrastDarkTheme with com.github.weisj.darklaf.DarkLaf
239                || ( entry.getValue().contains("darklaf")
240                    && UIManager.getLookAndFeel().getClass().getName().contains("darklaf") ) ) {
241                jmi.setSelected(true);
242            }
243        }
244    }
245
246    /**
247     * Create and return a JPanel for configuring default local.
248     * <p>
249     * Most of the action is handled in a separate thread, which replaces the
250     * contents of a JComboBox when the list of Locales is available.
251     *
252     * @return the panel
253     */
254    public JPanel doLocale() {
255        JPanel panel = new JPanel();
256        // add JComboBoxen for language and country
257        panel.setLayout(new FlowLayout());
258        panel.add(localeBox);
259        JComboBoxUtil.setupComboBoxMaxRows(localeBox);
260
261        // create object to find locales in new Thread
262        Runnable r = () -> {
263            Locale[] locales = Locale.getAvailableLocales();
264            String[] localeNames = new String[locales.length];
265            for (int i = 0; i < locales.length; i++) {
266                locale.put(locales[i].getDisplayName(), locales[i]);
267                localeNames[i] = locales[i].getDisplayName();
268            }
269            Arrays.sort(localeNames);
270            Runnable update = () -> {
271                localeBox.setModel(new DefaultComboBoxModel<>(localeNames));
272                //localeBox.setModel(new javax.swing.DefaultComboBoxModel(locale.keySet().toArray()));
273                localeBox.setSelectedItem(Locale.getDefault().getDisplayName());
274                localeBox.addActionListener((ActionEvent e) -> {
275                    InstanceManager.getDefault(GuiLafPreferencesManager.class).setLocale(locale.getOrDefault(localeBox.getSelectedItem(), Locale.getDefault()));
276                });
277            };
278            SwingUtilities.invokeLater(update);
279        };
280        new Thread(r).start();
281        return panel;
282    }
283
284    public void setLocale(String loc) {
285        localeBox.setSelectedItem(loc);
286    }
287
288    /**
289     * Get the currently configured Locale or Locale.getDefault if no
290     * configuration has been done.
291     *
292     * @return the in-use Locale
293     */
294    @Override
295    public Locale getLocale() {
296        Locale desired = locale.get(localeBox.getSelectedItem().toString());
297        return (desired != null) ? desired : Locale.getDefault();
298    }
299
300    public void doFontSize(JPanel panel) {
301        GuiLafPreferencesManager manager = InstanceManager.getDefault(GuiLafPreferencesManager.class);
302        Integer[] sizes = new Integer[MAX_DISPLAYED_FONT_SIZE - MIN_DISPLAYED_FONT_SIZE + 1];
303        for (int i = 0; i < sizes.length; i++) {
304            sizes[i] = i + MIN_DISPLAYED_FONT_SIZE;
305        }
306        fontSizeComboBox = new JComboBox<>(sizes);
307        fontSizeComboBox.setEditable(true); // allow users to set font sizes not listed
308        JLabel fontSizeLabel = new JLabel(ConfigBundle.getMessage("ConsoleFontSize"));
309        fontSizeComboBox.setSelectedItem(manager.getFontSize());
310        JLabel fontSizeUoM = new JLabel(ConfigBundle.getMessage("ConsoleFontSizeUoM"));
311        JButton resetButton = new JButton(ConfigBundle.getMessage("ResetDefault"));
312        resetButton.setToolTipText(ConfigBundle.getMessage("GUIFontSizeReset"));
313
314        panel.add(fontSizeLabel);
315        panel.add(fontSizeComboBox);
316        panel.add(fontSizeUoM);
317        panel.add(resetButton);
318
319        JComboBoxUtil.setupComboBoxMaxRows(fontSizeComboBox);
320
321        fontSizeComboBox.addActionListener((ActionEvent e) -> {
322            manager.setFontSize((int) fontSizeComboBox.getSelectedItem());
323        });
324        resetButton.addActionListener((ActionEvent e) -> {
325            if ((int) fontSizeComboBox.getSelectedItem() != manager.getDefaultFontSize()) {
326                fontSizeComboBox.setSelectedItem(manager.getDefaultFontSize());
327            }
328        });
329    }
330
331    private JSpinner maxComboRowsSpinner;
332
333    public void doMaxComboRows(JPanel panel) {
334        GuiLafPreferencesManager manager = InstanceManager.getDefault(GuiLafPreferencesManager.class);
335        JLabel maxComboRowsLabel = new JLabel(ConfigBundle.getMessage("GUIMaxComboRows"));
336        maxComboRowsSpinner = new JSpinner(new SpinnerNumberModel(manager.getMaxComboRows(), 0, 999, 1));
337        this.maxComboRowsSpinner.addChangeListener((ChangeEvent e) -> {
338            manager.setMaxComboRows((int) maxComboRowsSpinner.getValue());
339        });
340        this.maxComboRowsSpinner.setToolTipText(ConfigBundle.getMessage("GUIMaxComboRowsToolTip"));
341        maxComboRowsLabel.setToolTipText(this.maxComboRowsSpinner.getToolTipText());
342        panel.add(maxComboRowsLabel);
343        panel.add(maxComboRowsSpinner);
344    }
345
346    private JSpinner toolTipDismissDelaySpinner;
347
348    public void doToolTipDismissDelay(JPanel panel) {
349        GuiLafPreferencesManager manager = InstanceManager.getDefault(GuiLafPreferencesManager.class);
350        JLabel toolTipDismissDelayLabel = new JLabel(ConfigBundle.getMessage("GUIToolTipDismissDelay"));
351        toolTipDismissDelaySpinner = new JSpinner(new SpinnerNumberModel(manager.getToolTipDismissDelay() / 1000, MIN_TOOLTIP_TIME, MAX_TOOLTIP_TIME, 1));
352        this.toolTipDismissDelaySpinner.addChangeListener((ChangeEvent e) -> {
353            manager.setToolTipDismissDelay((int) toolTipDismissDelaySpinner.getValue() * 1000); // convert to milliseconds from seconds
354        });
355        this.toolTipDismissDelaySpinner.setToolTipText(MessageFormat.format(ConfigBundle.getMessage("GUIToolTipDismissDelayToolTip"), MIN_TOOLTIP_TIME, MAX_TOOLTIP_TIME));
356        toolTipDismissDelayLabel.setToolTipText(this.toolTipDismissDelaySpinner.getToolTipText());
357        JLabel toolTipDismissDelayUoM = new JLabel(ConfigBundle.getMessage("GUIToolTipDismissDelayUoM"));
358        toolTipDismissDelayUoM.setToolTipText(this.toolTipDismissDelaySpinner.getToolTipText());
359        panel.add(toolTipDismissDelayLabel);
360        panel.add(toolTipDismissDelaySpinner);
361        panel.add(toolTipDismissDelayUoM);
362    }
363
364    public String getClassName() {
365        return LAFGroup.getSelection().getActionCommand();
366
367    }
368
369    @Override
370    public String getPreferencesItem() {
371        return "DISPLAY"; // NOI18N
372    }
373
374    @Override
375    public String getPreferencesItemText() {
376        return ConfigBundle.getMessage("MenuDisplay"); // NOI18N
377    }
378
379    @Override
380    public String getTabbedPreferencesTitle() {
381        return ConfigBundle.getMessage("TabbedLayoutGUI"); // NOI18N
382    }
383
384    @Override
385    public String getLabelKey() {
386        return ConfigBundle.getMessage("LabelTabbedLayoutGUI"); // NOI18N
387    }
388
389    @Override
390    public JComponent getPreferencesComponent() {
391        return this;
392    }
393
394    @Override
395    public boolean isPersistant() {
396        return true;
397    }
398
399    @Override
400    public String getPreferencesTooltip() {
401        return null;
402    }
403
404    @Override
405    public void savePreferences() {
406        Profile profile = ProfileManager.getDefault().getActiveProfile();
407        if (profile != null) {
408            InstanceManager.getDefault(GuiLafPreferencesManager.class).savePreferences(profile);
409        }
410    }
411
412    @Override
413    public boolean isDirty() {
414        return InstanceManager.getDefault(GuiLafPreferencesManager.class).isDirty();
415    }
416
417    @Override
418    public boolean isRestartRequired() {
419        return InstanceManager.getDefault(GuiLafPreferencesManager.class).isRestartRequired();
420    }
421
422    @Override
423    public boolean isPreferencesValid() {
424        return true; // no validity checking performed
425    }
426}