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 }