View Javadoc

1   /*
2   Copyright (C) 2000 - 2007 Grid Systems, S.A.
3   
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2, as
6   published by the Free Software Foundation.
7   
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12  
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17  
18  /*
19   * Project: KernelConfigurator
20   * Created on 26-feb-2004
21   *
22   * Copyright (c)2003 Grid Systems
23   */
24  package com.gridsystems.config.tools.swing;
25  
26  import java.awt.LayoutManager;
27  import java.awt.event.ActionEvent;
28  import java.awt.event.ActionListener;
29  import java.awt.event.FocusEvent;
30  import java.awt.event.FocusListener;
31  import java.beans.PropertyChangeEvent;
32  import java.beans.PropertyChangeListener;
33  import java.text.MessageFormat;
34  import java.util.HashSet;
35  import java.util.Iterator;
36  import java.util.Locale;
37  import java.util.MissingResourceException;
38  import java.util.ResourceBundle;
39  import java.util.Set;
40  import java.util.Stack;
41  
42  import javax.swing.AbstractButton;
43  import javax.swing.Icon;
44  import javax.swing.InputVerifier;
45  import javax.swing.JComboBox;
46  import javax.swing.JComponent;
47  import javax.swing.JPanel;
48  import javax.swing.JTextField;
49  import javax.swing.event.ChangeEvent;
50  import javax.swing.event.ChangeListener;
51  
52  import com.gridsystems.config.Configurator;
53  import com.gridsystems.config.ConfiguratorModel;
54  import com.gridsystems.config.SwingConfiguratorView;
55  import com.gridsystems.config.tools.TextVerifier;
56  
57  /**
58   * Panel Extension for Swing Configuration Views. It adds some common functionalities,
59   * like:
60   * <ul>
61   *   <li>isModified() implementation, supported through focus events on fields
62   *   <li>Field group enabling through "guarding" buttons
63   *   <li>I18N support through internal bundle
64   * </ul>
65   *
66   * @author <a href="mailto:rruiz@gridsystems.com">Rodrigo Ruiz Aguayo</a>
67   * @version 1.0
68   */
69  public class BasePanel extends JPanel implements ActionListener, FocusListener,
70                                                   PropertyChangeListener,
71                                                   SwingConfiguratorView {
72  
73    /**
74     * The set of listeners for this panel.
75     */
76    private Set<ChangeListener> listeners = new HashSet<ChangeListener>();
77  
78    /**
79     * "Field value modified" flag.
80     */
81    private boolean modified = false;
82  
83    /**
84     * I18N resource bundle.
85     */
86    private ResourceBundle bundle;
87  
88    /**
89     * Icon attribute.
90     */
91    private Icon icon;
92  
93    /**
94     * Small icon attribute.
95     */
96    private Icon smallIcon;
97  
98    /**
99     * The configurator this view is associated to.
100    */
101   private Configurator config;
102 
103   /**
104    * Creates a new instance.
105    *
106    * @param config The configurator this view is associated to
107    */
108   public BasePanel(Configurator config) {
109     super();
110     this.config = config;
111   }
112 
113   /**
114    * Creates a new instance with the specified layout.
115    *
116    * @param layout The layout for this instance
117    * @param config The configurator this view is associated to
118    */
119   public BasePanel(LayoutManager layout, Configurator config) {
120     super(layout);
121     this.config = config;
122   }
123 
124   /**
125    * Sets the name of the bundle to be used by this instance.
126    *
127    * @param name The name of the resource bundle
128    */
129   public void setBundle(String name) {
130     Locale locale = Locale.getDefault();
131     this.bundle = ResourceBundle.getBundle(name, locale);
132   }
133 
134   /**
135    * Sets the bundle to be used by this instance.
136    *
137    * @param bundle The resource bundle
138    */
139   public void setBundle(ResourceBundle bundle) {
140     this.bundle = bundle;
141   }
142 
143   /**
144    * Sets the bundle used by this instance.
145    *
146    * @return the ResourceBundle used to get the internationalizated messages.
147    */
148   public ResourceBundle getBundle() {
149     return this.bundle;
150   }
151 
152   /**
153    * Gets the configurator this view is associated to.
154    *
155    * @return  The configurator
156    */
157   public Configurator getConfigurator() {
158     return this.config;
159   }
160 
161   /**
162    * {@inheritDoc}
163    */
164   public Icon getIcon() {
165     return icon;
166   }
167 
168   /**
169    * Sets the icon.
170    *
171    * @param icon The icon
172    */
173   public void setIcon(Icon icon) {
174     this.icon = icon;
175   }
176 
177   /**
178    * {@inheritDoc}
179    */
180   public Icon getSmallIcon() {
181     return smallIcon;
182   }
183 
184   /**
185    * Sets the small icon.
186    *
187    * @param icon The small icon
188    */
189   public void setSmallIcon(Icon icon) {
190     this.smallIcon = icon;
191   }
192 
193   /**
194    * {@inheritDoc}
195    */
196   public JComponent getComponent() {
197     return this;
198   }
199 
200   /**
201    * {@inheritDoc}
202    */
203   public String getTitle() {
204     return getString("config.name");
205   }
206 
207   /**
208    * {@inheritDoc}
209    */
210   public String getSubtitle() {
211     return getString("config.description");
212   }
213 
214   /**
215    * {@inheritDoc}
216    */
217   public void getValues(ConfiguratorModel model) {
218     throw new UnsupportedOperationException("Not implemented");
219   }
220 
221   /**
222    * {@inheritDoc}
223    */
224   public void setValues(ConfiguratorModel model) {
225     throw new UnsupportedOperationException("Not implemented");
226   }
227 
228   /**
229    * Gets a localized string.
230    *
231    * @param key The i18n key
232    * @return    The localized string, or null if none found for key
233    * @throws NullPointerException if the bundle is not set
234    */
235   public String getString(String key) {
236     if (bundle == null) {
237       throw new NullPointerException("Bundle not set");
238     }
239 
240     try {
241       return bundle.getString(key);
242     } catch (MissingResourceException e) {
243       return null;
244     }
245   }
246 
247   /**
248    * Gets a localized string, using the found string as a pattern to be used
249    * by a MessageFormat instance.
250    *
251    * @param key    The key for the localized pattern
252    * @param params The parameters for pattern substitution
253    * @return       The localized / formatter string
254    * @throws NullPointerException if the bundle is not set
255    */
256   public String getString(String key, Object[] params) {
257     if (bundle == null) {
258       throw new NullPointerException("Bundle not set");
259     }
260 
261     try {
262       String pattern = bundle.getString(key);
263       return MessageFormat.format(pattern, params);
264     } catch (MissingResourceException e) {
265       return key + "#not found";
266     }
267   }
268 
269   /**
270    * Gets if a change has been detected on the view.
271    *
272    * @return true if a change has been detected on this view, false otherwise
273    */
274   public boolean isModified() {
275     return modified;
276   }
277 
278   /**
279    * Adds a change listener to the listener list of this panel.
280    *
281    * @param listener The listener to add
282    */
283   public void addChangeListener(ChangeListener listener) {
284     synchronized (listeners) {
285       listeners.add(listener);
286     }
287   }
288 
289   /**
290    * Sets the "isModified" flag, and notifies all registered change listeners.
291    *
292    * @param src  The event source, for listeners notification
293    * @param flag The new value of the flag
294    */
295   protected void setModified(Object src, boolean flag) {
296     this.modified = flag;
297 
298     ChangeEvent event = new ChangeEvent(src);
299     synchronized (listeners) {
300       for (Iterator<ChangeListener> it = listeners.iterator(); it.hasNext();) {
301         ChangeListener l = it.next();
302         l.stateChanged(event);
303       }
304     }
305   }
306 
307   /**
308    * Registers itself as interested in changes in the specified text field.
309    *
310    * @param field The text field to listen
311    */
312   protected void watch(JTextField field) {
313     field.addFocusListener(this);
314   }
315 
316   /**
317    * Registers itself as interested in changes in the specified text field.
318    *
319    * @param field The text field to listen
320    */
321   protected void watch(PopupTextField field) {
322     field.addPropertyChangeListener(this);
323   }
324 
325   /**
326    * Registers itself as interested in changes in the specified combo box.
327    *
328    * @param field the combo box field to listen
329    */
330   protected void watch(JComboBox field) {
331     field.addActionListener(this);
332   }
333 
334   /**
335    * Registers btn as a "enabled" guard for the components in "guarded".
336    * The state of guarded components will be synchronized with the
337    * selection state of btn.
338    *
339    * @param btn     The guarding button
340    * @param guarded The list of components
341    */
342   protected void setGuarded(AbstractButton btn, JComponent[] guarded) {
343     btn.putClientProperty("guarded", guarded);
344     btn.addActionListener(this);
345   }
346 
347   /**
348    * If the event source is a button that is "guarding" a component set,
349    * it synchronizes the "enabled" state of the component set with the
350    * "selected" state of the source button.
351    *
352    * @param event The event containing the event source
353    */
354   public void actionPerformed(ActionEvent event) {
355     Object src = event == null ? this : event.getSource();
356     if (src instanceof AbstractButton) {
357       AbstractButton btn = (AbstractButton)src;
358       boolean selected = btn.isSelected();
359 
360       JComponent[] guarded = (JComponent[])btn.getClientProperty("guarded");
361       int count = (guarded == null) ? 0 : guarded.length;
362       for (int i = 0; i < count; i++) {
363         guarded[i].setEnabled(selected);
364       }
365     }
366     setModified(src, true);
367   }
368 
369   /**
370    * {@inheritDoc}
371    */
372   public void focusGained(FocusEvent e) {
373     Object src = e.getSource();
374     if (src instanceof JTextField) {
375       JTextField field = (JTextField)src;
376       field.putClientProperty("originalText", field.getText());
377     }
378   }
379 
380   /**
381    * {@inheritDoc}
382    */
383   public void focusLost(FocusEvent e) {
384     Object src = e.getSource();
385     if (src instanceof JTextField) {
386       JTextField field = (JTextField)src;
387       String text = field.getText();
388       String original = (String)field.getClientProperty("originalText");
389 
390       InputVerifier verifier = field.getInputVerifier();
391       if (verifier != null) {
392         boolean valid = verifier.verify(field);
393         if (!valid) {
394           //field.setText(original);
395           return;
396         }
397       }
398 
399       if (!original.equals(text)) {
400         setModified(field, true);
401       }
402     }
403   }
404 
405   /**
406    * Manages a PropertyChangeEvent.
407    *
408    * Manages a PropertyChangeEvent. It uses the input verifier (if any) from
409    * the source object to check the new value. If it is not valid, the old
410    * value is set again to the component.
411    *
412    * @param propertyChangeEvent a propertyChangeEvent with the information of
413    * the event.
414    *
415    * @warning current implementation only reacts to events from PopupTextField
416    * objects. If the verifier is a text verifier, the verification is done
417    * with the result of calling the toString() to the value returned from
418    * getValue() from the source object. If the verifier is not a text verifier,
419    * then the verification uses the entire source object.
420    */
421   public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
422     Object source = propertyChangeEvent.getSource();
423     if (source == null) {
424       return;
425     }
426     if (source instanceof PopupTextField) {
427       PopupTextField popupTextField = (PopupTextField) source;
428       // SJM: If the new value received from the event is not the value
429       // currently managed by the component, this is not the event that we want
430       // to check.
431       if (!popupTextField.getPropertyName().equals(
432         propertyChangeEvent.getPropertyName())) {
433         return;
434       }
435 
436       InputVerifier verifier = popupTextField.getInputVerifier();
437       if (verifier != null) {
438         if (verifier instanceof TextVerifier) {
439           TextVerifier textVerifier = (TextVerifier) verifier;
440           String newValueText = popupTextField.getValue() == null
441                               ? null : popupTextField.getValue().toString();
442           if (!textVerifier.verify(newValueText)) {
443             popupTextField.discard();
444             return;
445           }
446         } else {
447           if (!verifier.verify(popupTextField)) {
448             popupTextField.setValue(propertyChangeEvent.getOldValue());
449             return;
450           }
451         }
452       }
453     }
454     this.setModified(source, true);
455   }
456 
457   /**
458    * {@inheritDoc}
459    */
460   public boolean hasErrors() {
461     Stack stack = (Stack)this.getClientProperty("error.messages");
462     return stack != null && stack.size() > 0;
463   }
464 }