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