View Javadoc

1   package com.melloware.jukes.gui.view.preferences;
2   
3   import java.awt.Component;
4   import java.beans.PropertyChangeEvent;
5   import java.beans.PropertyChangeListener;
6   import java.util.ArrayList;
7   import java.util.Collections;
8   import java.util.Comparator;
9   import java.util.logging.Logger;
10  import java.util.prefs.Preferences;
11  
12  import javax.swing.DefaultListCellRenderer;
13  import javax.swing.JList;
14  import javax.swing.JPanel;
15  import javax.swing.JScrollPane;
16  import javax.swing.ListModel;
17  import javax.swing.ListSelectionModel;
18  import javax.swing.LookAndFeel;
19  import javax.swing.UIManager;
20  import javax.swing.UnsupportedLookAndFeelException;
21  import javax.swing.event.ListSelectionEvent;
22  import javax.swing.event.ListSelectionListener;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  
27  import com.jgoodies.binding.list.ArrayListModel;
28  import com.jgoodies.binding.value.ValueModel;
29  import com.jgoodies.forms.builder.PanelBuilder;
30  import com.jgoodies.forms.layout.CellConstraints;
31  import com.jgoodies.forms.layout.FormLayout;
32  import com.jgoodies.looks.plastic.PlasticLookAndFeel;
33  import com.jgoodies.looks.plastic.PlasticTheme;
34  import com.jgoodies.uif.application.Application;
35  import com.jgoodies.uif.laf.ExtUIManager;
36  import com.jgoodies.uif.laf.LookConfiguration;
37  import com.jgoodies.uif.laf.LookConfigurations;
38  import com.jgoodies.uif.lazy.Preparable;
39  
40  
41  /**
42   * A panel for choosing a <code>LookAndFeel</code> and other
43   * look configuration settings, e.g. a color theme.<p>
44   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
45   * @author Emil A. Lefkof III <info@melloware.com>
46   * @version 4.0
47   * 
48   * @see ListModel
49   * @see ArrayListModel
50   */
51  
52  public final class LookAndFeelPanel extends JPanel {
53     /**
54      * Logger for this class
55      */
56     private static final Log LOG = LogFactory.getLog(LookAndFeelPanel.class);
57  
58      private static final ListModel PLASTIC_THEMES_MODEL =
59          new ArrayListModel(PlasticLookAndFeel.getInstalledThemes());
60  
61      private static final ListModel NO_THEMES_MODEL =
62          new ArrayListModel();
63  
64  
65      private static ListModel supportedLookAndFeelInstances;
66  
67      private final LookConfigurations configurations;
68  
69      private JList lafList;
70      private JList themesList;
71      private transient ListSelectionListener themeListener;
72      private LookAndFeelPreviewPanel previewPanel;
73      private boolean isLookChanged = false;
74  
75      
76      // Instance Creation ******************************************************
77      
78      /**
79       * Constructs a <code>LookAndFeelPanel</code> using the given
80       * apply trigger, which triggers the UI update.
81       * 
82       * @param triggerChannel    changes to <code>Boolean.TRUE</code> or 
83       *      <code>Boolean.FALSE</code> to indicate a commit or flush event
84       */
85      public LookAndFeelPanel(ValueModel triggerChannel) {
86          this.configurations = getClonedLookConfigurations();
87          initComponents();
88          build();
89          registerListeners();
90          triggerChannel.addValueChangeListener(new CommitHandler());
91      }
92  
93      private static LookConfigurations getClonedLookConfigurations() {
94          Object storedValue =
95              ExtUIManager.getLookConfigurations();
96          LookConfigurations storedConfigs = (LookConfigurations) storedValue;
97          return (LookConfigurations) storedConfigs.clone();
98      }
99  
100     // Public API ***********************************************************
101 
102     /**
103      * Ensures that the preselections in the look-and-feel list
104      * and in the themes list are visible.
105      */
106     public void ensureSelectionsAreVisible() {
107         lafList.ensureIndexIsVisible(lafList.getSelectedIndex());
108         themesList.ensureIndexIsVisible(themesList.getSelectedIndex());
109     }
110 
111     // Building *************************************************************
112 
113     private void initComponents() {
114         lafList = new JList();
115         lafList.setModel(getSupportedLookAndFeelInstances());
116         lafList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
117         lafList.setCellRenderer(new LooksListCellRenderer());
118         selectLook(configurations.getDefaultConfiguration().getLookAndFeel());
119 
120         themesList = new JList();
121         updateThemesModel();
122         selectTheme(getSelectedConfiguration().getTheme());
123         themesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
124         themesList.setCellRenderer(new ThemesListCellRenderer());
125 
126         previewPanel = new LookAndFeelPreviewPanel();
127     }
128     
129     /**
130      * Builds the panel: look and feel panel on top, and the preview at the bottom.
131      */
132     private void build() {
133         FormLayout fl = new FormLayout(
134                 "fill:default:grow, 4dlu, fill:default:grow", 
135                 "pref, 1dlu, fill:100dlu, 9dlu, pref, 1dlu, pref");
136         fl.setColumnGroups(new int[][]{{1, 3}});
137         fl.setRowGroups(new int[][]{{3, 7}});
138         
139         PanelBuilder builder = new PanelBuilder(fl, this);
140         builder.setDefaultDialogBorder();
141         CellConstraints cc = new CellConstraints();
142 
143         builder.addTitle("Look && Feel",         cc.xy (1, 1));
144         builder.addTitle("Theme",                cc.xy (3, 1));
145         builder.add(new JScrollPane(lafList),    cc.xy (1, 3));
146         builder.add(new JScrollPane(themesList), cc.xy (3, 3));
147         builder.addTitle("Preview",              cc.xyw(1, 5, 3));
148         builder.add(previewPanel,                cc.xyw(1, 7, 3));
149     }
150 
151     /**
152      * Registers listeners to respond to selections in the
153      * look-and-feel and themes lists.
154      */
155     private void registerListeners() {
156         // Listen to selection events.
157         lafList.addListSelectionListener(new ListSelectionListener() {
158             public void valueChanged(ListSelectionEvent e) {
159                 if (!e.getValueIsAdjusting())
160                     changedLookAndFeel();
161             }
162         });
163         themeListener = new ListSelectionListener() {
164             public void valueChanged(ListSelectionEvent e) {
165                 if (!e.getValueIsAdjusting())
166                     changedTheme();
167             }
168         };
169         // Listen to selection changes
170         themesList.addListSelectionListener(themeListener);
171     }
172 
173     // Updating *************************************************************
174 
175     /**
176      * The look and feel selection changed; performs the appropriate update actions.
177      */
178     private void changedLookAndFeel() {
179         isLookChanged = true; 
180         // Get the (new) selected look and feel.
181         //LookAndFeel laf = getSelectedLookAndFeel();
182         // Make it the selected configuration.
183         //configurations.getDefaultConfiguration().
184 
185         // Update the themes list without firing the change listener.
186         themesList.removeListSelectionListener(themeListener);
187         updateThemesModel();
188         selectTheme(getSelectedConfiguration().getTheme());
189         themesList.addListSelectionListener(themeListener);
190 
191         updatePreviewPanel();
192     }
193 
194     /**
195      * The theme selection changed; performs the appropriate update actions.
196      */
197     private void changedTheme() {
198         LookAndFeel laf = getSelectedLookAndFeel();
199         if (!(laf instanceof PlasticLookAndFeel))
200             return;
201         putConfiguration(createLookConfiguration());
202         updatePreviewPanel();
203     }
204 
205     /**
206      * Selects the specified <code>LookAndFeel</code>.
207      */
208     private void selectLook(LookAndFeel selectedLook) {
209         String lafClassName = selectedLook.getClass().getName();
210         ListModel model = lafList.getModel();
211         for (int i = 0; i < model.getSize(); i++) {
212             LookAndFeel laf = (LookAndFeel) model.getElementAt(i);
213             if (lafClassName.equals(laf.getClass().getName())) {
214                 lafList.setSelectedIndex(i);
215                 lafList.ensureIndexIsVisible(i);
216                 break;
217             }
218         }
219     }
220 
221     /**
222      * Selects the specified theme.
223      */
224     private void selectTheme(Object selectedTheme) {
225         if (selectedTheme == null)
226             return;
227         String themeClassName = selectedTheme.getClass().getName();
228         ListModel model = themesList.getModel();
229         for (int i = 0; i < model.getSize(); i++) {
230             PlasticTheme theme = (PlasticTheme) model.getElementAt(i);
231             if (themeClassName.equals(theme.getClass().getName())) {
232                 themesList.setSelectedIndex(i);
233                 themesList.ensureIndexIsVisible(i);
234                 break;
235             }
236         }
237     }
238 
239     /**
240      * Updates the preview panel.
241      */
242     private void updatePreviewPanel() {
243         try {
244             // Save the old L&F.
245             LookAndFeel oldLaf = UIManager.getLookAndFeel();
246             LookAndFeel selectedLaF = getSelectedLookAndFeel();
247             PlasticTheme oldTheme = null;
248 
249             if (selectedLaF instanceof PlasticLookAndFeel) {
250                 oldTheme = (PlasticTheme) PlasticLookAndFeel.getPlasticTheme(); 
251                 PlasticTheme theme = getSelectedTheme();
252                 if (theme != null)
253                     PlasticLookAndFeel.setPlasticTheme(theme);
254             }
255 
256             UIManager.setLookAndFeel(selectedLaF);
257             previewPanel.updateAndValidate();
258 
259             if (selectedLaF instanceof PlasticLookAndFeel)
260                 PlasticLookAndFeel.setPlasticTheme(oldTheme);
261 
262             // Restore the old L&F.
263             UIManager.setLookAndFeel(oldLaf);
264             previewPanel.updateComponentTree();
265 
266         } catch (UnsupportedLookAndFeelException e) {
267             // Ignore unsupported looks
268         }
269     }
270 
271     /**
272      * Updates the themes model.
273      */
274     private void updateThemesModel() {
275         boolean supportsThemes =
276             getSelectedLookAndFeel() instanceof PlasticLookAndFeel;
277 
278         ListModel newModel = supportsThemes 
279                                 ? PLASTIC_THEMES_MODEL 
280                                 : NO_THEMES_MODEL;
281         if (themesList.getModel() != newModel)
282             themesList.setModel(newModel);
283 
284         themesList.setEnabled(supportsThemes);
285     }
286 
287     // Accessing the look and feel configurations ***************************
288 
289     private LookConfiguration getSelectedConfiguration() {
290         return configurations.getDefaultConfiguration();
291     }
292 
293     @SuppressWarnings("unchecked")
294     private void putConfiguration(LookConfiguration config) {
295         configurations.getConfigurations().add(config);
296         configurations.setDefaultConfiguration(config);
297     }
298 
299     private LookConfiguration createLookConfiguration() {
300         LookAndFeel laf = getSelectedLookAndFeel();
301         Object theme =
302             laf instanceof PlasticLookAndFeel ? getSelectedTheme() : null;
303         return new LookConfiguration(laf, theme);
304     }
305 
306     private LookAndFeel getSelectedLookAndFeel() {
307         return (LookAndFeel) lafList.getSelectedValue();
308     }
309 
310     private PlasticTheme getSelectedTheme() {
311         return (PlasticTheme) themesList.getSelectedValue();
312     }
313 
314     // Computing the Supported Looks ****************************************
315 
316     /**
317      * Answers a cached and lazily instantiated list of supported look and feels.
318      */
319     private synchronized static ListModel getSupportedLookAndFeelInstances() {
320         if (supportedLookAndFeelInstances == null) {
321             supportedLookAndFeelInstances =
322                 new ArrayListModel(computeSupportedLookAndFeelInstances());
323         }
324         return supportedLookAndFeelInstances;
325     }
326 
327     /**
328      * Computes the list of supported look and feels.
329      */
330     @SuppressWarnings("unchecked")
331     private static java.util.List computeSupportedLookAndFeelInstances() {
332         UIManager.LookAndFeelInfo[] lafInfos =
333             UIManager.getInstalledLookAndFeels();
334         java.util.List result = new ArrayList(lafInfos.length);
335         for (int i = 0; i < lafInfos.length; i++) {
336             String className = lafInfos[i].getClassName();
337             LookAndFeel laf =
338                 ExtUIManager.createLookAndFeelInstance(className);
339             if ((laf != null) && (laf.isSupportedLookAndFeel()))
340                 result.add(laf);
341         }
342         Collections.sort(result, new Comparator() {
343             public int compare(Object o1, Object o2) {
344                 LookAndFeel laf1 = (LookAndFeel) o1;
345                 LookAndFeel laf2 = (LookAndFeel) o2;
346                 return laf1.getName().toUpperCase().compareTo(
347                     laf2.getName().toUpperCase());
348             }
349         });
350         return result;
351     }
352 
353     // Inner Helper Classes ***************************************************
354     
355     private class CommitHandler implements PropertyChangeListener {
356 
357         public void propertyChange(PropertyChangeEvent evt) {
358             if (Boolean.TRUE.equals(evt.getNewValue())) {
359                 if (isLookChanged) {
360                    LOG.warn("You may have to restart Jukes to see the Look and Feel changes");
361                 }
362                 ExtUIManager.setLookConfigurations(configurations);
363                 final LookConfiguration config = createLookConfiguration();
364                 ExtUIManager.setDefaultLookConfiguration(config);
365                 final Preferences prefs = Application.getUserPreferences();
366                 if (config.getTheme() != null) {
367                    prefs.put("laf.themeName."+ config.getLookAndFeel().getName(), config.getTheme().getClass().getName());
368                 }
369                 
370             }
371         }
372     }
373 
374     // Renders instances of LookAndFeel displaying their name
375     private static class LooksListCellRenderer extends DefaultListCellRenderer {
376 
377         public Component getListCellRendererComponent(
378                 JList list, Object value, int index,
379                 boolean isSelected,
380                 boolean cellHasFocus) {
381             return super.getListCellRendererComponent(
382                 list,
383                 ((LookAndFeel) value).getName(),
384                 index, isSelected, cellHasFocus);
385         }
386     }
387     
388     // Renders instances of MetalTheme
389     private static class ThemesListCellRenderer extends DefaultListCellRenderer {
390 
391         public Component getListCellRendererComponent(
392                 JList list, Object value, int index,
393                 boolean isSelected,
394                 boolean cellHasFocus) {
395             return super.getListCellRendererComponent(
396                 list,
397                 (value instanceof PlasticTheme
398                     ? ((PlasticTheme) value).getName()
399                     : value),
400                 index, isSelected, cellHasFocus);
401         }
402     }
403     
404     /**
405     * An implementation of the <code>Preparable</code> interface 
406     * that computes a list of supported <code>LookAndFeel</code> instances.
407     */
408     public static class EagerInitializer implements Preparable {
409 
410         /**
411          * Ensures that the supported look and feels are computed and that 
412          * the look and feel preview panel has been loaded once.
413          */
414         public void prepare() {
415             Logger.getLogger("LookAndFeelPanel").info(
416                 "Computing supported look and feels...");
417             getSupportedLookAndFeelInstances();
418             Logger.getLogger("LookAndFeelPanel").info(
419                 "Preloading preview panel classes...");
420             new LookAndFeelPreviewPanel();
421         }
422     }
423 
424 }