View Javadoc

1   /*
2    * Copyright (c) 2003-2008 by Cosylab d. d.
3    *
4    * This file is part of CosyBeans-Common.
5    *
6    * CosyBeans-Common is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU General Public License as published by
8    * the Free Software Foundation, either version 3 of the License, or
9    * (at your option) any later version.
10   *
11   * CosyBeans-Common is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU General Public License for more details.
15   *
16   * You should have received a copy of the GNU General Public License
17   * along with CosyBeans-Common.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  
20  package com.cosylab.gui.components.customizer;
21  
22  import java.awt.BorderLayout;
23  import java.awt.Component;
24  import java.awt.FlowLayout;
25  import java.awt.Font;
26  import java.awt.font.FontRenderContext;
27  import java.beans.BeanInfo;
28  import java.beans.Customizer;
29  import java.beans.IntrospectionException;
30  import java.beans.Introspector;
31  import java.beans.PropertyChangeEvent;
32  import java.beans.PropertyChangeListener;
33  import java.beans.PropertyDescriptor;
34  import java.util.ArrayList;
35  import java.util.Collections;
36  import java.util.HashMap;
37  import java.util.LinkedHashSet;
38  import java.util.List;
39  
40  import javax.swing.AbstractAction;
41  import javax.swing.AbstractListModel;
42  import javax.swing.JButton;
43  import javax.swing.JDialog;
44  import javax.swing.JList;
45  import javax.swing.JPanel;
46  import javax.swing.JScrollPane;
47  import javax.swing.JSplitPane;
48  import javax.swing.event.ListSelectionEvent;
49  import javax.swing.event.ListSelectionListener;
50  
51  /**
52   * <code>AbstractCustomizerPanel</code> is the base class which implements 
53   * the <code>java.beans.Customizer</code>. This class itself is a container
54   * which does not provide any customization functionality. It serves as
55   * a container, which can hold other implementations of Customizer and present
56   * all of them in a tabbed display.
57   * 
58   * @author Alen Vrecko
59   * 
60   */
61  public abstract class AbstractCustomizerPanel extends JSplitPane implements Customizer {
62  
63  	private Object bean;
64  	private JDialog dialog;
65  	private LinkedHashSet<Customizer> customizers;
66  	private HashMap<String, Component> aspectToComponent;
67  	private AspectModel model;
68  	private JList list;
69  	private JScrollPane rightPanel;
70  
71  	class AspectModel extends AbstractListModel {
72  		private static final long serialVersionUID = 1L;
73  		private List<String> aspects= new ArrayList<String>();
74  		
75  		/**
76  		 * Creates new instance of AbstractCustomizerPanel.AspectModel.
77  		 */
78  		public AspectModel() {
79  		}
80  
81  		/* (non-Javadoc)
82  		 * @see javax.swing.ListModel#getSize()
83  		 */
84  		public int getSize() {
85  			return aspects.size();
86  		}
87  
88  		/* (non-Javadoc)
89  		 * @see javax.swing.ListModel#getElementAt(int)
90  		 */
91  		public Object getElementAt(int index) {
92  			return aspects.get(index);
93  		}
94  		
95  		public void add(String name) {
96  			if (aspects.contains(name)) {
97  				return;
98  			}
99  			aspects.add(name);
100 			Collections.sort(aspects);
101 			fireContentsChanged(this,0,getSize()-1);
102 		}
103 		
104 	}
105 	
106 	/**
107 	 * The default constructor.
108 	 * 
109 	 */
110 	public AbstractCustomizerPanel() {
111 		initialize();
112 	}
113 
114 	/**
115 	 * Adds a Customizer to this container. NOTE: The Customizer should be a
116 	 * simple panel.
117 	 * 
118 	 * @param aspect
119 	 * @param customizer
120 	 */
121 	public void addCustomizer(String aspect, Customizer customizer) {
122 
123 		model.add(aspect);
124 		customizers.add(customizer);
125 		customizer.addPropertyChangeListener(new PropertyChangeListener() {
126 
127 			public void propertyChange(PropertyChangeEvent evt) {
128 				firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt
129 						.getNewValue());
130 
131 			}
132 		});
133 
134 		aspectToComponent.put(aspect, (Component) customizer);
135 		
136 		// Sets divider location to fit current aspect, if it doesn't already
137 		Font f = (new JScrollPane()).getFont();
138 		int l = (int) f.getStringBounds(aspect, new FontRenderContext(f.getTransform(), false, false)).getWidth();
139 		l += 25;
140 		if (getDividerLocation() < l) setDividerLocation(l);
141 	}
142 
143 	/**
144 	 * Adds a placeholder for aspect.
145 	 * 
146 	 * @param aspect
147 	 * @param panel
148 	 */
149 	public void addCustomizerPlaceholder(String aspect, JPanel panel) {
150 		model.add(aspect);
151 		aspectToComponent.put(aspect, panel);
152 	}
153 
154 	/**
155 	 * Creates a customizer for the table.
156 	 * 
157 	 * @param aspect
158 	 * @param properties
159 	 */
160 	public void addCustomizerTable(String aspect, String... properties) {
161 		addCustomizer(aspect, CustomizerFactory.createCustomizer(properties));
162 	}
163 
164 	/**
165 	 * Creates a customizer from the properties.
166 	 * 
167 	 * @param aspect
168 	 * @param properties
169 	 */
170 	public void addCustomizerTable(String aspect, PropertyDescriptor... properties) {
171 		addCustomizer(aspect, CustomizerFactory.createCustomizer(properties));
172 	}
173 
174 	/**
175 	 * Use this to put inherit from other customizers.
176 	 * 
177 	 * @param abstractCustomizer
178 	 */
179 	public void addCustomizer(AbstractCustomizerPanel abstractCustomizer) {
180 		// puts all aspects and aspect <-> component mappings to this Customizer
181 		for (int i=0; i< abstractCustomizer.model.getSize(); i++) {
182 			model.add((String)abstractCustomizer.model.getElementAt(i));
183 		}
184 		this.aspectToComponent.putAll(abstractCustomizer.aspectToComponent);
185 
186 		// puts all registered customizers from the other one tho this one
187 
188 		for (Customizer customizer : abstractCustomizer.customizers) {
189 			this.customizers.add(customizer);
190 
191 		}
192 
193 		// register the event delegation
194 
195 		abstractCustomizer.addPropertyChangeListener(new PropertyChangeListener() {
196 
197 			public void propertyChange(PropertyChangeEvent evt) {
198 				firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt
199 						.getNewValue());
200 
201 			}
202 		});
203 
204 	}
205 
206 	/**
207 	 * Tries to determine customizer for provided visual component in following
208 	 * order:
209 	 * 
210 	 * <ol>
211 	 * <li> Checks BeanInfo for the component.</li>
212 	 * <li> Tries to guess from the component class name. If component is name
213 	 * is MyComponent, then tries to load customizer class
214 	 * MyComponentCustomizer.</li>
215 	 * </ol>
216 	 * 
217 	 * @param disp
218 	 *            the component fo which ccustomizer is returned
219 	 * @return customizer foor the component
220 	 */
221 	public static AbstractCustomizerPanel findCustomizer(Object bean) {
222 
223 		// step 1 obtain from the BeanInfo
224 		Class beanType = bean.getClass();
225 		Class customizerClass;
226 		AbstractCustomizerPanel c=null;
227 
228 		try {
229 			BeanInfo beanInfo = Introspector.getBeanInfo(beanType);
230 
231 			customizerClass = beanInfo.getBeanDescriptor().getCustomizerClass();
232 
233 			if (customizerClass != null) {
234 				// determine if we have an AbstractCustomizerPanel
235 
236 				if (AbstractCustomizerPanel.class.isAssignableFrom(customizerClass)) {
237 					try {
238 						c= (AbstractCustomizerPanel) customizerClass.newInstance();
239 					} catch (InstantiationException e) {
240 						e.printStackTrace();
241 					} catch (IllegalAccessException e) {
242 						e.printStackTrace();
243 					}
244 				}
245 			}
246 
247 		} catch (IntrospectionException e) {
248 			e.printStackTrace();
249 		}
250 		
251 		if (c == null) {
252 			// step 2 : Obtain from file name
253 			try {
254 	
255 				customizerClass = Class.forName(beanType.getName() + "Customizer");
256 				if (customizerClass != null) {
257 					// determine if we have an AbstractCustomizerPanel
258 	
259 					if (AbstractCustomizerPanel.class.isAssignableFrom(customizerClass)) {
260 						try {
261 							c= (AbstractCustomizerPanel) customizerClass.newInstance();
262 						} catch (InstantiationException e) {
263 							e.printStackTrace();
264 						} catch (IllegalAccessException e) {
265 							e.printStackTrace();
266 						}
267 					}
268 	
269 				}
270 			} catch (ClassNotFoundException e) {
271 				e.printStackTrace();
272 			}
273 		}
274 		if (c!=null) {
275 			c.setObject(bean);
276 		}
277 		return c;
278 	}
279 
280 	private void initialize() {
281 		customizers = new LinkedHashSet<Customizer>();
282 		model= new AspectModel();
283 		aspectToComponent = new HashMap<String, Component>();
284 		list = new JList();
285 		list.setModel(model);
286 		list.addListSelectionListener(new ListSelectionListener() {
287 
288 			public void valueChanged(ListSelectionEvent e) {
289 
290 				Component component = aspectToComponent.get(list.getSelectedValue());
291 
292 				if (component != null) {
293 					rightPanel.setViewportView(component);
294 				}
295 
296 			}
297 		});
298 
299 		setLeftComponent(new JScrollPane(list));
300 		
301 		rightPanel= new JScrollPane();
302 		setRightComponent(rightPanel);
303 		setSize(500,300);
304 		setDividerLocation(30);
305 	}
306 
307 	/*
308 	 * (non-Javadoc)
309 	 * @see java.beans.Customizer#setObject(java.lang.Object)
310 	 */
311 	public void setObject(Object bean) {
312 		this.bean = bean;
313 
314 		for (Customizer customizer : customizers) {
315 			customizer.setObject(bean);
316 		}
317 		
318 		if (getName()==null) {
319 			String s= bean.getClass().getName();
320 			int i= s.lastIndexOf('.');
321 			if (i>-1 && i+1<s.length()) {
322 				setName(s.substring(i+1));
323 			} else {
324 				setName(s);
325 			}
326 		}
327 	}
328 
329 	/**
330 	 * <p>
331 	 * Shows Customizer dialog centered around customized displayer.
332 	 * </p>
333 	 * 
334 	 * <p>
335 	 * Dialog title is composed from Customizer name. Initial size is set to the
336 	 * size of Customizer.
337 	 * </p>
338 	 * 
339 	 * @return a <code>JDialog</code> instance with customizer
340 	 * 
341 	 */
342 	public JDialog showDialog() {
343 		return showDialog(bean instanceof Component ? (Component) bean : null);
344 	}
345 
346 	/**
347 	 * <p>
348 	 * Shows customizer dialog centered around specified component.
349 	 * </p>
350 	 * 
351 	 * <p>
352 	 * Dialog title is composed from customiser name. Initial size is set to the
353 	 * size of customiser.
354 	 * </p>
355 	 * 
356 	 * @param c
357 	 *            a component on which customizer dialog to be centered
358 	 * 
359 	 * @return a <code>javax.swing.JDialog</code> instance with customizer
360 	 * 
361 	 */
362 	public JDialog showDialog(Component c) {
363 		if (dialog == null) {
364 			dialog = new JDialog();
365 			dialog.setTitle(getName() + " Customizer");
366 			dialog.setModal(false);
367 			dialog.setSize(getSize().width + dialog.getInsets().left
368 					+ dialog.getInsets().right, getSize().height + dialog.getInsets().top
369 					+ dialog.getInsets().bottom);
370 			dialog.getContentPane().setLayout(new BorderLayout());
371 			dialog.getContentPane().add(this, BorderLayout.CENTER);
372 			JPanel p = new JPanel();
373 			p.setLayout(new FlowLayout(FlowLayout.RIGHT));
374 			p.add(new JButton(new AbstractAction("Close") {
375 				private static final long serialVersionUID = 1L;
376 				public void actionPerformed(java.awt.event.ActionEvent e) {
377 					dialog.dispose();
378 				};
379 			}));
380 			dialog.getContentPane().add(p, BorderLayout.SOUTH);
381 			dialog.setLocationRelativeTo(c);
382 		}
383 
384 		dialog.setVisible(false);
385 		dialog.setVisible(true);
386 		dialog.toFront();
387 		return dialog;
388 	}
389 	
390 	/**
391 	 * Hides the dialog if it is visible.
392 	 *
393 	 */
394 	public void closeDialog() {
395 		if (dialog != null) {
396 			dialog.setVisible(false);
397 		}
398 	}
399 	
400 	/**
401 	 * Selects an aspect in the aspect list.
402 	 * 
403 	 * @param aspect the aspect to select
404 	 */
405 	public void selectAspect(String aspect) {
406 		list.setSelectedValue(aspect, true);
407 	}
408 
409 }