001    /* JAPI - (Yet another (hopefully) useful) Java API
002     *
003     * Copyright (C) 2004-2006 Christian Hujer
004     *
005     * This program is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU General Public License as
007     * published by the Free Software Foundation; either version 2 of the
008     * License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful, but
011     * WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013     * General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program; if not, write to the Free Software
017     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018     * 02111-1307, USA.
019     */
020    
021    package net.sf.japi.swing.prefs;
022    
023    import java.awt.BorderLayout;
024    import java.awt.CardLayout;
025    import java.awt.Component;
026    import java.util.HashMap;
027    import java.util.Map;
028    import javax.swing.Action;
029    import static javax.swing.BorderFactory.createEmptyBorder;
030    import javax.swing.Box;
031    import javax.swing.DefaultListCellRenderer;
032    import javax.swing.JButton;
033    import javax.swing.JComponent;
034    import javax.swing.JDialog;
035    import javax.swing.JList;
036    import javax.swing.JOptionPane;
037    import javax.swing.JPanel;
038    import javax.swing.JScrollPane;
039    import javax.swing.SwingConstants;
040    import javax.swing.border.Border;
041    import javax.swing.event.ListSelectionEvent;
042    import javax.swing.event.ListSelectionListener;
043    import net.sf.japi.swing.ActionFactory;
044    
045    /** Panel to display preferences.
046     * @serial exclude This class is not intended to be serialized.
047     * @author <a href="mailto:chris@riedquat.de">Christian Hujer</a>
048     */
049    @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
050    public final class PreferencesPane extends JOptionPane implements ListSelectionListener {
051    
052        /** Action Factory. */
053        private static final ActionFactory ACTION_FACTORY = ActionFactory.getFactory("net.sf.japi.swing.prefs");
054    
055        /** A map for DIALOGS that are already displaying.
056         * This map is used to prevent the dialog for the same PreferencesGroup be shown twice within the same application.
057         */
058        private static final Map<PreferencesGroup,JDialog> DIALOGS = new HashMap<PreferencesGroup,JDialog>();
059    
060        /** The group of preferences to display. */
061        private final PreferencesGroup prefs;
062    
063        /** The currently selected preferences module. */
064        private Prefs currentPref;
065    
066        /** Action for help. */
067        private final Action helpAction     = ACTION_FACTORY.createAction(false, "help"    , this);
068    
069        /** Action for defaults. */
070        private final Action defaultsAction = ACTION_FACTORY.createAction(false, "defaults", this);
071    
072        /** Action for ok. */
073        private final Action okAction       = ACTION_FACTORY.createAction(false, "ok"      , this);
074    
075        /** Action for apply. */
076        private final Action applyAction    = ACTION_FACTORY.createAction(false, "apply"   , this);
077    
078        /** Action for revert. */
079        private final Action revertAction   = ACTION_FACTORY.createAction(false, "revert"  , this);
080    
081        /** Action for cancel. */
082        private final Action cancelAction   = ACTION_FACTORY.createAction(false, "cancel"  , this);
083    
084        /** CardLayout for switching between prefs modules.
085         * @see #cardPanel
086         */
087        private final CardLayout cards = new CardLayout();
088    
089        /** Panel where the CardLayout for switching between prefs modules is used.
090         * @see #cards
091         */
092        private final JPanel cardPanel = new JPanel(cards);
093    
094        /** Show Preferences.
095         * @param parentComponent determines the Frame in which the dialog is displayed; if <code>null</code>, or if the <code>parentComponent</code> has
096         * no <code>Frame</code>, a default <code>Frame</code> is used
097         * @param prefs PreferencesGroup to be displayed
098         * @param modal <code>true</code> if the displayed dialog should be modal, otherwise <code>false</code>
099         */
100        public static void showPreferencesDialog(final Component parentComponent, final PreferencesGroup prefs, final boolean modal) {
101            synchronized (DIALOGS) {
102                if (DIALOGS.containsKey(prefs)) {
103                    DIALOGS.get(prefs).toFront();
104                } else {
105                    final PreferencesPane pane = new PreferencesPane(prefs);
106                    final JDialog dialog = pane.createDialog(parentComponent, prefs.getTitle());
107                    DIALOGS.put(prefs, dialog);
108                    dialog.setResizable(true);
109                    dialog.setModal(modal);
110                    dialog.setVisible(true);
111                }
112            }
113        }
114    
115        /** Create a PreferencesPane.
116         * @param prefs PreferencesGroup to create panel for
117         */
118        private PreferencesPane(final PreferencesGroup prefs) {
119            this.prefs = prefs;
120            setMessage(createMessage());
121            setOptions(createOptions());
122        }
123    
124        /** Create the Message.
125         * @return subpanel
126         */
127        private JComponent createMessage() {
128            final JPanel panel = new JPanel(new BorderLayout());
129            panel.add(createList(), BorderLayout.WEST);
130            panel.add(createPanel(), BorderLayout.CENTER);
131            return panel;
132        }
133    
134        /** Create the list.
135         * @return list
136         */
137        private JComponent createList() {
138            final JList list = new JList(prefs);
139            list.setCellRenderer(new PrefsListCellRenderer());
140            list.setSelectedIndex(0);
141            list.addListSelectionListener(this);
142            return new JScrollPane(list);
143        }
144    
145        /** Create the Panel.
146         * @return panel
147         */
148        private JComponent createPanel() {
149            int index = 0;
150            for (final Prefs pref : prefs) {
151                cardPanel.add(Integer.toString(index++), pref.getEditComponent());
152            }
153            currentPref = prefs.getElementAt(0);
154            return cardPanel;
155        }
156    
157        /** Create the Options.
158         * @return options
159         */
160        private Object[] createOptions() {
161            return new Object[] {
162                new JButton(helpAction),
163                new JButton(defaultsAction),
164                Box.createHorizontalStrut(50),
165                new JButton(okAction),
166                new JButton(applyAction),
167                Box.createHorizontalStrut(50),
168                new JButton(revertAction),
169                new JButton(cancelAction),
170            };
171        }
172    
173        /** {@inheritDoc} */
174        public void valueChanged(final ListSelectionEvent e) {
175            if (e.getValueIsAdjusting()) {
176                return;
177            }
178            final int index = ((JList) e.getSource()).getSelectedIndex();
179            final Prefs newPref = prefs.getElementAt(index);
180            //noinspection ObjectEquality
181            if (currentPref == newPref) {
182                return;
183            }
184            if (currentPref.isChanged()) {
185                final int result = ACTION_FACTORY.showConfirmDialog(this, YES_NO_CANCEL_OPTION, QUESTION_MESSAGE, "prefsChanged", currentPref.getListLabelText());
186                switch (result) {
187                    case CLOSED_OPTION:
188                    case CANCEL_OPTION:
189                        ((JList) e.getSource()).setSelectedValue(currentPref, true);
190                        return;
191                    case YES_OPTION:
192                        currentPref.apply();
193                        break;
194                    case NO_OPTION:
195                    default:
196                }
197            }
198            currentPref = newPref;
199            cards.show(cardPanel, Integer.toString(index));
200        }
201    
202        /** Action method for cancel.
203         * @used
204         */
205        public void cancel() {
206            setValue(CANCEL_OPTION);
207        }
208    
209        /** {@inheritDoc} */
210        @Override public void setValue(final Object newValue) {
211            super.setValue(newValue);
212            //noinspection ObjectEquality
213            if (newValue != null && newValue != UNINITIALIZED_VALUE) {
214                synchronized (DIALOGS) {
215                    DIALOGS.remove(prefs).dispose();
216                }
217            }
218        }
219    
220        /** Action method for defaults.
221         * @used
222         */
223        public void defaults() {
224            currentPref.defaults();
225        }
226    
227        /** Action method for help.
228         * @used
229         */
230        public void help() {
231            // TODO
232        }
233    
234        /** Action method for ok.
235         * @used
236         */
237        @SuppressWarnings({"InstanceMethodNamingConvention"})
238        public void ok() {
239            apply();
240            setValue(OK_OPTION);
241        }
242    
243        /** Action method for apply.
244         * @used
245         */
246        public void apply() {
247            if (currentPref.isChanged()) {
248                currentPref.apply();
249            }
250        }
251    
252        /** Action method for revert.
253         * @used
254         */
255        public void revert() {
256            if (currentPref.isChanged()) {
257                currentPref.revert();
258            }
259        }
260    
261        /** Class for rendering preferences list items. */
262        private static final class PrefsListCellRenderer extends DefaultListCellRenderer {
263    
264            /** The border.
265             * For some reason it gets lost when set in the initializer, so we store it and set it each time the renderer is used.
266             */
267            private Border border = createEmptyBorder(10, 10, 10, 10);
268    
269            /** Create a PrefsListCellRenderer. */
270            PrefsListCellRenderer() {
271                setHorizontalTextPosition(SwingConstants.CENTER);
272                setVerticalTextPosition(SwingConstants.BOTTOM);
273                setHorizontalAlignment(SwingConstants.CENTER);
274                setVerticalAlignment(SwingConstants.CENTER);
275            }
276    
277            /** {@inheritDoc} */
278            @SuppressWarnings({"ReturnOfThis"})
279            @Override public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) {
280                super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
281                setBorder(border);
282                final Prefs pref = (Prefs) value;
283                setText(pref.getListLabelText());
284                setIcon(pref.getListLabelIcon());
285                return this;
286            }
287    
288        } // class PrefsListCellRenderer
289    
290    } // class PreferencesPane