Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
InfoPanel |
|
| 2.6538461538461537;2.654 |
1 | /* |
|
2 | Wotonomy: OpenStep design patterns for pure Java applications. |
|
3 | Copyright (C) 2000 Blacksmith, Inc. |
|
4 | ||
5 | This library is free software; you can redistribute it and/or |
|
6 | modify it under the terms of the GNU Lesser General Public |
|
7 | License as published by the Free Software Foundation; either |
|
8 | version 2.1 of the License, or (at your option) any later version. |
|
9 | ||
10 | This library is distributed in the hope that it will be useful, |
|
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
13 | Lesser General Public License for more details. |
|
14 | ||
15 | You should have received a copy of the GNU Lesser General Public |
|
16 | License along with this library; if not, see http://www.gnu.org |
|
17 | */ |
|
18 | ||
19 | package net.wotonomy.ui.swing.components; |
|
20 | ||
21 | import java.awt.AWTEventMulticaster; |
|
22 | import java.awt.BorderLayout; |
|
23 | import java.awt.Component; |
|
24 | import java.awt.Container; |
|
25 | import java.awt.Dimension; |
|
26 | import java.awt.GridBagConstraints; |
|
27 | import java.awt.GridBagLayout; |
|
28 | import java.awt.GridLayout; |
|
29 | import java.awt.Insets; |
|
30 | import java.awt.event.ActionEvent; |
|
31 | import java.awt.event.ActionListener; |
|
32 | import java.beans.BeanInfo; |
|
33 | import java.beans.Introspector; |
|
34 | import java.beans.MethodDescriptor; |
|
35 | import java.lang.reflect.Method; |
|
36 | import java.util.ArrayList; |
|
37 | import java.util.Collection; |
|
38 | import java.util.Collections; |
|
39 | import java.util.HashMap; |
|
40 | import java.util.Iterator; |
|
41 | import java.util.LinkedList; |
|
42 | import java.util.List; |
|
43 | import java.util.Map; |
|
44 | ||
45 | import javax.swing.Box; |
|
46 | import javax.swing.JLabel; |
|
47 | import javax.swing.JPanel; |
|
48 | import javax.swing.JTextField; |
|
49 | import javax.swing.SwingConstants; |
|
50 | ||
51 | /** |
|
52 | * InfoPanel uses labels and textfields (or any other component - see below) |
|
53 | * to display a list of keys and values in a well-aligned and consistent manner, |
|
54 | * conforming to alignment and pixel spacing in the java look and feel |
|
55 | * <a href="http://java.sun.com/products/jlf/dg/higg.htm#55417">design guidelines</a>. |
|
56 | * <BR><BR> |
|
57 | * |
|
58 | * Each key is displayed in a label to the left of the component that contains |
|
59 | * the corresponding value. Each row is displayed starting at the top of the |
|
60 | * component's available area. Each row's height is the maximum preferred |
|
61 | * height of its components and the field itself gets as much of the width as |
|
62 | * it can, dependent on the length of the longest label. <BR><BR> |
|
63 | * |
|
64 | * The values in the fields can be editable, and the |
|
65 | * current value can be retrieved using the key - for this reason, unique keys |
|
66 | * are recommended. <BR><BR> |
|
67 | * |
|
68 | * As a convenience, push buttons may be placed across the |
|
69 | * bottom of the panel in a manner similar to ButtonPanel. <BR><BR> |
|
70 | * |
|
71 | * The panel forwards any ActionEvents generated by the components and |
|
72 | * buttons on it to all registered listeners. <BR><BR> |
|
73 | * |
|
74 | * Optionally, any component can be used instead of a textfield. |
|
75 | * However, <code>get/setValueForKey()</code> and <code>get/setEditable()</code> |
|
76 | * may not work for those components. Use <code>getComponentForKey()</code> to |
|
77 | * access them instead. |
|
78 | * |
|
79 | * @author michael@mpowers.net |
|
80 | * @author $Author: cgruber $ |
|
81 | * @version $Revision: 904 $ |
|
82 | * $Date: 2006-02-18 23:19:05 +0000 (Sat, 18 Feb 2006) $ |
|
83 | */ |
|
84 | 0 | public class InfoPanel extends JPanel implements ActionListener |
85 | { |
|
86 | /** |
|
87 | * Special label for an empty pair - a label and component |
|
88 | * that take up space but are hidden from view. This might |
|
89 | * be useful for achieving certain layouts. |
|
90 | */ |
|
91 | public static final String HIDDEN = "(hidden)"; |
|
92 | ||
93 | /** Cache for the introspectComponent method */ |
|
94 | 0 | private static Map _method_cache = |
95 | 0 | Collections.synchronizedMap( new HashMap(30) ); |
96 | ||
97 | 0 | protected Container listContainer = null; |
98 | protected int hgap; // set in constructor |
|
99 | protected int vgap; // set in constructor |
|
100 | protected int margin; // set in constructor |
|
101 | protected int columns; // set in constructor |
|
102 | 0 | protected List fields = null; |
103 | 0 | protected List labels = null; |
104 | 0 | protected List fieldSpacers = null; |
105 | 0 | protected ButtonPanel buttonPanel = null; |
106 | 0 | protected boolean isEditable = true; |
107 | protected String prefix; |
|
108 | protected String postfix; |
|
109 | protected int labelAnchor; |
|
110 | protected int labelAlign; |
|
111 | // protected Component marginStrut = null; |
|
112 | ||
113 | // for action multicasting |
|
114 | 0 | protected ActionListener actionListener = null; |
115 | ||
116 | /** |
|
117 | * Constructs an empty InfoPanel. |
|
118 | */ |
|
119 | 0 | public InfoPanel() |
120 | 0 | { |
121 | 0 | hgap = 12; // per java l&f guidelines |
122 | 0 | vgap = 6; // java l&f says 11 |
123 | 0 | columns = 1; // default columns |
124 | 0 | margin = 0; // default margin: none |
125 | 0 | prefix = ""; // default prefix: none |
126 | 0 | postfix = ":"; // per java l&f guidelines |
127 | 0 | fields = new ArrayList(); |
128 | 0 | labels = new ArrayList(); |
129 | 0 | labelAnchor = GridBagConstraints.NORTHWEST; |
130 | // per java l&f guidelines (CENTER is nicer) |
|
131 | 0 | labelAlign = SwingConstants.LEFT; |
132 | // per java l&f guidelines |
|
133 | ||
134 | 0 | doInitialLayout(); |
135 | 0 | } |
136 | ||
137 | /** |
|
138 | * Constructs an InfoPanel with the specified labels |
|
139 | * each paired with a blank textfield. |
|
140 | * @param labelArray An Array containing the labels in the |
|
141 | * order in which they should appear from top to bottom. |
|
142 | * A null value produces an empty panel. |
|
143 | */ |
|
144 | public InfoPanel( String[] labelArray ) |
|
145 | { |
|
146 | 0 | this(); |
147 | 0 | setLabels( labelArray ); |
148 | 0 | } |
149 | ||
150 | /** |
|
151 | * Creates a set of labels and empty textfields after first |
|
152 | * clearing all existing components on the panel. |
|
153 | * @param labelArray An Array containing the labels in the order |
|
154 | * in which they should appear from top to bottom. A null |
|
155 | * value will clear the panel. |
|
156 | */ |
|
157 | public void setLabels( String[] labelArray ) |
|
158 | { |
|
159 | 0 | removeAll(); |
160 | 0 | if ( labelArray == null ) return; // null clears panel |
161 | 0 | for ( int i = 0; i < labelArray.length; i++ ) |
162 | { |
|
163 | 0 | addPair( labelArray[i], new JTextField() ); |
164 | } |
|
165 | 0 | } |
166 | ||
167 | /** |
|
168 | * Retrieves the labls for the components on the panel |
|
169 | * in the order in which they are displayed from top WIDTH bottom. |
|
170 | * These are the keys used to reference values or to reference |
|
171 | * the components directly. |
|
172 | * @return An Array of Strings containing the labels. |
|
173 | */ |
|
174 | public String[] getLabels() |
|
175 | { |
|
176 | 0 | int length = fields.size(); |
177 | 0 | String[] labelArray = new String[ length ]; |
178 | 0 | for ( int i = 0; i < length; i++ ) |
179 | { |
|
180 | 0 | labelArray[i] = ((Component)fields.get(i)).getName(); |
181 | } |
|
182 | 0 | return labelArray; |
183 | } |
|
184 | ||
185 | /** |
|
186 | * Retrieves the constant used to anchor the labels in place. |
|
187 | * The default value is GridBagConstraints.NORTHWEST. |
|
188 | */ |
|
189 | public int getLabelAnchor() |
|
190 | { |
|
191 | 0 | return labelAnchor; |
192 | } |
|
193 | ||
194 | /** |
|
195 | * Sets the constant used to anchor the labels in place |
|
196 | * and reflows the layout. |
|
197 | * @param anAnchorConstant An anchor constant from |
|
198 | * GridBagConstraints. |
|
199 | */ |
|
200 | public void setLabelAnchor( int anAnchorConstant ) |
|
201 | { |
|
202 | 0 | labelAnchor = anAnchorConstant; |
203 | 0 | updateLabels(); |
204 | 0 | } |
205 | ||
206 | /** |
|
207 | * Retrieves the constant used to align the labels in place. |
|
208 | * The default value is GridBagConstraints.CENTER. |
|
209 | */ |
|
210 | public int getLabelAlignment() |
|
211 | { |
|
212 | 0 | return labelAlign; |
213 | } |
|
214 | ||
215 | /** |
|
216 | * Sets the constant used to align the labels in place |
|
217 | * and reflows the layout. |
|
218 | * @param anAlignmentConstant LEFT, CENTER, or RIGHT constants |
|
219 | * from SwingUtilities. |
|
220 | */ |
|
221 | public void setLabelAlignment( int anAlignmentConstant ) |
|
222 | { |
|
223 | 0 | labelAlign = anAlignmentConstant; |
224 | 0 | updateLabels(); |
225 | 0 | } |
226 | ||
227 | /** |
|
228 | * Factory method for creating panel spacers. |
|
229 | * This implementation returns a JPanel with |
|
230 | * opaque set to false. Override to customize. |
|
231 | */ |
|
232 | public JPanel createPanel() |
|
233 | { |
|
234 | 0 | JPanel result = new JPanel(); |
235 | 0 | result.setOpaque( false ); |
236 | 0 | return result; |
237 | } |
|
238 | ||
239 | /** |
|
240 | * This method is responsible for the initial layout of the panel. |
|
241 | * All labels and textfields will later added to listContainer. |
|
242 | * This method is responsible for initializing listContainer. |
|
243 | */ |
|
244 | protected void doInitialLayout() |
|
245 | { |
|
246 | 0 | listContainer = createPanel(); |
247 | 0 | listContainer.setLayout( new BetterGridBagLayout() ); |
248 | 0 | this.setLayout( new BorderLayout() ); |
249 | 0 | this.add( listContainer, BorderLayout.NORTH ); |
250 | ||
251 | //listContainer.setBackground( Color.blue ); // useful for testing |
|
252 | //this.setBackground( Color.red ); |
|
253 | 0 | } |
254 | ||
255 | /** |
|
256 | * Changes the horizontal spacing between the label and the components in the panel. |
|
257 | * Note: Assumes listContainer uses a GridBagLayout. |
|
258 | * @param newHgap the new spacing, in pixels. May not be negative. |
|
259 | */ |
|
260 | public void setHgap( int newHgap ) |
|
261 | { |
|
262 | 0 | if ( newHgap < 0 ) return; // may not be negative |
263 | 0 | this.hgap = newHgap; |
264 | 0 | updateGaps(); |
265 | 0 | this.revalidate(); |
266 | 0 | this.repaint(); |
267 | ||
268 | 0 | } |
269 | ||
270 | /** |
|
271 | * Gets the current horizontal spacing between components. |
|
272 | * @return the current horizontal spacing, in pixels. |
|
273 | */ |
|
274 | public int getHgap() |
|
275 | { |
|
276 | 0 | return this.hgap; |
277 | } |
|
278 | ||
279 | /** |
|
280 | * Changes the vertical spacing between components in the panel. |
|
281 | * Note: Assumes listContainer uses a GridBagLayout. |
|
282 | * @param newVgap the new spacing, in pixels. May not be negative. |
|
283 | */ |
|
284 | public void setVgap( int newVgap ) |
|
285 | { |
|
286 | 0 | if ( newVgap < 0 ) return; // may not be negative |
287 | 0 | this.vgap = newVgap; |
288 | 0 | updateGaps(); |
289 | 0 | this.revalidate(); |
290 | 0 | this.repaint(); |
291 | ||
292 | 0 | } |
293 | ||
294 | /** |
|
295 | * Gets the current vertical spacing between components. |
|
296 | * @return the current vertical spacing, in pixels. |
|
297 | */ |
|
298 | public int getVgap() |
|
299 | { |
|
300 | 0 | return this.vgap; |
301 | } |
|
302 | ||
303 | /** |
|
304 | * Sets the minimum width for the labels column. |
|
305 | * This left margin will grow if one of the labels |
|
306 | * is wider than this value. |
|
307 | * Note: assumes GridBagLayout. |
|
308 | * @param newMargin the new minimum margin in pixels. May not be negative. |
|
309 | */ |
|
310 | public void setMargin( int newMargin ) |
|
311 | { |
|
312 | 0 | if ( newMargin < 0 ) return; // may not be negative |
313 | 0 | this.margin = newMargin; |
314 | ||
315 | 0 | if ( listContainer.getLayout() instanceof GridBagLayout ) |
316 | { |
|
317 | 0 | GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); |
318 | 0 | GridBagConstraints constraints = null; |
319 | 0 | Component c = null; |
320 | 0 | int count = listContainer.getComponentCount(); |
321 | 0 | for ( int i = 0; i < count; i++ ) |
322 | { |
|
323 | 0 | c = listContainer.getComponent( i ); |
324 | 0 | constraints = gridBag.getConstraints( c ); |
325 | 0 | if ( constraints.gridy == 0 && constraints.gridx % 2 == 0 ) |
326 | { // if this is a label spacer |
|
327 | // replace it with an appropriately sized box |
|
328 | 0 | listContainer.remove( c ); |
329 | 0 | listContainer.add( Box.createHorizontalStrut( this.margin ), constraints ); |
330 | } |
|
331 | } |
|
332 | } |
|
333 | ||
334 | 0 | this.revalidate(); |
335 | 0 | this.repaint(); |
336 | ||
337 | 0 | } |
338 | ||
339 | /** |
|
340 | * Gets the current minimum margin for the labels column. |
|
341 | * @return the current minimum margin in pixels. |
|
342 | */ |
|
343 | public int getMargin() |
|
344 | { |
|
345 | 0 | return this.margin; |
346 | } |
|
347 | ||
348 | /** |
|
349 | * Sets the number of columns for the panel. |
|
350 | * Label/Component pairs will start from the top left |
|
351 | * and fill in to the right before wrapping to the |
|
352 | * next row. The default number of columns is one. |
|
353 | * Note: assumes GridBagLayout. |
|
354 | * @param newColumns the new number of columns. May not be less than one. |
|
355 | */ |
|
356 | public void setColumns( int newColumns ) |
|
357 | { |
|
358 | 0 | if ( newColumns < 1 ) return; // may not be less than one. |
359 | 0 | int oldColumns = this.columns; |
360 | 0 | this.columns = newColumns; |
361 | ||
362 | 0 | if ( listContainer.getLayout() instanceof GridBagLayout ) |
363 | { |
|
364 | 0 | GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); |
365 | 0 | int count = listContainer.getComponentCount(); |
366 | 0 | Component[] components = listContainer.getComponents(); |
367 | 0 | GridBagConstraints[] constraints = new GridBagConstraints[ components.length ]; |
368 | 0 | for ( int i = 0; i < components.length; i++ ) |
369 | { |
|
370 | 0 | constraints[i] = gridBag.getConstraints( components[i] ); |
371 | } |
|
372 | 0 | listContainer.removeAll(); |
373 | 0 | for ( int i = 0; i < components.length; i++ ) |
374 | { |
|
375 | 0 | if ( constraints[i].gridy != 0 ) |
376 | { // ignore first row which is reserved for spacers. |
|
377 | ||
378 | // translate component to new position |
|
379 | // (columns*2 accounts for two grid columns for one "actual" column) |
|
380 | 0 | int index = ( constraints[i].gridy - 1 ) * oldColumns*2 + constraints[i].gridx; |
381 | 0 | constraints[i].gridy = ( index / (newColumns*2) ) + 1; |
382 | 0 | constraints[i].gridx = index % (newColumns*2) ; |
383 | 0 | listContainer.add( components[i], constraints[i] ); |
384 | } |
|
385 | } |
|
386 | 0 | createSpacers(); // replace the spacers |
387 | 0 | updateGaps(); |
388 | } |
|
389 | ||
390 | 0 | this.revalidate(); |
391 | 0 | this.repaint(); |
392 | ||
393 | 0 | } |
394 | ||
395 | /** |
|
396 | * Sets the vertical weight used for determining how to distribute additional |
|
397 | * vertical space in the component. |
|
398 | * @param aComponent Key that exists in the layout. |
|
399 | * @return weighty The weight of the component, or -1.0 if not found. |
|
400 | */ |
|
401 | public double getVerticalWeightForKey( String key ) |
|
402 | { |
|
403 | 0 | Container c = getCompositeComponentForKey( key ); |
404 | 0 | if ( c == null ) return -1.0; |
405 | 0 | if ( ! ( listContainer.getLayout() instanceof GridBagLayout ) ) return -1.0; |
406 | 0 | GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); |
407 | 0 | GridBagConstraints gbc = layout.getConstraints( c ); |
408 | 0 | return gbc.weighty; |
409 | } |
|
410 | ||
411 | /** |
|
412 | * Sets the vertical weight used for determining how to distribute additional |
|
413 | * vertical space in the component. By default, all weights are zero, so each |
|
414 | * component gets its preferred height. If any weights are specified, then |
|
415 | * additional space is allocated to those components proportionately. |
|
416 | * @param aComponent Key that exists in the layout. |
|
417 | * @param weighty The new weight. |
|
418 | */ |
|
419 | public void setVerticalWeightForKey( String key, double weighty ) |
|
420 | { |
|
421 | 0 | Container c = getCompositeComponentForKey( key ); |
422 | 0 | if ( c == null ) return; |
423 | 0 | if ( ! ( listContainer.getLayout() instanceof GridBagLayout ) ) return; |
424 | 0 | GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); |
425 | 0 | GridBagConstraints gbc = layout.getConstraints( c ); |
426 | 0 | gbc.weighty = weighty; |
427 | 0 | layout.setConstraints( c, gbc ); |
428 | // handle adding on-the-fly |
|
429 | 0 | updateGaps(); |
430 | 0 | this.revalidate(); |
431 | 0 | this.repaint(); |
432 | 0 | } |
433 | ||
434 | /** |
|
435 | * Gets the current number of columns. |
|
436 | * @return the current number of columns. |
|
437 | */ |
|
438 | public int getColumns() |
|
439 | { |
|
440 | 0 | return this.columns; |
441 | } |
|
442 | ||
443 | /** |
|
444 | * Appends a label containing a key and the specified component |
|
445 | * to the bottom of the panel. Any registered action listeners |
|
446 | * will receive action events from the component - the key corresponding |
|
447 | * to the component will be used as the action command. |
|
448 | * @param key A string that will be displayed in a label, preferrably unique. |
|
449 | * @param component A component that will be placed next to the label. |
|
450 | * If null, a blank JPanel will be used. |
|
451 | */ |
|
452 | public void addPair( String key, Component component ) |
|
453 | { |
|
454 | 0 | addRow( key, new Component[] { component } ); |
455 | 0 | } |
456 | ||
457 | /** |
|
458 | * Appends a label containing a key and the specified component |
|
459 | * to the bottom of the panel. Any registered action listeners |
|
460 | * will receive action events from the component - the key corresponding |
|
461 | * to the component will be used as the action command. |
|
462 | * @param key A string that will be displayed in a label, preferrably unique. |
|
463 | * @param component A component that will be placed next to the label. |
|
464 | * If null, a blank JPanel will appear. |
|
465 | */ |
|
466 | public void addRow( String key, Component component ) |
|
467 | { |
|
468 | 0 | addRow( key, new Component[] { component } ); |
469 | 0 | } |
470 | ||
471 | /** |
|
472 | * Appends a label containing a key and the specified components |
|
473 | * to the bottom of the panel. Any registered action listeners |
|
474 | * will receive action events from the component - the key corresponding |
|
475 | * to the component will be used as the action command. |
|
476 | * @param key A string that will be displayed in a label, preferrably unique. |
|
477 | * @param components An array of components that will be placed next to the label. |
|
478 | * Any nulls in the list will be replaced with blank JPanels. |
|
479 | */ |
|
480 | public void addRow( |
|
481 | String key, Component[] components ) |
|
482 | { |
|
483 | 0 | addCompositeComponent( key, makeCompositeComponent( key, components ) ); |
484 | 0 | } |
485 | ||
486 | /** |
|
487 | * Appends a label containing a key and the specified components |
|
488 | * to the bottom of the panel. Any registered action listeners |
|
489 | * will receive action events from the components - the key corresponding |
|
490 | * to the component will be used as the action command. |
|
491 | * @param key A string that will be displayed in a label, preferrably unique. |
|
492 | * @param west A component that will appear to the left of the other components, |
|
493 | * as wide as its preferred width and as tall as the tallest of the other components. |
|
494 | * A null will be replaced with a blank JPanel. |
|
495 | * @param center A component that will appear between the other components, |
|
496 | * taking up available space. |
|
497 | * A null will be replaced with a blank JPanel. |
|
498 | * @param east A component that will appear to the right of the other components, |
|
499 | * as wide as its preferred width and as tall as the tallest of the other components. |
|
500 | * A null will be replaced with a blank JPanel. |
|
501 | */ |
|
502 | public void addRow( |
|
503 | String key, Component west, Component center, Component east ) |
|
504 | { |
|
505 | 0 | addCompositeComponent( key, |
506 | 0 | makeCompositeComponent( key, |
507 | 0 | west, center, east ) ); |
508 | 0 | } |
509 | ||
510 | /** |
|
511 | * Appends a label containing a key and the specified components |
|
512 | * to the bottom of the panel. Any registered action listeners |
|
513 | * will receive action events from the components - the key corresponding |
|
514 | * to the component will be used as the action command. |
|
515 | * @param key A string that will be displayed in a label, preferrably unique. |
|
516 | * @param west A component that will appear to the left of the other components, |
|
517 | * as wide as its preferred width and as tall as the tallest of the other components. |
|
518 | * A null will be replaced with a blank JPanel. |
|
519 | * @param north A component that will appear above all the other components, |
|
520 | * as tall as its preferred height and as wide as the info panel itself. |
|
521 | * @param center A component that will appear between the other components, |
|
522 | * taking up available space. A null will be replaced with a blank JPanel. |
|
523 | * @param south A component that will appear below all the other components, |
|
524 | * as tall as its preferred height and as wide as the info panel itself. |
|
525 | * @param east A component that will appear to the right of the other components, |
|
526 | * as wide as its preferred width and as tall as the tallest of the other components. |
|
527 | * A null will be replaced with a blank JPanel. |
|
528 | */ |
|
529 | public void addRow( |
|
530 | String key, Component west, Component north, |
|
531 | Component center, Component south, Component east ) |
|
532 | { |
|
533 | 0 | addCompositeComponent( key, |
534 | 0 | makeCompositeComponent( key, |
535 | 0 | west, north, center, south, east ) ); |
536 | 0 | } |
537 | ||
538 | /** |
|
539 | * Produces a container that contains the specified components, |
|
540 | * using GridLayout. Nulls are ignored. |
|
541 | * This implementation returns a JPanel. |
|
542 | */ |
|
543 | protected Container makeCompositeComponent( |
|
544 | String key, Component[] components ) |
|
545 | { |
|
546 | 0 | JPanel panel = createPanel(); |
547 | 0 | if ( components.length != 0 ) |
548 | { |
|
549 | 0 | panel.setLayout( new GridLayout( 1, components.length, hgap, vgap ) ); |
550 | ||
551 | Component c; |
|
552 | 0 | for ( int i = 0; i < components.length; i++ ) |
553 | { |
|
554 | 0 | c = components[i]; |
555 | 0 | if ( c != null ) |
556 | { |
|
557 | 0 | introspectComponent( c, key ); |
558 | 0 | panel.add( c ); |
559 | } |
|
560 | } |
|
561 | } |
|
562 | 0 | return panel; |
563 | } |
|
564 | ||
565 | /** |
|
566 | * Produces a container that contains the specified components, |
|
567 | * using BorderLayout. Nulls are ignored. |
|
568 | * This implementation returns a JPanel. |
|
569 | */ |
|
570 | protected Container makeCompositeComponent( |
|
571 | String key, Component west, Component center, Component east ) |
|
572 | { |
|
573 | 0 | JPanel panel = createPanel(); |
574 | 0 | panel.setLayout( new BorderLayout( hgap, vgap ) ); |
575 | ||
576 | 0 | if ( west != null ) |
577 | { |
|
578 | 0 | introspectComponent( west, key ); |
579 | 0 | panel.add( west, BorderLayout.WEST ); |
580 | } |
|
581 | ||
582 | 0 | if ( center != null ) |
583 | { |
|
584 | 0 | introspectComponent( center, key ); |
585 | 0 | panel.add( center, BorderLayout.CENTER ); |
586 | } |
|
587 | ||
588 | 0 | if ( east != null ) |
589 | { |
|
590 | 0 | introspectComponent( east, key ); |
591 | 0 | panel.add( east, BorderLayout.EAST ); |
592 | } |
|
593 | ||
594 | 0 | return panel; |
595 | } |
|
596 | ||
597 | /** |
|
598 | * Produces a container that contains the specified components, |
|
599 | * using BorderLayout. Nulls are ignored. |
|
600 | * This implementation returns a JPanel. |
|
601 | */ |
|
602 | protected Container makeCompositeComponent( |
|
603 | String key, Component west, Component north, |
|
604 | Component center, Component south, Component east ) |
|
605 | { |
|
606 | 0 | JPanel panel = createPanel(); |
607 | 0 | panel.setLayout( new BorderLayout( hgap, vgap ) ); |
608 | ||
609 | 0 | if ( west != null ) |
610 | { |
|
611 | 0 | introspectComponent( west, key ); |
612 | 0 | panel.add( west, BorderLayout.WEST ); |
613 | } |
|
614 | ||
615 | 0 | if ( north != null ) |
616 | { |
|
617 | 0 | introspectComponent( north, key ); |
618 | 0 | panel.add( north, BorderLayout.WEST ); |
619 | } |
|
620 | ||
621 | 0 | if ( center != null ) |
622 | { |
|
623 | 0 | introspectComponent( center, key ); |
624 | 0 | panel.add( center, BorderLayout.CENTER ); |
625 | } |
|
626 | ||
627 | 0 | if ( south != null ) |
628 | { |
|
629 | 0 | introspectComponent( south, key ); |
630 | 0 | panel.add( south, BorderLayout.CENTER ); |
631 | } |
|
632 | ||
633 | 0 | if ( east != null ) |
634 | { |
|
635 | 0 | introspectComponent( east, key ); |
636 | 0 | panel.add( east, BorderLayout.EAST ); |
637 | } |
|
638 | ||
639 | 0 | return panel; |
640 | } |
|
641 | ||
642 | /** |
|
643 | * Override to return a specific component to be used |
|
644 | * as a label. This implementation calls createLabel(). |
|
645 | */ |
|
646 | protected Component createLabelForKey( String aKey ) |
|
647 | { |
|
648 | 0 | return createLabel(); |
649 | } |
|
650 | ||
651 | /** |
|
652 | * Provided for backwards compatibility, and called by |
|
653 | * the default implementation of createLabelForKey. |
|
654 | * This implementation returns a JLabel. |
|
655 | */ |
|
656 | protected JLabel createLabel() |
|
657 | { |
|
658 | 0 | return new JLabel(); |
659 | } |
|
660 | ||
661 | /** |
|
662 | * Appends a label containing a key and the specified component |
|
663 | * to the bottom of the panel. Any registered action listeners |
|
664 | * will receive action events from the component - the key corresponding |
|
665 | * to the component will be used as the action command. |
|
666 | * @param key A string that will be displayed in a label, preferrably unique. |
|
667 | * @param component A component that will be placed next to the label. |
|
668 | * If null, a stock JTextField will be used. |
|
669 | */ |
|
670 | protected void addCompositeComponent( String key, Component component ) |
|
671 | { |
|
672 | 0 | if ( key == null ) |
673 | { |
|
674 | 0 | key = ""; |
675 | } |
|
676 | 0 | Component label = createLabelForKey( key ); |
677 | 0 | Component field = component; |
678 | 0 | if ( field == null ) |
679 | { |
|
680 | 0 | field = new JTextField( 15 ); // default to 15 columns |
681 | } |
|
682 | 0 | field.setName( key ); // for association and reference |
683 | 0 | label.setName( key ); // ditto |
684 | 0 | if ( label instanceof JLabel ) |
685 | { |
|
686 | 0 | ((JLabel)label).setHorizontalAlignment( labelAlign ); |
687 | 0 | ((JLabel)label).setLabelFor( field ); // for accessibility |
688 | } |
|
689 | 0 | if ( "".equals( key ) ) |
690 | { |
|
691 | 0 | setText( label, "" ); |
692 | 0 | } |
693 | else |
|
694 | { |
|
695 | 0 | setText( label, prefix + key + postfix ); |
696 | } |
|
697 | 0 | field.setEnabled( this.isEditable ); // was: setEditable |
698 | ||
699 | 0 | GridBagConstraints gbc = new GridBagConstraints(); |
700 | ||
701 | 0 | if ( listContainer.getComponentCount() == 0 ) |
702 | { // we've just initialized or called removeAll |
|
703 | 0 | createSpacers(); |
704 | } |
|
705 | ||
706 | 0 | gbc.gridx = ( fields.size() % this.columns ) * 2; |
707 | 0 | gbc.gridy = ( fields.size() / this.columns ) + 1; // spacer is at index zero |
708 | 0 | gbc.weightx = 0.0; |
709 | 0 | gbc.weighty = 0.0; |
710 | 0 | gbc.anchor = this.labelAnchor; |
711 | 0 | gbc.fill = GridBagConstraints.HORIZONTAL; |
712 | 0 | listContainer.add( label, gbc ); |
713 | ||
714 | 0 | gbc.fill = GridBagConstraints.BOTH; |
715 | 0 | gbc.gridx = gbc.gridx + 1; |
716 | //FIXME: components default to the labelAnchor - should be different? |
|
717 | 0 | gbc.weightx = 1.0; |
718 | 0 | gbc.weighty = 0.0; |
719 | ||
720 | 0 | listContainer.add( field, gbc ); |
721 | ||
722 | 0 | if ( key.equals( HIDDEN ) ) |
723 | { // these components are not to be shown |
|
724 | 0 | setText( label, " " ); |
725 | 0 | field.setVisible( false ); |
726 | } |
|
727 | ||
728 | 0 | fields.add( field ); // using list not map to allow for duplicate keys |
729 | 0 | labels.add( label ); // ditto |
730 | ||
731 | // handle adding on-the-fly |
|
732 | 0 | updateGaps(); |
733 | 0 | this.revalidate(); |
734 | 0 | this.repaint(); |
735 | 0 | } |
736 | ||
737 | /** |
|
738 | * Introspects a component to set the action command and to add the |
|
739 | * InfoPanel to its list of ActionListeners. |
|
740 | * @param aComponent The Component to be introspected. |
|
741 | * @param aKey The action command to be set. |
|
742 | */ |
|
743 | protected void introspectComponent( Component aComponent, String aKey ) |
|
744 | { |
|
745 | // try to set properties of whatever component this might be |
|
746 | try { |
|
747 | 0 | Method [] methods = |
748 | 0 | (Method []) _method_cache.get( aComponent.getClass() ); |
749 | 0 | if (methods == null) { |
750 | 0 | Class componentClass = aComponent.getClass(); |
751 | 0 | BeanInfo info = |
752 | 0 | Introspector.getBeanInfo( componentClass ); |
753 | ||
754 | 0 | MethodDescriptor[] descriptors = |
755 | 0 | info.getMethodDescriptors(); |
756 | 0 | Method setMethod = null; |
757 | 0 | Method addMethod = null; |
758 | 0 | for ( int i = 0; |
759 | 0 | ((setMethod == null || addMethod == null) && |
760 | 0 | i < descriptors.length); |
761 | 0 | i++ ) |
762 | { |
|
763 | 0 | Method m = descriptors[i].getMethod(); |
764 | 0 | String name = m.getName (); |
765 | 0 | if ( setMethod == null && |
766 | 0 | name.equals( "setActionCommand" ) ) |
767 | { |
|
768 | 0 | setMethod = m; |
769 | 0 | } |
770 | 0 | else if ( addMethod == null && |
771 | 0 | name.equals( "addActionListener" ) ) |
772 | { |
|
773 | 0 | addMethod = m; |
774 | } |
|
775 | } |
|
776 | ||
777 | 0 | methods = new Method [] {setMethod, addMethod}; |
778 | 0 | _method_cache.put (componentClass, methods); |
779 | } |
|
780 | 0 | if (methods [0] != null) { |
781 | 0 | methods [0].invoke( aComponent, new Object[] { aKey } ); |
782 | } |
|
783 | 0 | if (methods [1] != null) { |
784 | 0 | methods [1].invoke( aComponent, new Object[] { this } ); |
785 | 0 | listenedToComponents.add( aComponent ); |
786 | } |
|
787 | } |
|
788 | 0 | catch ( Exception exc ) |
789 | { // error occured while introspecting... move along. |
|
790 | 0 | System.out.println( "InfoPanel.introspectComponent: " + exc ); |
791 | 0 | } |
792 | 0 | } |
793 | ||
794 | /** |
|
795 | * Called to populate a label component with the specified text. |
|
796 | * This implementation attempts to call setText(String) on the component. |
|
797 | * Override to customize. |
|
798 | */ |
|
799 | protected void setText( Component c, String text ) |
|
800 | { |
|
801 | try |
|
802 | { |
|
803 | 0 | Method m = c.getClass().getMethod( "setText", new Class[] { String.class } ); |
804 | 0 | if ( m != null ) |
805 | { |
|
806 | 0 | m.invoke( c, new Object[] { text } ); |
807 | } |
|
808 | } |
|
809 | 0 | catch ( Exception exc ) |
810 | { |
|
811 | // no such method: ignore |
|
812 | 0 | } |
813 | 0 | } |
814 | ||
815 | /** |
|
816 | * Creates spacer components on the reserved first grid row |
|
817 | * for each column of labels and fields. |
|
818 | * This allows us to set the margin for those label columns, |
|
819 | * and set the preferred width of the field columns. |
|
820 | * A list containing the field spacers should be assigned to |
|
821 | * the fieldSpacers instance variable. |
|
822 | */ |
|
823 | private void createSpacers() |
|
824 | { |
|
825 | 0 | if ( listContainer.getLayout() instanceof GridBagLayout ) |
826 | { |
|
827 | // insert spacers for labels column |
|
828 | 0 | GridBagLayout gridBag = (GridBagLayout) listContainer.getLayout(); |
829 | 0 | GridBagConstraints constraints = new GridBagConstraints(); |
830 | 0 | constraints.gridy = 0; |
831 | 0 | constraints.fill = GridBagConstraints.HORIZONTAL; |
832 | ||
833 | 0 | fieldSpacers = new LinkedList(); |
834 | Component fieldSpacer; |
|
835 | 0 | for ( int i = 0; i < this.columns; i++ ) |
836 | { |
|
837 | 0 | constraints.gridx = i * 2; |
838 | 0 | listContainer.add( Box.createHorizontalStrut( this.margin ), constraints ); |
839 | ||
840 | 0 | constraints.gridx = i * 2 + 1; |
841 | 0 | fieldSpacer = Box.createHorizontalStrut( 0 ); |
842 | 0 | fieldSpacers.add( fieldSpacer ); |
843 | 0 | listContainer.add( fieldSpacer, constraints ); |
844 | } |
|
845 | } |
|
846 | 0 | } |
847 | ||
848 | /** |
|
849 | * Updates the insets for all components. |
|
850 | */ |
|
851 | protected void updateGaps() |
|
852 | { |
|
853 | 0 | if ( listContainer.getLayout() instanceof GridBagLayout ) |
854 | { |
|
855 | 0 | GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); |
856 | 0 | Component c = null; |
857 | 0 | GridBagConstraints gbc = null; |
858 | 0 | double totalWeightY = 0.0; |
859 | 0 | int count = listContainer.getComponentCount(); |
860 | int i; |
|
861 | 0 | for ( i = 0; i < count; i++ ) |
862 | { |
|
863 | 0 | c = listContainer.getComponent( i ); |
864 | 0 | gbc = layout.getConstraints( c ); |
865 | 0 | totalWeightY += gbc.weighty; |
866 | 0 | if ( (gbc.gridx + 1) % ( this.columns * 2 ) == 0 ) |
867 | { // if last component in row |
|
868 | 0 | gbc.insets = new Insets( 0, 0, this.vgap, 0 ); |
869 | 0 | } |
870 | else |
|
871 | { |
|
872 | 0 | if ( gbc.gridx % 2 == 0 ) |
873 | { // is a label column - NOTE: uses eleven pixels before component, per l&f guide |
|
874 | 0 | gbc.insets = new Insets( 0, 0, this.vgap, 11 ); |
875 | 0 | } |
876 | else |
|
877 | { // is a component column |
|
878 | 0 | if ( gbc.gridy != 0 ) |
879 | { |
|
880 | 0 | if ( c instanceof JPanel ) ((JPanel)c).setPreferredSize( null ); |
881 | 0 | gbc.insets = new Insets( 0, 0, this.vgap, this.hgap ); |
882 | } |
|
883 | } |
|
884 | } |
|
885 | 0 | layout.setConstraints( c, gbc ); |
886 | } |
|
887 | ||
888 | //hack: gridbag clumps components in center if weighty is zero |
|
889 | // if sum of weighty is zero, top-justify the list container |
|
890 | 0 | this.remove( listContainer ); |
891 | 0 | if ( totalWeightY == 0.0 ) |
892 | { |
|
893 | 0 | this.add( listContainer, BorderLayout.NORTH ); |
894 | 0 | } |
895 | else // put list container in center so it will grow |
|
896 | { |
|
897 | 0 | this.add( listContainer, BorderLayout.CENTER ); |
898 | } |
|
899 | } |
|
900 | 0 | } |
901 | ||
902 | /** |
|
903 | * Updates the label alignment. |
|
904 | */ |
|
905 | protected void updateLabels() |
|
906 | { |
|
907 | 0 | if ( listContainer.getLayout() instanceof GridBagLayout ) |
908 | { |
|
909 | 0 | GridBagLayout layout = (GridBagLayout) listContainer.getLayout(); |
910 | 0 | Component c = null; |
911 | 0 | GridBagConstraints gbc = null; |
912 | 0 | Iterator it = labels.iterator(); |
913 | 0 | while ( it.hasNext() ) |
914 | { |
|
915 | 0 | c = (Component) it.next(); |
916 | 0 | if ( c instanceof JLabel ) |
917 | { |
|
918 | 0 | ((JLabel)c).setHorizontalAlignment( labelAlign ); |
919 | } |
|
920 | 0 | gbc = layout.getConstraints( c ); |
921 | 0 | gbc.anchor = this.labelAnchor; |
922 | 0 | layout.setConstraints( c, gbc ); |
923 | 0 | } |
924 | } |
|
925 | 0 | } |
926 | ||
927 | /** |
|
928 | * Convenience method that uses a stock JTextField. |
|
929 | * @param key A string that will be displayed in a label, preferrably unique. |
|
930 | * @param value A string that will be displayed in a textfield. |
|
931 | */ |
|
932 | public void addPair( String key, String value ) |
|
933 | { |
|
934 | 0 | addPair( key, value, null ); |
935 | 0 | } |
936 | ||
937 | /** |
|
938 | * Convenience method that uses the specified JTextField or subclass |
|
939 | * and sets it to the specified value. |
|
940 | * @param key A string that will be displayed in a label, preferrably unique. |
|
941 | * @param value A string that will be displayed in a textfield. |
|
942 | * @param textField A JTextField or subclass that will be used to display the value. |
|
943 | * If null, a stock JTextField will be used. |
|
944 | */ |
|
945 | public void addPair( String key, String value, JTextField textField ) |
|
946 | { |
|
947 | 0 | if ( value == null ) |
948 | { |
|
949 | 0 | value = ""; |
950 | } |
|
951 | 0 | JTextField field = textField; |
952 | 0 | if ( field == null ) |
953 | { |
|
954 | 0 | field = new JTextField( 15 ); // default to 15 columns |
955 | 0 | } |
956 | else |
|
957 | { |
|
958 | 0 | field = textField; |
959 | } |
|
960 | 0 | field.setText( value ); |
961 | ||
962 | 0 | addPair( key, (Component) field ); |
963 | 0 | } |
964 | ||
965 | /** |
|
966 | * Removes all components from the list. Buttons, if any, |
|
967 | * will remain unchanged - use setButtons( null ) to remove |
|
968 | * them. NOTE: does not call super.removeAll(). |
|
969 | */ |
|
970 | public void removeAll() |
|
971 | { |
|
972 | Object component; |
|
973 | Method method; |
|
974 | 0 | Class[] paramClasses = new Class[] { ActionListener.class }; |
975 | 0 | Object[] paramObjects = new Object[] { this }; |
976 | ||
977 | 0 | Iterator iterator = listenedToComponents.iterator(); |
978 | 0 | while ( iterator.hasNext() ) |
979 | { |
|
980 | 0 | component = iterator.next(); |
981 | try |
|
982 | { |
|
983 | 0 | method = component.getClass().getMethod( "removeActionListener", paramClasses ); |
984 | 0 | if ( method != null ) |
985 | { |
|
986 | 0 | method.invoke( component, paramObjects ); |
987 | } |
|
988 | } |
|
989 | 0 | catch ( Exception exception ) |
990 | { |
|
991 | // No removeActionListener() method, move along. |
|
992 | 0 | } |
993 | 0 | } |
994 | ||
995 | 0 | listenedToComponents.clear(); |
996 | ||
997 | 0 | listContainer.removeAll(); |
998 | 0 | fields.clear(); |
999 | 0 | labels.clear(); |
1000 | 0 | this.revalidate(); |
1001 | 0 | this.repaint(); |
1002 | ||
1003 | //FIXME: It is very confusing that this |
|
1004 | // implementation does not call super.removeAll(). |
|
1005 | 0 | } |
1006 | ||
1007 | /** |
|
1008 | * Adds one or buttons to the bottom of the panel with the specified labels |
|
1009 | * from left to right. Any action listeners will receive action events |
|
1010 | * from clicks on these buttons - the supplied label will be used as the action command. |
|
1011 | * @param buttons A string array containing the strings to be used for the button labels |
|
1012 | * and action commands. A null value will remove the button panel. |
|
1013 | * @see ButtonPanel |
|
1014 | */ |
|
1015 | public void setButtons( String[] buttons ) |
|
1016 | { |
|
1017 | 0 | if ( buttonPanel == null ) |
1018 | { |
|
1019 | 0 | buttonPanel = new ButtonPanel(); |
1020 | 0 | buttonPanel.setInsets( new Insets( 6, 0, 0, 0 ) ); |
1021 | // button panel has a 11-pixel top inset |
|
1022 | // and java l&f guide says 17-pixels before command buttons |
|
1023 | 0 | buttonPanel.addActionListener( this ); |
1024 | 0 | this.add( buttonPanel, BorderLayout.SOUTH ); |
1025 | } |
|
1026 | 0 | if ( buttons == null ) |
1027 | { |
|
1028 | 0 | this.remove( buttonPanel ); |
1029 | 0 | buttonPanel = null; |
1030 | 0 | } |
1031 | else |
|
1032 | { |
|
1033 | 0 | buttonPanel.setLabels( buttons ); |
1034 | } |
|
1035 | ||
1036 | 0 | this.revalidate(); |
1037 | 0 | this.repaint(); |
1038 | 0 | } |
1039 | 0 | protected Collection listenedToComponents = new LinkedList(); |
1040 | ||
1041 | /** |
|
1042 | * Retrieves the names of the buttons that are displayed, if any. |
|
1043 | * @return A string array containing the strings used for the button labels |
|
1044 | * and action commands, or null if no buttons have been created. |
|
1045 | * @see ButtonPanel |
|
1046 | */ |
|
1047 | public String[] getButtons() |
|
1048 | { |
|
1049 | 0 | if ( buttonPanel == null ) |
1050 | { |
|
1051 | 0 | return null; // none created |
1052 | } |
|
1053 | ||
1054 | 0 | return buttonPanel.getLabels(); |
1055 | } |
|
1056 | ||
1057 | /** |
|
1058 | * Retrieves the actual button panel, if any. |
|
1059 | * @return A button panel, or null if none has been created. |
|
1060 | * @see ButtonPanel |
|
1061 | */ |
|
1062 | public ButtonPanel getButtonPanel() |
|
1063 | { |
|
1064 | 0 | return buttonPanel; |
1065 | } |
|
1066 | ||
1067 | ||
1068 | /** |
|
1069 | * Sets whether the values displayed in the panel should be editable. Defaults to true. |
|
1070 | * @param isEditable Whether the values should be editable. |
|
1071 | */ |
|
1072 | public void setEditable( boolean isEditable ) |
|
1073 | { |
|
1074 | 0 | this.isEditable = isEditable; |
1075 | 0 | Iterator enumeration = fields.iterator(); |
1076 | 0 | while ( enumeration.hasNext() ) |
1077 | { |
|
1078 | 0 | ( (Component) enumeration.next() ).setEnabled( isEditable ); |
1079 | 0 | } |
1080 | 0 | } |
1081 | ||
1082 | /** |
|
1083 | * Gets whether the values displayed in the panel are editable. |
|
1084 | * @return Whether the values should be editable. |
|
1085 | */ |
|
1086 | public boolean isEditable() |
|
1087 | { |
|
1088 | 0 | return this.isEditable; |
1089 | } |
|
1090 | ||
1091 | /** |
|
1092 | * Sets the field associated with the key to the specified value. |
|
1093 | * Note: If the component does not respond to setText() or setString() |
|
1094 | * or setValue() the value will not be set. JTextFields and the like will work. |
|
1095 | * @param key A string representing the key associated with the field. Nulls are converted to an empty string. |
|
1096 | * @param value A object to be displayed in the specified field. Nulls are converted to an empty string. |
|
1097 | */ |
|
1098 | public void setValueForKey( String key, Object value ) |
|
1099 | { |
|
1100 | 0 | setValueForKey( key, value, 0 ); |
1101 | 0 | } |
1102 | ||
1103 | /** |
|
1104 | * Sets the field associated with the key to the specified value. |
|
1105 | * Note: If the component does not respond to setText() or setString() |
|
1106 | * or setValue() the value will not be set. JTextFields and the like will work. |
|
1107 | * @param key A string representing the key associated with the field. Nulls are converted to an empty string. |
|
1108 | * @param value A object to be displayed in the specified field. Nulls are converted to an empty string. |
|
1109 | */ |
|
1110 | public void setValueForKey( String key, Object value, int index ) |
|
1111 | { |
|
1112 | 0 | if ( key == null ) |
1113 | { |
|
1114 | 0 | key = ""; |
1115 | } |
|
1116 | ||
1117 | 0 | Container field = null; |
1118 | 0 | for ( int i = 0; i < fields.size(); i++ ) |
1119 | { |
|
1120 | 0 | field = (Container) fields.get(i); |
1121 | 0 | if ( key.equals( field.getName() ) ) |
1122 | { |
|
1123 | 0 | setValueForIndex( index, i, value ); |
1124 | 0 | return; |
1125 | } |
|
1126 | } |
|
1127 | // else not found - ignore |
|
1128 | 0 | } |
1129 | ||
1130 | /** |
|
1131 | * Sets the first field at the specified row index to the specified value. |
|
1132 | * Note: If the component does not respond to setText() or setString() |
|
1133 | * or setValue() the value will not be set. JTextFields and the like will work. |
|
1134 | * @param row The row index of the component. |
|
1135 | * @param value A object to be displayed in the specified field. |
|
1136 | * Nulls are converted to an empty string. |
|
1137 | */ |
|
1138 | public void setValueForIndex( int row, Object value ) |
|
1139 | { |
|
1140 | 0 | setValueForIndex( row, 0, value ); |
1141 | 0 | } |
1142 | ||
1143 | /** |
|
1144 | * Sets the field at the specified row index and column index to the specified value. |
|
1145 | * Note: If the component does not respond to setText() or setString() |
|
1146 | * or setValue() the value will not be set. JTextFields and the like will work. |
|
1147 | * @param row The row index of the component. |
|
1148 | * @param index The column index of the component. |
|
1149 | * @param value A object to be displayed in the specified field. |
|
1150 | * Nulls are converted to an empty string. |
|
1151 | */ |
|
1152 | public void setValueForIndex( int row, int col, Object value ) |
|
1153 | { |
|
1154 | 0 | Container field = (Container) fields.get( row ); |
1155 | 0 | Component c = field.getComponent( col ); |
1156 | 0 | setValueForComponent( c, value ); |
1157 | 0 | } |
1158 | ||
1159 | ||
1160 | ||
1161 | /** |
|
1162 | * Sets the value in the field at the specified index. |
|
1163 | * Note: If the component does not respond to setText() or setString() |
|
1164 | * or setValue() this method will return null. JTextFields and the like will work. |
|
1165 | * @param A valid index. |
|
1166 | * @param value A object to be displayed in the specified field. |
|
1167 | */ |
|
1168 | protected void setValueForComponent( Component aComponent, Object value ) |
|
1169 | { |
|
1170 | // try to set a text or string property |
|
1171 | try { |
|
1172 | 0 | BeanInfo info = Introspector.getBeanInfo( aComponent.getClass() ); |
1173 | 0 | MethodDescriptor[] methods = info.getMethodDescriptors(); |
1174 | 0 | for ( int i = 0; i < methods.length; i++ ) |
1175 | { |
|
1176 | 0 | Method m = methods[i].getMethod(); |
1177 | 0 | Class[] paramTypes = m.getParameterTypes(); |
1178 | 0 | if ( paramTypes.length == 1 ) |
1179 | { |
|
1180 | 0 | if ( m.getName().equals( "setText" ) ) |
1181 | { |
|
1182 | 0 | if ( paramTypes[0].getName().equals( String.class.getName() ) ) |
1183 | { |
|
1184 | 0 | m.invoke( aComponent, new Object[] { value } ); |
1185 | } |
|
1186 | } |
|
1187 | 0 | if ( m.getName().equals( "setString" ) ) |
1188 | { |
|
1189 | 0 | if ( paramTypes[0].getName().equals( String.class.getName() ) ) |
1190 | { |
|
1191 | 0 | m.invoke( aComponent, new Object[] { value } ); |
1192 | } |
|
1193 | } |
|
1194 | 0 | if ( m.getName().equals( "setValue" ) ) |
1195 | { |
|
1196 | 0 | if ( paramTypes[0].getName().equals( Object.class.getName() ) ) |
1197 | { |
|
1198 | 0 | m.invoke( aComponent, new Object[] { value } ); |
1199 | } |
|
1200 | } |
|
1201 | } |
|
1202 | } |
|
1203 | } |
|
1204 | 0 | catch ( Exception exc ) |
1205 | { // error occured while introspecting... move along. |
|
1206 | // FIXME: should log error in ErrorManager |
|
1207 | 0 | System.out.println( "InfoPanel.setValueForComponent: " + exc ); |
1208 | 0 | } |
1209 | 0 | } |
1210 | ||
1211 | /** |
|
1212 | * Gets the value in the field at the specified index. |
|
1213 | * Note: If the component does not respond to getText() or getString() |
|
1214 | * or getSelectedItem() this method will return null. JTextFields and the like will work. |
|
1215 | * @param A valid index. |
|
1216 | * @return An object representing the value in the field at the specified index, |
|
1217 | * or null if the component does not have a text property or if the index is out of bounds. |
|
1218 | */ |
|
1219 | public Object getValueForIndex( int anIndex ) |
|
1220 | { |
|
1221 | 0 | return getValueForIndex( anIndex, 0 ); |
1222 | } |
|
1223 | ||
1224 | /** |
|
1225 | * Gets the value in the field at the specified row and column. |
|
1226 | * Note: If the component does not respond to getText() or getString() |
|
1227 | * or getSelectedItem() this method will return null. JTextFields and the like will work. |
|
1228 | * @param A valid index. |
|
1229 | * @return An object representing the value in the field at the specified index, |
|
1230 | * or null if the component does not have a text property or if the index is out of bounds. |
|
1231 | */ |
|
1232 | public Object getValueForIndex( int row, int col ) |
|
1233 | { |
|
1234 | 0 | if ( ( row >= fields.size() ) || ( row < 0 ) ) |
1235 | { // out of bounds |
|
1236 | 0 | return null; |
1237 | } |
|
1238 | ||
1239 | 0 | Container field = (Container) fields.get( row ); |
1240 | 0 | Component c = field.getComponent( col ); |
1241 | 0 | return getValueForComponent( c ); |
1242 | } |
|
1243 | ||
1244 | /** |
|
1245 | * Gets the value in the field associated with the key. |
|
1246 | * Note: If the component does not respond to getText() or getString() |
|
1247 | * or getSelectedItem() this method will return null. JTextFields and the like will work. |
|
1248 | * @param key An string representing the key associated with the field. Nulls are converted to an empty string. |
|
1249 | * @return An object representing the value in the field associated with the key, |
|
1250 | * or null if the key does not exist or if the component does not have a text property. |
|
1251 | */ |
|
1252 | public Object getValueForKey( String key ) |
|
1253 | { |
|
1254 | 0 | return getValueForKey( key, 0 ); |
1255 | } |
|
1256 | ||
1257 | /** |
|
1258 | * Gets the value in the field associated with the key. |
|
1259 | * Note: If the component does not respond to getText() or getString() |
|
1260 | * or getSelectedItem() this method will return null. JTextFields and the like will work. |
|
1261 | * @param key An string representing the key associated with the field. Nulls are converted to an empty string. |
|
1262 | * @return An object representing the value in the field associated with the key, |
|
1263 | * or null if the key does not exist or if the component does not have a text property. |
|
1264 | */ |
|
1265 | public Object getValueForKey( String key, int index ) |
|
1266 | { |
|
1267 | 0 | if ( key == null ) |
1268 | { |
|
1269 | 0 | key = ""; |
1270 | } |
|
1271 | ||
1272 | 0 | Container field = null; |
1273 | 0 | Iterator enumeration = fields.iterator(); |
1274 | 0 | while ( enumeration.hasNext() ) |
1275 | { // finds first value in list with specified key |
|
1276 | 0 | field = (Container) enumeration.next(); |
1277 | 0 | if ( key.equals( field.getName() ) ) |
1278 | { |
|
1279 | 0 | Component c = field.getComponent( index ); |
1280 | 0 | if ( c != null ) |
1281 | { |
|
1282 | 0 | return getValueForComponent( c ); |
1283 | } |
|
1284 | 0 | } |
1285 | } |
|
1286 | // else not found |
|
1287 | 0 | return null; |
1288 | } |
|
1289 | ||
1290 | /** |
|
1291 | * Gets the value in the specified component. |
|
1292 | * Note: If the component does not respond to getText() or getString() |
|
1293 | * or getSelectedItem() this method will return null. JTextFields and the like will work. |
|
1294 | * @param aComponent The specified component. |
|
1295 | * @return An object representing the value in the component. |
|
1296 | * or null if the component does not have a text property. |
|
1297 | */ |
|
1298 | protected Object getValueForComponent( Component aComponent ) |
|
1299 | { |
|
1300 | // try to get a text or string property |
|
1301 | try |
|
1302 | { |
|
1303 | 0 | BeanInfo info = Introspector.getBeanInfo( aComponent.getClass() ); |
1304 | 0 | MethodDescriptor[] methods = info.getMethodDescriptors(); |
1305 | 0 | for ( int i = 0; i < methods.length; i++ ) |
1306 | { |
|
1307 | 0 | Method m = methods[i].getMethod(); |
1308 | 0 | Class[] paramTypes = m.getParameterTypes(); |
1309 | 0 | if ( m.getName().equals( "getText" ) ) |
1310 | { |
|
1311 | 0 | if ( paramTypes.length == 0 ) |
1312 | { |
|
1313 | 0 | return m.invoke( aComponent, new Object[] {} ); |
1314 | } |
|
1315 | } |
|
1316 | 0 | if ( m.getName().equals( "getString" ) ) |
1317 | { |
|
1318 | 0 | if ( paramTypes.length == 0 ) |
1319 | { |
|
1320 | 0 | return m.invoke( aComponent, new Object[] {} ); |
1321 | } |
|
1322 | } |
|
1323 | 0 | if ( m.getName().equals( "getSelectedItem" ) ) |
1324 | { |
|
1325 | 0 | if ( paramTypes.length == 0 ) |
1326 | { |
|
1327 | 0 | return m.invoke( aComponent, new Object[] {} ); |
1328 | } |
|
1329 | } |
|
1330 | // TODO: should also handle variants of setValue() |
|
1331 | } |
|
1332 | } |
|
1333 | 0 | catch ( Exception exc ) |
1334 | { // error occured while introspecting... move along. |
|
1335 | 0 | System.out.println( "InfoPanel.getValueFromComponent: " + exc ); |
1336 | 0 | } |
1337 | ||
1338 | // not found |
|
1339 | 0 | return null; |
1340 | } |
|
1341 | ||
1342 | /** |
|
1343 | * Gets the component associated with the key as a JTextField, for backwards compatibility. |
|
1344 | * @param key A string representing the key associated with the component. Nulls are converted to an empty string. |
|
1345 | * @return A JTextField that contains the value associated with the key, |
|
1346 | * or null if the key does not exist or if the component is not a JTextField. |
|
1347 | */ |
|
1348 | public JTextField getFieldForKey( String key ) |
|
1349 | { |
|
1350 | 0 | Component c = getComponentForKey( key ); |
1351 | 0 | if ( c instanceof JTextField ) |
1352 | { |
|
1353 | 0 | return (JTextField) c; |
1354 | } |
|
1355 | 0 | return null; |
1356 | } |
|
1357 | ||
1358 | /** |
|
1359 | * Gets the component associated with the key. If more than one component is associated |
|
1360 | * with the key, returns the first such component. |
|
1361 | * @param key A string representing the key associated with the component. |
|
1362 | * Nulls are converted to an empty string. |
|
1363 | * @return A component that contains the value associated with the key, |
|
1364 | * or null if the key does not exist. |
|
1365 | */ |
|
1366 | public Component getComponentForKey( String key ) |
|
1367 | { |
|
1368 | 0 | return getComponentForKey( key, 0 ); |
1369 | } |
|
1370 | ||
1371 | /** |
|
1372 | * Gets the component associated with the key and index. |
|
1373 | * @param key A string representing the key associated with the component. |
|
1374 | * Nulls are converted to an empty string. |
|
1375 | * @return A component that contains the value associated with the key, |
|
1376 | * or null if the key does not exist. |
|
1377 | */ |
|
1378 | public Component getComponentForKey( String key, int index ) |
|
1379 | { |
|
1380 | 0 | Container c = getCompositeComponentForKey( key ); |
1381 | 0 | if ( c == null ) return null; |
1382 | 0 | return c.getComponent( index ); |
1383 | } |
|
1384 | ||
1385 | /** |
|
1386 | * Gets the component at the specified row. If more than one component exists |
|
1387 | * on that row, returns the first such component. |
|
1388 | * @return A component or null if the row does not exist. |
|
1389 | */ |
|
1390 | public Object getComponentForIndex( int row ) |
|
1391 | { |
|
1392 | 0 | return getComponentForIndex( row, 0 ); |
1393 | } |
|
1394 | ||
1395 | /** |
|
1396 | * Gets the component at the specified row and column. |
|
1397 | * @return A component or null if the index is out of bounds. |
|
1398 | */ |
|
1399 | public Object getComponentForIndex( int row, int col ) |
|
1400 | { |
|
1401 | 0 | if ( ( row > fields.size() ) || ( row < 0 ) ) |
1402 | { // out of bounds |
|
1403 | 0 | return null; |
1404 | } |
|
1405 | ||
1406 | 0 | Container field = (Container) fields.get( row ); |
1407 | 0 | return field.getComponent( col ); |
1408 | } |
|
1409 | ||
1410 | /** |
|
1411 | * Gets the container associated with the key. |
|
1412 | * @param key A string representing the key associated with the component. |
|
1413 | * Nulls are converted to an empty string. |
|
1414 | * @return A component that contains the value associated with the key, |
|
1415 | * or null if the key does not exist. |
|
1416 | */ |
|
1417 | protected Container getCompositeComponentForKey( String key ) |
|
1418 | { |
|
1419 | 0 | if ( key == null ) |
1420 | { |
|
1421 | 0 | key = ""; |
1422 | } |
|
1423 | ||
1424 | 0 | JPanel field = null; |
1425 | 0 | Iterator enumeration = fields.iterator(); |
1426 | 0 | while ( enumeration.hasNext() ) |
1427 | { // finds first value in list with specified key |
|
1428 | 0 | field = (JPanel) enumeration.next(); |
1429 | 0 | if ( key.equals( field.getName() ) ) |
1430 | { |
|
1431 | 0 | return field; |
1432 | } |
|
1433 | } |
|
1434 | ||
1435 | // else not found |
|
1436 | 0 | return null; |
1437 | } |
|
1438 | ||
1439 | /** |
|
1440 | * Provided for backwards compatibility: calls getLabelComponentForKey. |
|
1441 | * @param key A string representing the key associated with the compoent. |
|
1442 | * Nulls are converted to an empty string. |
|
1443 | * @return Component label object associated with the key, or null if the key does not exist |
|
1444 | * or if the label component is not an instance of JLabel. |
|
1445 | */ |
|
1446 | public JLabel getLabelForKey( String key ) |
|
1447 | { |
|
1448 | 0 | Component result = getLabelComponentForKey( key ); |
1449 | 0 | if ( result instanceof JLabel ) return (JLabel) result; |
1450 | 0 | return null; |
1451 | } |
|
1452 | ||
1453 | /** |
|
1454 | * Get the label component associated with the key. |
|
1455 | * @param key A string representing the key associated with the compoent. |
|
1456 | * Nulls are converted to an empty string. |
|
1457 | * @return Component label object associated with the key, or null if the key does not exist. |
|
1458 | */ |
|
1459 | public Component getLabelComponentForKey( String key ) |
|
1460 | { |
|
1461 | 0 | if ( key == null ) |
1462 | { |
|
1463 | 0 | key = ""; |
1464 | } |
|
1465 | ||
1466 | 0 | Component label = null; |
1467 | 0 | Iterator enumeration = labels.iterator(); |
1468 | 0 | while ( enumeration.hasNext() ) |
1469 | { // finds first value in list with specified key |
|
1470 | 0 | label = (Component) enumeration.next(); |
1471 | 0 | if ( key.equals( label.getName() ) ) |
1472 | { |
|
1473 | 0 | return label; |
1474 | } |
|
1475 | } |
|
1476 | ||
1477 | // else not found |
|
1478 | 0 | return null; |
1479 | } |
|
1480 | ||
1481 | /** |
|
1482 | * Replaces the first component associated with the key. Any value in the existing |
|
1483 | * component will be copied to the new component. |
|
1484 | * @param key A string representing the key to be associated with the component. |
|
1485 | * Nulls are converted to an empty string. |
|
1486 | * @param c A component to be placed next to the label corresponding to the key. |
|
1487 | * Nulls are converted to a JTextField. |
|
1488 | */ |
|
1489 | public void setComponentForKey( String key, Component c ) |
|
1490 | { |
|
1491 | 0 | setComponentForKey( key, c, 0 ); |
1492 | 0 | } |
1493 | ||
1494 | /** |
|
1495 | * Replaces the component associated with the key. Any value in the existing |
|
1496 | * component will be copied to the new component. |
|
1497 | * @param key A string representing the key to be associated with the component. |
|
1498 | * Nulls are converted to an empty string. |
|
1499 | * @param c A component to be placed next to the label corresponding to the key. |
|
1500 | * Nulls are converted to a JTextField. |
|
1501 | */ |
|
1502 | public void setComponentForKey( String key, Component c, int index ) |
|
1503 | { |
|
1504 | 0 | if ( c == null ) |
1505 | { |
|
1506 | 0 | c = new JTextField( 15 ); |
1507 | } |
|
1508 | 0 | if ( key == null ) |
1509 | { |
|
1510 | 0 | key = ""; |
1511 | } |
|
1512 | ||
1513 | 0 | Container container = this.getCompositeComponentForKey( key ); |
1514 | 0 | Component field = container.getComponent( index ); |
1515 | 0 | Object value = this.getValueForKey( key, index ); |
1516 | 0 | if ( field != null ) |
1517 | { |
|
1518 | 0 | container.remove( index ); |
1519 | 0 | container.add( c, index ); |
1520 | 0 | c.setEnabled( this.isEditable ); |
1521 | 0 | introspectComponent( c, key ); |
1522 | 0 | setValueForComponent( c, value ); |
1523 | } |
|
1524 | 0 | } |
1525 | ||
1526 | /** |
|
1527 | * Replaces the first component in the specified row. Any value in the existing |
|
1528 | * component will be copied to the new component. |
|
1529 | * @param row A valid index. |
|
1530 | * @param c A component to be placed next to the label corresponding to the key. |
|
1531 | */ |
|
1532 | public void setComponentForIndex( int row, Component c ) |
|
1533 | { |
|
1534 | 0 | setComponentForIndex( row, 0, c ); |
1535 | 0 | } |
1536 | ||
1537 | /** |
|
1538 | * Replaces the component associated with the key. Any value in the existing |
|
1539 | * component will be copied to the new component. |
|
1540 | * @param row A valid index. |
|
1541 | * @param c A component to be placed next to the label corresponding to the key. |
|
1542 | */ |
|
1543 | public void setComponentForIndex( int row, int col, Component c ) |
|
1544 | { |
|
1545 | 0 | setComponentForKey( getLabels()[row], c, col ); |
1546 | 0 | } |
1547 | ||
1548 | /** |
|
1549 | * Sets the string that appears before each label's text on the panel. |
|
1550 | * @param aString A String to be used as the label prefix. |
|
1551 | */ |
|
1552 | public void setLabelPrefix( String aString ) |
|
1553 | { |
|
1554 | 0 | prefix = aString; |
1555 | 0 | setLabels( getLabels() ); // force refresh |
1556 | 0 | } |
1557 | ||
1558 | /** |
|
1559 | * Gets the string that appears before each label's text on the panel. |
|
1560 | * Defaults to "", an empty string. |
|
1561 | * @return A String that is currently used as the label prefix. |
|
1562 | */ |
|
1563 | public String getLabelPrefix() |
|
1564 | { |
|
1565 | 0 | return prefix; |
1566 | } |
|
1567 | ||
1568 | /** |
|
1569 | * Sets the string that appears after each label's text on the panel. |
|
1570 | * Defaults to ": ", a colon followed by a space. |
|
1571 | * @param aString A String to be used as the label postfix. |
|
1572 | */ |
|
1573 | public void setLabelPostfix( String aString ) |
|
1574 | { |
|
1575 | 0 | postfix = aString; |
1576 | 0 | setLabels( getLabels() ); // force refresh |
1577 | 0 | } |
1578 | ||
1579 | /** |
|
1580 | * Gets the string that appears after each label's text on the panel. |
|
1581 | * @return A String that is currently used as the label postfix. |
|
1582 | */ |
|
1583 | public String getLabelPostfix() |
|
1584 | { |
|
1585 | 0 | return postfix; |
1586 | } |
|
1587 | ||
1588 | /** |
|
1589 | * Adds an action listener to the list that will be |
|
1590 | * notified by events occurring in the panel. |
|
1591 | * @param l An action listener to be notified. |
|
1592 | */ |
|
1593 | public void addActionListener(ActionListener l) |
|
1594 | { |
|
1595 | 0 | actionListener = AWTEventMulticaster.add(actionListener, l); |
1596 | 0 | } |
1597 | /** |
|
1598 | * Removes an action listener from the list that will be |
|
1599 | * notified by events occurring in the panel. |
|
1600 | * @param l An action listener to be removed. |
|
1601 | */ |
|
1602 | public void removeActionListener(ActionListener l) |
|
1603 | { |
|
1604 | 0 | actionListener = AWTEventMulticaster.remove(actionListener, l); |
1605 | 0 | } |
1606 | /** |
|
1607 | * Notifies all registered action listeners of a pending Action Event. |
|
1608 | * @param e An action event to be broadcast. |
|
1609 | */ |
|
1610 | protected void broadcastEvent(ActionEvent e) |
|
1611 | { |
|
1612 | 0 | if (actionListener != null) |
1613 | { |
|
1614 | 0 | actionListener.actionPerformed(e); |
1615 | } |
|
1616 | 0 | } |
1617 | ||
1618 | // interface ActionListener |
|
1619 | ||
1620 | /** |
|
1621 | * Called by buttons on panel and by other components that |
|
1622 | * might be set to broadcast events to this listener. |
|
1623 | * Simply forwards the action event unchanged. |
|
1624 | * @param e An action event to be received. |
|
1625 | */ |
|
1626 | public void actionPerformed(ActionEvent e) |
|
1627 | { |
|
1628 | // if ( e.getSource() instanceof AbstractButton ) |
|
1629 | // { |
|
1630 | 0 | broadcastEvent(e); |
1631 | // } |
|
1632 | 0 | } |
1633 | ||
1634 | /** |
|
1635 | * GridBagLayout allocates weightx only after considering |
|
1636 | * the preferred width of the components in a column. |
|
1637 | * We'd prefer that preferred width wasn't considered, |
|
1638 | * so that the layout worked more like a html-table. |
|
1639 | * GridBagLayout is poorly factored for subclassing, |
|
1640 | * so this code is going to get a little bit ugly. |
|
1641 | * Really, what good is a protected method that returns |
|
1642 | * a private class? Would have liked to just override |
|
1643 | * getLayoutInfo and be done with it. |
|
1644 | */ |
|
1645 | 0 | private class BetterGridBagLayout extends GridBagLayout |
1646 | { |
|
1647 | public Dimension preferredLayoutSize(Container parent) |
|
1648 | { |
|
1649 | 0 | preprocess(); |
1650 | 0 | return super.preferredLayoutSize( parent ); |
1651 | } |
|
1652 | ||
1653 | public Dimension minimumLayoutSize(Container parent) |
|
1654 | { |
|
1655 | 0 | preprocess(); |
1656 | 0 | return super.minimumLayoutSize( parent ); |
1657 | } |
|
1658 | ||
1659 | ||
1660 | public void layoutContainer(Container parent) |
|
1661 | { |
|
1662 | 0 | preprocess(); |
1663 | 0 | super.layoutContainer( parent ); |
1664 | 0 | } |
1665 | ||
1666 | protected void preprocess() |
|
1667 | { |
|
1668 | 0 | if ( fieldSpacers == null ) return; |
1669 | Iterator i; |
|
1670 | ||
1671 | // find the field with the widest preferred size |
|
1672 | Component c; |
|
1673 | 0 | int maxWidth = 0; |
1674 | 0 | i = fields.iterator(); |
1675 | 0 | while ( i.hasNext() ) |
1676 | { |
|
1677 | 0 | c = (Component) i.next(); |
1678 | 0 | maxWidth = Math.max( maxWidth, |
1679 | 0 | Math.max( c.getPreferredSize().width, c.getMinimumSize().width ) ); |
1680 | 0 | } |
1681 | ||
1682 | // set each column's spacers to that preferred size |
|
1683 | 0 | Dimension min = new Dimension( 0, 0 ); |
1684 | 0 | Dimension pref = new Dimension( maxWidth, 0 ); |
1685 | 0 | i = fieldSpacers.iterator(); |
1686 | 0 | while ( i.hasNext() ) |
1687 | { |
|
1688 | 0 | ((Box.Filler)i.next()).changeShape( min, pref, pref ); |
1689 | 0 | } |
1690 | 0 | } |
1691 | } |
|
1692 | } |
|
1693 |