001    /*
002     * Copyright 2002-2006 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.samples.simple.ui;
017    
018    import ca.odell.glazedlists.EventList;
019    import ca.odell.glazedlists.FilterList;
020    import ca.odell.glazedlists.GlazedLists;
021    import ca.odell.glazedlists.TextFilterator;
022    import ca.odell.glazedlists.swing.TextComponentMatcherEditor;
023    import org.springframework.binding.value.ValueModel;
024    import org.springframework.context.ApplicationEvent;
025    import org.springframework.context.ApplicationListener;
026    import org.springframework.richclient.application.PageComponentContext;
027    import org.springframework.richclient.application.event.LifecycleApplicationEvent;
028    import org.springframework.richclient.application.support.AbstractView;
029    import org.springframework.richclient.command.ActionCommand;
030    import org.springframework.richclient.command.ActionCommandExecutor;
031    import org.springframework.richclient.command.CommandGroup;
032    import org.springframework.richclient.command.GuardedActionCommandExecutor;
033    import org.springframework.richclient.command.support.AbstractActionCommandExecutor;
034    import org.springframework.richclient.command.support.GlobalCommandIds;
035    import org.springframework.richclient.dialog.ConfirmationDialog;
036    import org.springframework.richclient.list.ListSelectionValueModelAdapter;
037    import org.springframework.richclient.list.ListSingleSelectionGuard;
038    import org.springframework.richclient.samples.simple.domain.Contact;
039    import org.springframework.richclient.samples.simple.domain.ContactDataStore;
040    import org.springframework.richclient.widget.table.PropertyColumnTableDescription;
041    import org.springframework.richclient.widget.table.glazedlists.GlazedListTableWidget;
042    import org.springframework.richclient.util.PopupMenuMouseListener;
043    
044    import javax.swing.*;
045    import java.awt.*;
046    import java.awt.event.MouseAdapter;
047    import java.awt.event.MouseEvent;
048    import java.util.Arrays;
049    
050    /**
051     * This class provides the main view of the contacts. It provides a table showing the contact objects and a quick filter
052     * field to narrow down the list of visible contacts. Several commands are tied to the selection of the contacts table
053     * <p/>
054     * By implementing special tag interfaces, this component will be automatically wired in to certain events of interest.
055     * <ul>
056     * <li><b>ApplicationListener</b> - This component will be automatically registered as a listener for application
057     * events.</li>
058     * </ul>
059     *
060     * @author Larry Streepy
061     */
062    public class ContactView extends AbstractView implements ApplicationListener
063    {
064    
065        /**
066         * The object table holding our contacts.
067         */
068        private ContactTable contactTable;
069        private GlazedListTableWidget widget;
070    
071        /**
072         * The data store holding all our contacts.
073         */
074        private ContactDataStore contactDataStore;
075    
076        /**
077         * Handler for the "New Contact" action.
078         */
079        private ActionCommandExecutor newContactExecutor = new NewContactExecutor();
080    
081        /**
082         * Handler for the "Properties" action.
083         */
084        private GuardedActionCommandExecutor propertiesExecutor = new PropertiesExecutor();
085    
086        /**
087         * Handler for the "Delete" action.
088         */
089        private GuardedActionCommandExecutor deleteExecutor = new DeleteExecutor();
090    
091        /**
092         * The text field allowing the user to filter the contents of the contact table.
093         */
094        private JTextField filterField;
095    
096        /**
097         * Default constructor.
098         */
099        public ContactView()
100        {
101        }
102    
103        /**
104         * @return the contactDataStore
105         */
106        protected ContactDataStore getContactDataStore()
107        {
108            return contactDataStore;
109        }
110    
111        /**
112         * @param contactDataStore the contactDataStore to set
113         */
114        public void setContactDataStore(ContactDataStore contactDataStore)
115        {
116            this.contactDataStore = contactDataStore;
117        }
118    
119        /**
120         * Create the control for this view. This method is called by the platform in order to obtain the control to add to
121         * the surrounding window and page.
122         *
123         * @return component holding this view
124         */
125        protected JComponent createControl()
126        {
127            PropertyColumnTableDescription desc = new PropertyColumnTableDescription("contactViewTable", Contact.class);
128            desc.addPropertyColumn("lastName").withMinWidth(150);
129            desc.addPropertyColumn("firstName").withMinWidth(150);
130            desc.addPropertyColumn("address.address1");
131            desc.addPropertyColumn("address.city");
132            desc.addPropertyColumn("address.state");
133            desc.addPropertyColumn("address.zip");
134            widget = new GlazedListTableWidget(Arrays.asList(contactDataStore.getAllContacts()), desc);
135            JPanel table = new JPanel(new BorderLayout());
136            table.add(widget.getListSummaryLabel(), BorderLayout.NORTH);
137            table.add(widget.getComponent(), BorderLayout.CENTER);
138            table.add(widget.getButtonBar(), BorderLayout.SOUTH);
139    
140            CommandGroup popup = new CommandGroup();
141            popup.add((ActionCommand) getWindowCommandManager().getCommand("deleteCommand", ActionCommand.class));
142            popup.addSeparator();
143            popup.add((ActionCommand) getWindowCommandManager().getCommand("propertiesCommand", ActionCommand.class));
144            JPopupMenu popupMenu = popup.createPopupMenu();
145    
146            widget.getTable().addMouseListener(new MouseAdapter()
147            {
148                public void mousePressed(MouseEvent e)
149                {
150                    // If the user right clicks on a row other than the selection,
151                    // then move the selection to the current row
152                    if (e.getButton() == MouseEvent.BUTTON3)
153                    {
154                        int rowUnderMouse = widget.getTable().rowAtPoint(e.getPoint());
155                        if (rowUnderMouse != -1 && !widget.getTable().isRowSelected(rowUnderMouse))
156                        {
157                            // Select the row under the mouse
158                            widget.getTable().getSelectionModel().setSelectionInterval(rowUnderMouse, rowUnderMouse);
159                        }
160                    }
161                }
162    
163                @Override
164                public void mouseClicked(MouseEvent e)
165                {
166                    if (e.getClickCount() >= 2)
167                    {
168                        if (propertiesExecutor.isEnabled())
169                            propertiesExecutor.execute();
170                    }
171                }
172            });
173    
174            widget.getTable().addMouseListener(new PopupMenuMouseListener(popupMenu));
175    
176            ValueModel selectionHolder = new ListSelectionValueModelAdapter(widget.getTable().getSelectionModel());
177            new ListSingleSelectionGuard(selectionHolder, deleteExecutor);
178            new ListSingleSelectionGuard(selectionHolder, propertiesExecutor);
179    
180            JPanel view = new JPanel(new BorderLayout());
181            view.add(widget.getTextFilterField(), BorderLayout.NORTH);
182            view.add(table, BorderLayout.CENTER);
183            return view;
184    
185    
186            //"lastName", "firstName", "address.address1", "address.city", "address.state", "address.zip"
187    
188    //              JPanel filterPanel = new JPanel(new BorderLayout());
189    //              JLabel filterLabel = getComponentFactory().createLabel("nameAddressFilter.label");
190    //              filterPanel.add(filterLabel, BorderLayout.WEST);
191    //
192    //              String tip = getMessage("nameAddressFilter.caption");
193    //              filterField = getComponentFactory().createTextField();
194    //              filterField.setToolTipText(tip);
195    //              filterPanel.add(filterField, BorderLayout.CENTER);
196    //              filterPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
197    //
198    //              contactTable = new ContactTableFactory().createContactTable();
199    //
200    //              JPanel view = new JPanel(new BorderLayout());
201    //              JScrollPane sp = getComponentFactory().createScrollPane(contactTable.getControl());
202    //              view.add(filterPanel, BorderLayout.NORTH);
203    //              view.add(sp, BorderLayout.CENTER);
204    //              return view;
205        }
206    
207        /**
208         * Register the local command executors to be associated with named commands. This is called by the platform prior
209         * to making the view visible.
210         */
211        protected void registerLocalCommandExecutors(PageComponentContext context)
212        {
213            context.register("newContactCommand", newContactExecutor);
214            context.register(GlobalCommandIds.PROPERTIES, propertiesExecutor);
215            context.register(GlobalCommandIds.DELETE, deleteExecutor);
216        }
217    
218        /**
219         * Prepare the table holding all the Contact objects. This table provides pretty much all the functional operations
220         * within this view. Prior to calling this method the setContactTable(ContactTable) will have already been
221         * called as part of the context bean creation.
222         */
223        private class ContactTableFactory
224        {
225            public ContactTable createContactTable()
226            {
227                ContactTable contactTable = new ContactTable(contactDataStore);
228    
229                // Get the table instance from our factory
230                // Make a double click invoke the properties dialog and plugin the
231                // context menu
232                contactTable.setDoubleClickHandler(propertiesExecutor);
233    
234                // Construct and install our filtering list. This filter will allow the user
235                // to simply type data into the txtFilter (JTextField). With the configuration
236                // setup below, the text entered by the user will be matched against the values
237                // in the lastName and address.address1 properties of the contacts in the table.
238                // The GlazedLists filtered lists is used to accomplish this.
239                EventList baseList = contactTable.getBaseEventList();
240                TextFilterator filterator = GlazedLists.textFilterator(new String[]{"lastName", "address.address1"});
241                FilterList filterList = new FilterList(baseList, new TextComponentMatcherEditor(filterField, filterator));
242    
243                // Install the fully constructed (layered) list into the table
244                contactTable.setFinalEventList(filterList);
245    
246                // Install the popup menu
247                CommandGroup popup = new CommandGroup();
248                popup.add((ActionCommand) getWindowCommandManager().getCommand("deleteCommand", ActionCommand.class));
249                popup.addSeparator();
250                popup.add((ActionCommand) getWindowCommandManager().getCommand("propertiesCommand", ActionCommand.class));
251                contactTable.setPopupCommandGroup(popup);
252    
253                // Register to get notified when the filtered list changes
254                contactTable.setStatusBar(getStatusBar());
255    
256                // Ensure our commands are only active when something is selected.
257                // These guard objects operate by inspecting a list selection model
258                // (held within a ValueModel) and then either enabling or disabling the
259                // guarded object (our executors) based on the configured criteria.
260                // This configuration greatly simplifies the interaction between commands
261                // that require a selection on which to operate.
262                ValueModel selectionHolder = new ListSelectionValueModelAdapter(contactTable.getSelectionModel());
263                new ListSingleSelectionGuard(selectionHolder, deleteExecutor);
264                new ListSingleSelectionGuard(selectionHolder, propertiesExecutor);
265    
266                return contactTable;
267            }
268        }
269    
270        /**
271         * Private inner class to create a new contact.
272         */
273        private class NewContactExecutor implements ActionCommandExecutor
274        {
275            public void execute()
276            {
277                new ContactPropertiesDialog(getContactDataStore()).showDialog();
278            }
279        }
280    
281        /**
282         * Private inner class to handle the properties form display.
283         */
284        private class PropertiesExecutor extends AbstractActionCommandExecutor
285        {
286            public void execute()
287            {
288                for (Object selected : widget.getSelectedRows())
289                    new ContactPropertiesDialog((Contact) selected, getContactDataStore()).showDialog();
290            }
291        }
292    
293        /**
294         * Private class to handle the delete command. Note that due to the configuration above, this executor is only
295         * enabled when exactly one contact is selected in the table. Thus, we don't have to protect against being executed
296         * with an incorrect state.
297         */
298        private class DeleteExecutor extends AbstractActionCommandExecutor
299        {
300            public void execute()
301            {
302                String title = getMessage("contact.confirmDelete.title");
303                String message = getMessage("contact.confirmDelete.message");
304                ConfirmationDialog dlg = new ConfirmationDialog(title, message)
305                {
306                    protected void onConfirm()
307                    {
308                        for (Object selected : widget.getSelectedRows())
309                        {
310                            Contact contact = (Contact) selected;
311                            // Delete the object from the persistent store.
312                            getContactDataStore().delete(contact);
313                            // And notify the rest of the application of the change
314                            getApplicationContext().publishEvent(
315                                    new LifecycleApplicationEvent(LifecycleApplicationEvent.DELETED, contact));
316                        }
317                    }
318                };
319                dlg.showDialog();
320            }
321        }
322    
323        /**
324         * Handle an application event. This will notify us of object adds, deletes, and modifications. Forward to our
325         * object table for handling.
326         *
327         * @param e event to process
328         */
329        public void onApplicationEvent(ApplicationEvent e)
330        {
331            contactTable.onApplicationEvent(e);
332        }
333    }