001    /*
002     * Copyright 2002-2005 the original author or authors.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.richclient.form;
017    
018    import java.beans.PropertyChangeListener;
019    import java.beans.PropertyChangeSupport;
020    
021    import javax.swing.JComponent;
022    
023    import org.springframework.binding.form.FormModel;
024    import org.springframework.binding.form.HierarchicalFormModel;
025    import org.springframework.binding.value.PropertyChangePublisher;
026    import org.springframework.binding.value.ValueModel;
027    import org.springframework.binding.value.support.ObservableList;
028    import org.springframework.binding.value.support.ValueHolder;
029    import org.springframework.richclient.command.AbstractCommand;
030    import org.springframework.richclient.command.ActionCommand;
031    import org.springframework.richclient.command.CommandGroup;
032    import org.springframework.richclient.util.GuiStandardUtils;
033    import org.springframework.util.Assert;
034    import org.springframework.util.StringUtils;
035    
036    /**
037     * This is an abstract base implementation of the detail side of a Master/Detail form
038     * pair. Derived types need only implement {@link AbstractForm#createFormControl()}.
039     * <p>
040     * The various form commands can be secured by specifying security controller id's for
041     * the command.  Use the {@link AbstractForm#getCommitSecurityControllerId()} and
042     * {@link AbstractForm#getNewFormObjectCommand()}.
043     * 
044     * @author Larry Streepy
045     * 
046     */
047    public abstract class AbstractDetailForm extends AbstractForm implements PropertyChangePublisher {
048    
049        /** State indicating that we are editing no object. */
050        public static final int STATE_CLEAR = 0;
051    
052        /** State indicating that we are editing an existing object. */
053        public static final int STATE_EDIT = 1;
054    
055        /** State indicating that we are creating a new object. */
056        public static final int STATE_CREATE = 2;
057    
058        /** Edit state property name for change notifications. */
059        public static final String EDIT_STATE_PROPERTY = "edit_state";
060    
061        /**
062         * @param pageFormModel
063         */
064        protected AbstractDetailForm(FormModel formModel, String formId, ObservableList editableItemList) {
065            super( formModel, formId );
066    
067            // Install the detail data as our editable object list
068            this.editableItemList = editableItemList;
069            setEditableFormObjects( editableItemList );
070            setEditingFormObjectIndexHolder(indexHolder);
071        }
072    
073        /**
074         * Construct a detail form using the provided parent form model (we will construct our
075         * own form model as a child of the parent model). The provided masterList will be
076         * installed as the set of editable form objects.
077         * 
078         * @param parentFormModel
079         * @param formId
080         * @param childFormObjectHolder
081         * @param masterList ObservableList holding the editable items
082         */
083        public AbstractDetailForm(HierarchicalFormModel parentFormModel, String formId, ValueModel childFormObjectHolder,
084                ObservableList masterList) {
085            super( parentFormModel, formId, childFormObjectHolder );
086            setMasterList( masterList );
087            setEditingFormObjectIndexHolder(indexHolder);
088        }
089    
090        /**
091         * Set the master list model.
092         * 
093         * @param masterList list to use as our master data
094         */
095        protected void setMasterList(ObservableList masterList) {
096            editableItemList = masterList;
097            setEditableFormObjects(editableItemList);
098        }
099    
100        /**
101         * Set the selected object index.
102         * 
103         * @param index of selected item
104         */
105        public void setSelectedIndex(int index) {
106            indexHolder.setValue( new Integer( index ) );
107            setEditState( index < 0 ? STATE_CLEAR : STATE_EDIT );
108            updateControlsForState();
109        }
110    
111        /**
112         * @return index of item being edited
113         */
114        public int getSelectedIndex() {
115            return getEditingFormObjectIndex();
116        }
117    
118        /**
119         * Get the value holder containing the editing index. This allows triggers to monitor
120         * for changes in the index of the object we are editing.
121         * 
122         * @return
123         */
124        public ValueHolder getEditingIndexHolder() {
125            return indexHolder;
126        }
127    
128        /**
129         * Set the form for "create new object" mode. This will set controls as needed for
130         * this edit mode.
131         */
132        public void creatingNewObject() {
133            setEditState( STATE_CREATE );
134            updateControlsForState();
135        }
136    
137        /**
138         * Update our controls based on our state.
139         */
140        protected void updateControlsForState() {
141            boolean showCancel = false;
142            boolean showRevert = false;
143    
144            switch( getEditState() ) {
145            case STATE_CREATE:
146                showCancel = true;
147                showRevert = false;
148                break;
149            case STATE_CLEAR:
150                showCancel = false;
151                showRevert = false;
152                break;
153    
154            case STATE_EDIT:
155                showCancel = false;
156                showRevert = true;
157                break;
158            default:
159                Assert.isTrue( false, "Invalid edit state: " + getEditState() );
160            }
161    
162            getCancelCommand().setVisible( showCancel );
163            getRevertCommand().setVisible( showRevert );
164        }
165    
166        /**
167         * Set the current edit state.
168         * 
169         * @param new edit state
170         */
171        protected void setEditState(int editState) {
172            int oldEditState = this.editState;
173            this.editState = editState;
174            updateControlsForState();
175            firePropertyChange( EDIT_STATE_PROPERTY, oldEditState, this.editState);
176        }
177    
178        /**
179         * Get the current edit state: one of {@link #STATE_CLEAR}, {@link #STATE_CREATE},
180         * or {@link #STATE_EDIT}.
181         * 
182         * @return current state
183         */
184        public int getEditState() {
185            return editState;
186        }
187    
188        /**
189         * Commit this forms data back to the master table. Let our super class do all the
190         * work and then just inform our master table that the value has changed.
191         */
192        public void postCommit(FormModel formModel) {
193            super.postCommit( formModel );
194    
195            // Now set the selected index back to -1 so that the forms properly reset
196            setSelectedIndex( -1 );
197        }
198    
199        protected String getRevertCommandFaceDescriptorId() {
200            return "revert";
201        }
202    
203        protected String getCommitCommandFaceDescriptorId() {
204            return "save";
205        }
206    
207        protected String getCancelCommandFaceDescriptorId() {
208            return "cancelNew";
209        }
210    
211        /**
212         * Override to return null for the new object security controller id.  We do
213         * this because this command is not used directly, so it shouldn't be controlled.
214         * The {@link AbstractMasterForm} is responsible for the real (invocable)
215         * instance of this command.
216         * 
217         * @return null
218         */
219        protected String getNewFormObjectSecurityControllerId() {
220            return null;
221        }
222    
223        /**
224         * Return the configured cancel command, creating it if necessary.
225         * 
226         * @return cancel command
227         */
228        public ActionCommand getCancelCommand() {
229            if( cancelCommand == null ) {
230                cancelCommand = createCancelCommand();
231            }
232            return cancelCommand;
233        }
234    
235        /**
236         * Create the cancel command. This will cancel the "create new" operation and reset
237         * the form.
238         * 
239         * @return cancel command action
240         */
241        protected ActionCommand createCancelCommand() {
242            String commandId = getCancelCommandFaceDescriptorId();
243            if( !StringUtils.hasText( commandId ) ) {
244                return null;
245            }
246            ActionCommand command = new ActionCommand( commandId ) {
247                protected void doExecuteCommand() {
248                    AbstractDetailForm.this.reset();
249                    AbstractDetailForm.this.setEnabled( false );
250                    setEditingNewFormObject( false );
251                    setEditingFormObjectIndexSilently( -1 );
252                    setEditState( STATE_CLEAR );
253                    setFormObject( null );
254                }
255            };
256            return (ActionCommand) getCommandConfigurer().configure( command );
257        }
258    
259        /**
260         * Return a standardized row of command buttons, right-justified and all of the same
261         * size, with OK as the default button, and no mnemonics used, as per the Java Look
262         * and Feel guidelines.
263         */
264        protected JComponent createButtonBar() {
265            commitCommand = getCommitCommand();
266            revertCommand = getRevertCommand();
267            cancelCommand = getCancelCommand();
268    
269            formCommandGroup = CommandGroup.createCommandGroup( null, new AbstractCommand[] {cancelCommand,
270                    revertCommand, commitCommand} );
271            JComponent buttonBar = formCommandGroup.createButtonBar();
272            GuiStandardUtils.attachDialogBorder( buttonBar );
273            return buttonBar;
274        }
275    
276        // =======================================
277        // PropertyChangePublisher implementation
278        // =======================================
279    
280        public final void addPropertyChangeListener(PropertyChangeListener listener) {
281            if( listener == null ) {
282                return;
283            }
284            if( changeSupport == null ) {
285                changeSupport = new PropertyChangeSupport( this );
286            }
287            changeSupport.addPropertyChangeListener( listener );
288        }
289    
290        public final void removePropertyChangeListener(PropertyChangeListener listener) {
291            if( listener == null || changeSupport == null ) {
292                return;
293            }
294            changeSupport.removePropertyChangeListener( listener );
295        }
296    
297        public final void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
298            if( listener == null ) {
299                return;
300            }
301            if( changeSupport == null ) {
302                changeSupport = new PropertyChangeSupport( this );
303            }
304            changeSupport.addPropertyChangeListener( propertyName, listener );
305        }
306    
307        public final void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
308            if( listener == null || changeSupport == null ) {
309                return;
310            }
311            changeSupport.removePropertyChangeListener( propertyName, listener );
312        }
313    
314        protected final void firePropertyChange(String propertyName, int oldValue, int newValue) {
315            if( changeSupport == null ) {
316                return;
317            }
318            changeSupport.firePropertyChange( propertyName, oldValue, newValue );
319        }
320    
321        private ValueHolder indexHolder = new ValueHolder( new Integer( -1 ) );
322        private CommandGroup formCommandGroup;
323        private ActionCommand commitCommand;
324        private ActionCommand revertCommand;
325        private ActionCommand cancelCommand;
326        private ObservableList editableItemList;
327        private int editState = STATE_CLEAR;
328        private transient PropertyChangeSupport changeSupport;
329    
330    }