Source code: org/progeeks/meta/swing/editor/ListEditor.java
1 /*
2 * $Id: ListEditor.java,v 1.10 2003/09/24 07:16:01 pspeed Exp $
3 *
4 * Copyright (c) 2001-2002, Paul Speed
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1) Redistributions of source code must retain the above copyright notice,
12 * this list of conditions and the following disclaimer.
13 * 2) Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3) Neither the names "Progeeks", "Meta-JB", nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 package org.progeeks.meta.swing.editor;
34
35 import java.awt.*;
36 import java.awt.event.*;
37 import java.beans.*;
38 import javax.swing.*;
39 import javax.swing.border.*;
40 import javax.swing.event.*;
41
42 import org.progeeks.meta.*;
43 import org.progeeks.meta.format.*;
44 import org.progeeks.meta.swing.*;
45 import org.progeeks.util.beans.*;
46
47 /**
48 * An editor implementation for a List of values otherwise editable
49 * with another registered editor.
50 *
51 * @version $Revision: 1.10 $
52 * @author Paul Speed
53 */
54 public class ListEditor extends AbstractPropertyEditor
55 implements MetaPropertyRenderer
56 {
57 private FactoryRegistry factories;
58
59 private EditorDialog popup;
60 private JPanel component;
61 private JList list;
62 private JScrollPane scrollPane;
63 private JButton add;
64 private JButton edit;
65 private JButton remove;
66 private ListMutator listMutator;
67 private ListModelAdapter model;
68 private PropertyType elementType;
69 private PropertyFormat elementFormat;
70 private EditorListener editListener = new EditorListener();
71 private boolean readOnly = false;
72
73 public ListEditor( FactoryRegistry factories )
74 {
75 this( factories, false );
76 }
77
78 public ListEditor( FactoryRegistry factories, boolean readOnly )
79 {
80 this.factories = factories;
81 this.readOnly = readOnly;
82
83 model = new ListModelAdapter();
84 list = new JList( model ); //new String[] { "test 1", "test 2" } );
85 list.setCellRenderer( new ItemRenderer() );
86 scrollPane = new JScrollPane( list );
87 list.setVisibleRowCount( 5 );
88 list.addListSelectionListener( editListener );
89
90 add = new JButton( "Add" );
91 add.addActionListener( editListener );
92 edit = new JButton( "Edit" );
93 edit.addActionListener( editListener );
94 remove = new JButton( "Remove" );
95 remove.addActionListener( editListener );
96
97 component = new JPanel( new GridBagLayout() );
98 GridBagConstraints gbc = new GridBagConstraints();
99 gbc.gridx = 0;
100 gbc.gridy = 0;
101 gbc.weightx = 1.0;
102 gbc.weighty = 1.0;
103 gbc.gridwidth = 3;
104 gbc.fill = GridBagConstraints.BOTH;
105
106 component.add( scrollPane, gbc );
107
108 gbc.gridy = 1;
109 gbc.weightx = 1.0;
110 gbc.weighty = 0;
111 gbc.gridwidth = 1;
112 component.add( add, gbc );
113
114 gbc.gridx++;
115 component.add( edit, gbc );
116
117 gbc.gridx++;
118 component.add( remove, gbc );
119 }
120
121 /**
122 * Returns true if the dialog should have an edit button
123 * if the property mutator supports it. This allows subclasses
124 * to specifically override.
125 */
126 public boolean hasEditButton()
127 {
128 return( true );
129 }
130
131 /**
132 * Returns true if the dialog should have a remove button
133 * if the property mutator supports it. This allows subclasses
134 * to specifically override.
135 */
136 public boolean hasRemoveButton()
137 {
138 return( true );
139 }
140
141 /**
142 * Returns true if the dialog should have an add button
143 * if the property mutator supports it. This allows subclasses
144 * to specifically override.
145 */
146 public boolean hasAddButton()
147 {
148 return( true );
149 }
150
151 /**
152 * Intercepted to configure the list panel to most appropriately
153 * modify the list.
154 */
155 public void setPropertyMutator( PropertyMutator mutator )
156 {
157 super.setPropertyMutator( mutator );
158
159 listMutator = (ListMutator)mutator;
160 ContainerPropertyInfo info = (ContainerPropertyInfo)listMutator.getPropertyInfo();
161 ListPropertyType type = (ListPropertyType)info.getPropertyType();
162 elementType = type.getValueType();
163 elementFormat = factories.getFormatRegistry().getFormat( elementType );
164
165 if( readOnly )
166 {
167 edit.setVisible( false );
168 add.setVisible( false );
169 remove.setVisible( false );
170 }
171 else
172 {
173 edit.setVisible( info.isMutable() && hasEditButton() );
174 add.setVisible( info.isResizable() && hasAddButton() );
175 remove.setVisible( info.isResizable() && hasRemoveButton() );
176 }
177
178 edit.setEnabled( false );
179 remove.setEnabled( false );
180
181 model.setListMutator( listMutator );
182 }
183
184 /**
185 * Returns false since lists should be full-width controls.
186 */
187 public boolean isSingleColumn()
188 {
189 return( false );
190 }
191
192 /**
193 * Returns the component that allows modification of the
194 * associated property mutator.
195 */
196 public Component getUIComponent()
197 {
198 return( component );
199 }
200
201 /**
202 * Implemented by subclasses to release any component-related
203 * resources.
204 */
205 protected void releaseComponent()
206 {
207 list.removeListSelectionListener( editListener );
208 add.removeActionListener( editListener );
209 edit.removeActionListener( editListener );
210 remove.removeActionListener( editListener );
211 }
212
213 /**
214 * Called to set the current value displayed in the component.
215 */
216 protected void setComponentValue( Object value )
217 {
218 // Only called for a full list change.
219
220 // FIXME implement this or verify that it's not needed
221 }
222
223 /**
224 * Called to set the component value to a default state.
225 * The default implementation calls setComponentValue(null).
226 */
227 protected void resetComponentValue()
228 {
229 // FIXME Implement this
230 }
231
232 /**
233 * Called when the value contained in the mutator changes.
234 * The default implementation ignores property changes if
235 * the value is the same as the current cached value either
236 * by reference or by .equals().
237 */
238 protected void propertyChanged( PropertyChangeEvent event )
239 {
240 if( !(event instanceof ListPropertyChangeEvent) )
241 {
242 // Cause a full list update
243 model.setListMutator( listMutator );
244 return;
245 }
246
247 ListPropertyChangeEvent listEvent = (ListPropertyChangeEvent)event;
248 switch( listEvent.getType() )
249 {
250 case ListPropertyChangeEvent.INSERT:
251 model.intervalAdded( listEvent.getFirstIndex(), listEvent.getLastIndex() );
252 break;
253 case ListPropertyChangeEvent.UPDATE:
254 model.intervalChanged( listEvent.getFirstIndex(), listEvent.getLastIndex() );
255 break;
256 case ListPropertyChangeEvent.DELETE:
257 model.intervalRemoved( listEvent.getFirstIndex(), listEvent.getLastIndex() );
258 break;
259 }
260 }
261
262 protected void addItem()
263 {
264 if( popup == null )
265 popup = new EditorDialog();
266 popup.setLocationRelativeTo( component );
267
268 Object newValue = null;
269 if( elementType instanceof MetaClassPropertyType )
270 {
271 MetaClass metaClass = ((MetaClassPropertyType)elementType).getMetaClass();
272 MetaObjectFactory factory = listMutator.getParentObject().getMetaKit().getMetaObjectFactory();
273 newValue = factory.createMetaObject( metaClass );
274 }
275 else
276 {
277 // We hope that the editor knows what to
278 // do with null values.
279 }
280 // Create a new value by popping up an editor with
281 // a fake PropertyMutator containing the appropriate
282 // type info.
283 popup.popupEdit( "Add List Value", "New Value", newValue, elementType, factories );
284
285 if( !popup.isCanceled()
286 && (popup.hasChanged() || newValue != null) ) // A hack until EditorDialog detects changes
287 // to meta-objects.
288 {
289 Object value = popup.getValue();
290 if( value != null )
291 listMutator.add( value );
292
293 // FIXME: This is a hack because normal beans don't know
294 // when their values have changed. I need to think
295 // about this some more because it's a fairly serious
296 // issue. It may be that they only get an opportunity
297 // to receive notifications if they implement add/remove/set
298 // methods.
299 listMutator.setValue( listMutator.getValue() );
300 }
301 }
302
303 protected void editItem( int i )
304 {
305 if( popup == null )
306 popup = new EditorDialog();
307 popup.setLocationRelativeTo( component );
308
309 popup.popupEdit( "Edit List Value", "Value", listMutator.get(i), elementType, factories );
310
311 if( popup.hasChanged() )
312 {
313 Object value = popup.getValue();
314 listMutator.set( i, value );
315
316 // FIXME: This is a hack because normal beans don't know
317 // when their values have changed. I need to think
318 // about this some more because it's a fairly serious
319 // issue. It may be that they only get an opportunity
320 // to receive notifications if they implement add/remove/set
321 // methods.
322 listMutator.setValue( listMutator.getValue() );
323 }
324 }
325
326 protected void removeItem( int i )
327 {
328 listMutator.remove( i );
329
330 // FIXME: This is a hack because normal beans don't know
331 // when their values have changed. I need to think
332 // about this some more because it's a fairly serious
333 // issue. It may be that they only get an opportunity
334 // to receive notifications if they implement add/remove/set
335 // methods.
336 listMutator.setValue( listMutator.getValue() );
337 }
338
339 private class ItemRenderer extends DefaultListCellRenderer
340 {
341 public Component getListCellRendererComponent( JList list,
342 Object value,
343 int index,
344 boolean isSelected,
345 boolean cellHasFocus )
346 {
347 // Format the value as needed
348 value = elementFormat.format( value );
349 return( super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus ) );
350 }
351 }
352
353 private class EditorListener implements ActionListener, ListSelectionListener
354 {
355 public void valueChanged( ListSelectionEvent event )
356 {
357 edit.setEnabled( true );
358 remove.setEnabled( true );
359 }
360
361 public void actionPerformed( ActionEvent event )
362 {
363 if( event.getSource() == add )
364 {
365 addItem();
366 }
367 else if( event.getSource() == edit )
368 {
369 int i = list.getLeadSelectionIndex();
370 if( i < 0 )
371 i = listMutator.size();
372
373 editItem( i );
374 }
375 else if( event.getSource() == remove )
376 {
377 removeItem( list.getLeadSelectionIndex() );
378 }
379 }
380 }
381
382 }
383
384