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 }