001 package org.springframework.richclient.widget.editor; 002 003 import com.jgoodies.forms.factories.Borders; 004 import com.jgoodies.forms.factories.FormFactory; 005 import com.jgoodies.forms.layout.*; 006 import org.apache.commons.lang.StringUtils; 007 import org.apache.commons.logging.Log; 008 import org.apache.commons.logging.LogFactory; 009 import org.jdesktop.swingx.JXTable; 010 import org.springframework.binding.form.FormModel; 011 import org.springframework.binding.form.NewFormObjectAware; 012 import org.springframework.binding.validation.support.DefaultValidationResultsModel; 013 import org.springframework.richclient.application.ApplicationServicesLocator; 014 import org.springframework.richclient.application.DefaultButtonFocusListener; 015 import org.springframework.richclient.command.*; 016 import org.springframework.richclient.command.config.CommandConfigurer; 017 import org.springframework.richclient.components.Focussable; 018 import org.springframework.richclient.dialog.Messagable; 019 import org.springframework.richclient.form.*; 020 import org.springframework.richclient.util.RcpSupport; 021 import org.springframework.richclient.widget.AbstractTitledWidget; 022 import org.springframework.richclient.widget.SelectionWidget; 023 import org.springframework.richclient.widget.TitledWidget; 024 import org.springframework.richclient.widget.Widget; 025 import org.springframework.richclient.widget.editor.provider.DataProviderEventSource; 026 import org.springframework.richclient.widget.editor.provider.DataProviderListener; 027 import org.springframework.richclient.widget.table.TableWidget; 028 029 import javax.swing.*; 030 import javax.swing.border.Border; 031 import javax.swing.event.ChangeEvent; 032 import javax.swing.event.ChangeListener; 033 import javax.swing.table.TableCellRenderer; 034 import javax.swing.table.TableColumn; 035 import javax.swing.text.JTextComponent; 036 import java.awt.*; 037 import java.awt.datatransfer.StringSelection; 038 import java.awt.event.KeyEvent; 039 import java.awt.event.MouseAdapter; 040 import java.awt.event.MouseEvent; 041 import java.beans.PropertyChangeEvent; 042 import java.beans.PropertyChangeListener; 043 import java.util.*; 044 import java.util.List; 045 046 /** 047 * AbstractDataEditorWidget implements a basic editor screen, based on 3 parts: 048 * <p/> 049 * <ol> 050 * <li>a table with sortable columns and a local quick search</li> 051 * <li>a filter to reduce the dataset</li> 052 * <li>a detail section for the details of 1 entity</li> 053 * </ol> 054 */ 055 public abstract class AbstractDataEditorWidget extends AbstractTitledWidget 056 implements 057 TitledWidget, 058 SelectionWidget 059 { 060 061 /** 062 * Log facility. 063 */ 064 private static final Log log = LogFactory.getLog(AbstractDataEditorWidget.class); 065 066 private static final String QUICKADD = "quickAdd"; 067 068 protected static final String UPDATE_COMMAND_ID = "update"; 069 070 protected static final String CREATE_COMMAND_ID = "create"; 071 072 private static final String TOGGLE_DETAIL_COMMAND_ID = "opendetail"; 073 074 private static final String REMOVE_CONTINUE_AFTER_ERROR = "remove.continue_after_error"; 075 076 private static final String REMOVE_CONFIRMATION_ID = "remove.confirmation"; 077 078 private static final String DBLCLICKSELECTS = "dblclick_for_edit"; 079 080 public static final String UNSAVEDCHANGES_WARNING_ID = "unsavedchanges.warning"; 081 082 public static final String UNSAVEDCHANGES_UNCOMMITTABLE_WARNING_ID = "unsavedchanges.uncommittable.warning"; 083 084 public static final RowSpec FILL_ROW_SPEC = new RowSpec(RowSpec.FILL, Sizes.DEFAULT, 085 FormSpec.DEFAULT_GROW); 086 087 public static final ColumnSpec FILL_NOGROW_COLUMN_SPEC = new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, 088 FormSpec.NO_GROW); 089 090 public static final ColumnSpec FILL_COLUMN_SPEC = new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, 091 FormSpec.DEFAULT_GROW); 092 093 public static final boolean ON = true; 094 095 public static final boolean OFF = false; 096 097 private boolean selectMode = OFF; 098 099 private boolean multipleSelectionInSelectMode = OFF; 100 101 private String searchString; 102 103 private Object selectedRowObject; 104 105 private final CellConstraints cc = new CellConstraints(); 106 107 private SplitPaneExpansionToggleCommand toggleDetailCommand; 108 109 private SplitPaneExpansionToggleCommand toggleFilterCommand; 110 111 private ActionCommand editRowCommand; 112 113 private ActionCommand addRowCommand; 114 115 private ActionCommand cloneRowCommand; 116 117 private ActionCommand removeRowsCommand; 118 119 private ActionCommand executeFilterCommand; 120 121 private ActionCommand refreshCommand; 122 123 private ActionCommand clearFilterCommand; 124 125 private ActionCommand emptyFilterCommand; 126 127 private ActionCommand selectionCommand; 128 129 private ActionCommand printCommand; 130 131 private ActionCommand updateCommand; 132 133 private ActionCommand createRowCommand; 134 135 private CardLayout saveUpdateSwitcher; 136 137 private JPanel saveUpdatePanel; 138 139 private JTextField textFilterField; 140 141 private Map dataProviderSources = null; 142 143 private final CommandConfigurer commandConfigurer = (CommandConfigurer) ApplicationServicesLocator 144 .services().getService(CommandConfigurer.class); 145 146 /** 147 * Observer listening to changes in the table selection. 148 */ 149 protected Observer tableSelectionObserver; 150 151 /** 152 * Default constructor will initialise the necessary listeners/observers. 153 */ 154 public AbstractDataEditorWidget() 155 { 156 tableSelectionObserver = createListSelectionObserver(); 157 } 158 159 /** 160 * Creates the observer that listens to selections in the listView. Normally forwards the selection to the 161 * detailForm. 162 */ 163 protected Observer createListSelectionObserver() 164 { 165 return new ListSelectionObserver(); 166 } 167 168 /** 169 * Set the select mode of this dataEditor. 170 */ 171 public void setSelectMode(boolean selectMode) 172 { 173 this.selectMode = selectMode; 174 } 175 176 public boolean isSelectMode() 177 { 178 return selectMode; 179 } 180 181 /** 182 * Set the local text filter field value 183 * 184 * @param queryString filterText. 185 */ 186 public void setSearchString(String queryString) 187 { 188 this.searchString = queryString; 189 if (this.textFilterField != null) 190 { 191 this.textFilterField.setText(this.searchString); 192 } 193 } 194 195 public String getSearchString() 196 { 197 return searchString; 198 } 199 200 public Object getSelectedRowObject() 201 { 202 return selectedRowObject; 203 } 204 205 public void setSelectedRowObject(Object selectedObject) 206 { 207 getTableWidget().selectRowObject(selectedObject, null); 208 } 209 210 public abstract Object setSelectedSearch(Object searchCriteria); 211 212 /** 213 * {@inheritDoc} 214 */ 215 @Override 216 public JComponent createWidgetContent() 217 { 218 return createDataEditorWidget(); 219 } 220 221 protected final JComponent createDataEditorWidget() 222 { 223 JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 224 splitPane.setBorder(BorderFactory.createEmptyBorder()); 225 if (isSelectMode() == ON) 226 { 227 DefaultButtonFocusListener.setDefaultButton(getTableWidget().getComponent(), getSelectCommand()); 228 getTableWidget().getTable().getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "none"); 229 } 230 231 if ((isSelectMode() == ON) && (selectedRowObject == null)) 232 { 233 splitPane.setDividerLocation(Integer.MAX_VALUE); 234 this.toggleDetailCommand = new SplitPaneExpansionToggleCommand(TOGGLE_DETAIL_COMMAND_ID, 235 splitPane, true); 236 } 237 else 238 { 239 splitPane.setDividerLocation(-1); 240 this.toggleDetailCommand = new SplitPaneExpansionToggleCommand(TOGGLE_DETAIL_COMMAND_ID, 241 splitPane, false); 242 } 243 244 splitPane.setLastDividerLocation(-1); 245 splitPane.setOneTouchExpandable(true); 246 splitPane.setResizeWeight(getTableResizeWeight()); 247 248 splitPane.setLeftComponent(getTableFilterPanel()); 249 splitPane.setRightComponent(getDetailPanel()); 250 251 getDetailForm().setEnabled(getTableWidget().hasSelection()); 252 253 createAddRowCommand(); 254 createRemoveRowCommand(); 255 createCloneRowCommand(); 256 257 return splitPane; 258 } 259 260 protected double getTableResizeWeight() 261 { 262 return 0.75; 263 } 264 265 private JComponent getControlPanel() 266 { 267 AbstractCommand localToggleDetailCommand = getToggleDetailCommand(); 268 AbstractCommand helpCommand = getHelpCommand(); 269 AbstractCommand[] commands = getControlCommands(); 270 271 ColumnSpec[] columnSpecs = new ColumnSpec[1 + (commands.length + 1) * 2]; 272 columnSpecs[0] = FILL_NOGROW_COLUMN_SPEC;// open-detail btn 273 columnSpecs[1] = FormFactory.UNRELATED_GAP_COLSPEC; // gap 274 columnSpecs[2] = FILL_NOGROW_COLUMN_SPEC;// help btn 275 columnSpecs[3] = FILL_COLUMN_SPEC; // glue space 276 277 for (int i = 0; i < commands.length; i++) 278 { 279 // print | select-and-close | cancel 280 columnSpecs[4 + (i * 2)] = FILL_NOGROW_COLUMN_SPEC; 281 if (i != commands.length - 1) 282 { 283 // gap, but not after last 284 columnSpecs[5 + (i * 2)] = FormFactory.UNRELATED_GAP_COLSPEC; 285 } 286 } 287 RowSpec[] rowSpecs = new RowSpec[]{FILL_ROW_SPEC}; 288 FormLayout formLayout = new FormLayout(columnSpecs, rowSpecs); 289 JPanel buttonPanel = new JPanel(formLayout); 290 291 buttonPanel.add(localToggleDetailCommand.createButton(), cc.xy(1, 1)); 292 293 buttonPanel.add(helpCommand.createButton(), cc.xy(3, 1)); 294 295 for (int i = 0; i < commands.length; i++) 296 { 297 buttonPanel.add(commands[i].createButton(), cc.xy(5 + i * 2, 1)); 298 } 299 300 return buttonPanel; 301 } 302 303 /** 304 * {@inheritDoc} 305 */ 306 @Override 307 public List<AbstractCommand> getCommands() 308 { 309 return Arrays.asList(getToggleDetailCommand()); 310 } 311 312 protected JComponent getDetailPanel() 313 { 314 ColumnSpec[] columnSpecs = new ColumnSpec[]{FILL_COLUMN_SPEC}; 315 RowSpec[] rowSpecs = new RowSpec[]{FormFactory.LINE_GAP_ROWSPEC, // gap 316 FormFactory.DEFAULT_ROWSPEC, // buttons for detailpanel 317 FormFactory.LINE_GAP_ROWSPEC, // gap 318 FILL_ROW_SPEC 319 // detailpanel itself (form) 320 }; 321 JPanel detailPanel = new JPanel(new FormLayout(columnSpecs, rowSpecs)); 322 323 Form detailForm = getDetailForm(); 324 newSingleLineResultsReporter(this); 325 detailPanel.add(getDetailControlPanel(), cc.xy(1, 2)); 326 detailPanel.add(detailForm.getControl(), cc.xy(1, 4)); 327 328 // force form readonly if adding & updating is not supported 329 if (!isAddRowSupported() && !isUpdateRowSupported()) 330 { 331 detailForm.getFormModel().setReadOnly(true); 332 } 333 334 return detailPanel; 335 } 336 337 private JComponent getDetailControlPanel() 338 { 339 ColumnSpec[] columnSpecs = new ColumnSpec[]{FILL_NOGROW_COLUMN_SPEC, // edit buttons 340 FILL_COLUMN_SPEC, // glue 341 FILL_NOGROW_COLUMN_SPEC, // navigation buttons 342 FILL_COLUMN_SPEC, // glue 343 new ColumnSpec(ColumnSpec.RIGHT, Sizes.DEFAULT, FormSpec.DEFAULT_GROW) // list summary 344 }; 345 346 RowSpec[] rowSpecs = new RowSpec[]{FILL_ROW_SPEC}; 347 FormLayout formLayout = new FormLayout(columnSpecs, rowSpecs); 348 // coupled glue space around nav buttons! 349 formLayout.setColumnGroups(new int[][]{{2, 4}}); 350 JPanel buttonPanel = new JPanel(formLayout); 351 352 JComponent editButtons = getEditButtons(); 353 JComponent tableButtonBar = getTableWidget().getButtonBar(); 354 if (editButtons != null) 355 { 356 buttonPanel.add(editButtons, cc.xy(1, 1)); 357 } 358 if (tableButtonBar != null) 359 { 360 buttonPanel.add(tableButtonBar, cc.xy(3, 1)); 361 } 362 363 buttonPanel.add(getTableWidget().getListSummaryLabel(), cc.xy(5, 1)); 364 365 return buttonPanel; 366 } 367 368 protected JComponent getEditButtons() 369 { 370 if (!isAddRowSupported() && !isUpdateRowSupported()) 371 { 372 return null; 373 } 374 375 ColumnSpec[] columnSpecs = new ColumnSpec[]{FILL_NOGROW_COLUMN_SPEC, // save 376 FormFactory.RELATED_GAP_COLSPEC, // gap 377 FILL_NOGROW_COLUMN_SPEC, // undo 378 FormFactory.RELATED_GAP_COLSPEC, // gap 379 FormFactory.DEFAULT_COLSPEC, // separator 380 FormFactory.RELATED_GAP_COLSPEC, // gap 381 FILL_NOGROW_COLUMN_SPEC, // quickadd 382 }; 383 384 RowSpec[] rowSpecs = new RowSpec[]{FILL_ROW_SPEC}; 385 FormLayout formLayout = new FormLayout(columnSpecs, rowSpecs); 386 JPanel buttonPanel = new JPanel(formLayout); 387 buttonPanel.add(getCommitComponent(), cc.xy(1, 1)); 388 buttonPanel.add(getRevertCommand().createButton(), cc.xy(3, 1)); 389 if (isAddRowSupported()) 390 { 391 buttonPanel.add(new JSeparator(SwingConstants.VERTICAL), cc.xy(5, 1)); 392 buttonPanel.add(createQuickAddCheckBox(), cc.xy(7, 1)); 393 } 394 return buttonPanel; 395 } 396 397 protected JComponent getCommitComponent() 398 { 399 if (isAddRowSupported() && isUpdateRowSupported()) 400 { 401 saveUpdateSwitcher = new CardLayout(); 402 saveUpdatePanel = new JPanel(saveUpdateSwitcher); 403 saveUpdatePanel.add(getCreateCommand().createButton(), CREATE_COMMAND_ID); 404 saveUpdatePanel.add(getUpdateCommand().createButton(), UPDATE_COMMAND_ID); 405 return saveUpdatePanel; 406 } 407 if (isAddRowSupported()) 408 { 409 DefaultButtonFocusListener.setDefaultButton(getDetailForm().getControl(), getCreateCommand()); 410 return getCreateCommand().createButton(); 411 } 412 413 DefaultButtonFocusListener.setDefaultButton(getDetailForm().getControl(), getUpdateCommand()); 414 return getUpdateCommand().createButton(); 415 } 416 417 /** 418 * Convenience method to retrieve the action command that should be used when changes are made in the detailForm. 419 * This can be update or add, according to whether a row is selected and changed or no row is selected. 420 * 421 * @return the command that should be used to save changes in the form. 422 */ 423 protected ActionCommand getCommitCommand() 424 { 425 if ((selectedRowObject == null) && (isAddRowSupported())) 426 return getCreateCommand(); 427 428 else if (isUpdateRowSupported()) 429 return getUpdateCommand(); 430 431 return null; 432 } 433 434 /** 435 * Returns the save command, lazily creates one if needed. 436 */ 437 public ActionCommand getUpdateCommand() 438 { 439 if (updateCommand == null) 440 { 441 updateCommand = createUpdateCommand(); 442 } 443 return updateCommand; 444 } 445 446 /** 447 * Creates the save command. 448 * 449 * @see #doUpdate() 450 */ 451 protected ActionCommand createUpdateCommand() 452 { 453 ActionCommand command = new ActionCommand(UPDATE_COMMAND_ID) 454 { 455 456 @Override 457 protected void doExecuteCommand() 458 { 459 doUpdate(); 460 } 461 }; 462 command.setSecurityControllerId(getId() + "." + UPDATE_COMMAND_ID); 463 getCommandConfigurer().configure(command); 464 getDetailForm().addGuarded(command, FormGuard.LIKE_COMMITCOMMAND); 465 return command; 466 } 467 468 /** 469 * Save the changes made in the detailForm according to following steps: 470 * <p/> 471 * <ol> 472 * <li>commit form</li> 473 * <li>formObject sent to back-end</li> 474 * <li>changes are handled in back-end</li> 475 * <li>changed object is returned to client</li> 476 * <li>old object is replaced by changed object</li> 477 * </ol> 478 */ 479 protected void doUpdate() 480 { 481 getDetailForm().commit(); 482 Object savedObject = null; 483 try 484 { 485 savedObject = saveEntity(getDetailForm().getFormObject()); 486 setDetailFormObject(savedObject, tableSelectionObserver, false); 487 } 488 catch (RuntimeException e) 489 { 490 Object changedObject = getDetailForm().getFormObject(); 491 // the following actually requests the object from the back-end 492 boolean success = setDetailFormObject(changedObject, tableSelectionObserver, true); 493 // set the changes back on the model if the object could be set on the form model 494 if (success) 495 RcpSupport.mapObjectOnFormModel(getDetailForm().getFormModel(), changedObject); 496 throw e; 497 } 498 } 499 500 /** 501 * Returns the create command, lazily creates one if needed. 502 */ 503 public ActionCommand getCreateCommand() 504 { 505 if (createRowCommand == null) 506 { 507 createRowCommand = createCreateCommand(); 508 } 509 return createRowCommand; 510 } 511 512 /** 513 * Creates the create command. 514 * 515 * @see #doCreate() 516 */ 517 protected ActionCommand createCreateCommand() 518 { 519 ActionCommand command = new ActionCommand(CREATE_COMMAND_ID) 520 { 521 522 @Override 523 protected void doExecuteCommand() 524 { 525 doCreate(); 526 } 527 }; 528 command.setSecurityControllerId(getId() + "." + CREATE_COMMAND_ID); 529 getCommandConfigurer().configure(command); 530 getDetailForm().addGuarded(command, FormGuard.LIKE_COMMITCOMMAND); 531 return command; 532 } 533 534 /** 535 * Creates a new data object according to following steps: 536 * <p/> 537 * <ol> 538 * <li>form commit</li> 539 * <li>formObject sent to back-end</li> 540 * <li>back-end creates item</li> 541 * <li>back-end returns new item to client</li> 542 * <li>new item is selected in dataEditor if possible</li> 543 * </ol> 544 */ 545 protected void doCreate() 546 { 547 getDetailForm().commit(); 548 Object newObject = null; 549 try 550 { 551 newObject = createNewEntity(getDetailForm().getFormObject()); 552 // select row only if user hasn't made another selection in 553 // table and this commit is triggered by the save-changes 554 // dialog and if not in quick add mode 555 if (newObject != null && !getTableWidget().hasSelection()) 556 { 557 if (getTableWidget().selectRowObject(newObject, null) == -1) 558 { 559 // select row wasn't succesfull, maybe search string 560 // was filled in? 561 setSearchString(null); 562 getTableWidget().selectRowObject(newObject, null); 563 } 564 } 565 } 566 catch (RuntimeException e) 567 { 568 Object changedFormObject = getDetailForm().getFormObject(); 569 newRow(null); 570 RcpSupport.mapObjectOnFormModel(getDetailForm().getFormModel(), changedFormObject); 571 throw e; 572 // make form dirty, show error in messagepane and throw exception to display error dialog 573 } 574 } 575 576 protected AbstractCommand getRevertCommand() 577 { 578 return getDetailForm().getRevertCommand(); 579 } 580 581 abstract protected DefaultValidationResultsModel getValidationResults(); 582 583 /** 584 * {@inheritDoc} 585 */ 586 @Override 587 public ValidationResultsReporter newSingleLineResultsReporter(Messagable messagable) 588 { 589 return new SimpleValidationResultsReporter(getValidationResults(), messagable); 590 } 591 592 protected JComponent createQuickAddCheckBox() 593 { 594 quickAddCheckBox = new JCheckBox(RcpSupport.getMessage(getId(), QUICKADD, RcpSupport.TITLE)); 595 quickAddCheckBox.setFocusable(false); 596 597 getCreateCommand().addCommandInterceptor(new ActionCommandInterceptor() 598 { 599 600 public boolean preExecution(ActionCommand command) 601 { 602 return true; // proceed 603 } 604 605 public void postExecution(ActionCommand command) 606 { 607 if (quickAddCheckBox.isSelected()) 608 { 609 getAddRowCommand().execute(); 610 } 611 } 612 }); 613 614 quickAddCheckBox.setEnabled(getAddRowCommand().isEnabled()); 615 getAddRowCommand().addPropertyChangeListener(AbstractCommand.ENABLED_PROPERTY_NAME, 616 new PropertyChangeListener() 617 { 618 619 public void propertyChange(PropertyChangeEvent evt) 620 { 621 Object newValue = evt.getNewValue(); 622 quickAddCheckBox.setEnabled(((Boolean) newValue).booleanValue()); 623 } 624 }); 625 626 return quickAddCheckBox; 627 } 628 629 protected JComponent getTableFilterPanel() 630 { 631 ColumnSpec[] columnSpecs = new ColumnSpec[]{FILL_COLUMN_SPEC}; 632 RowSpec[] rowSpecs = new RowSpec[]{ 633 // buttons for list and filter 634 FormFactory.DEFAULT_ROWSPEC, FormFactory.LINE_GAP_ROWSPEC, 635 // splitpane with list and filter 636 FILL_ROW_SPEC}; 637 JPanel top = new JPanel(new FormLayout(columnSpecs, rowSpecs)); 638 639 final JTable table = getTableWidget().getTable(); 640 if (isUpdateRowSupported()) 641 { 642 String tooltip = RcpSupport.getMessage(getId(), DBLCLICKSELECTS, RcpSupport.CAPTION); 643 table.setToolTipText(tooltip); 644 } 645 646 CommandGroup tableGroup = getTablePopupMenuCommandGroup(); 647 648 table.addMouseListener(new TableMouseListener(table, tableGroup.createPopupMenu())); 649 650 JComponent tableScroller = getTableWidget().getComponent(); 651 JComponent tableAndOptionalFilter = tableScroller; 652 if (isFilterSupported()) // add filter too via splitpane 653 { 654 JSplitPane splitPane = new JSplitPane(); 655 if (this.selectMode == ON) 656 { 657 splitPane.setDividerLocation(-1); 658 this.toggleFilterCommand = new SplitPaneExpansionToggleCommand("openfilter", splitPane, false); 659 } 660 else 661 { 662 splitPane.setDividerLocation(Integer.MAX_VALUE); 663 this.toggleFilterCommand = new SplitPaneExpansionToggleCommand("openfilter", splitPane, true); 664 } 665 666 splitPane.setLastDividerLocation(Integer.MAX_VALUE); 667 splitPane.setOneTouchExpandable(true); 668 splitPane.setResizeWeight(1.0); 669 670 this.toggleFilterCommand.setEnabled(isFilterSupported()); 671 672 splitPane.setLeftComponent(tableScroller); 673 JComponent filterPanel = getFilterPanel(); 674 splitPane.setRightComponent(filterPanel); 675 tableAndOptionalFilter = splitPane; 676 } 677 678 top.add(getTableFilterControlPanel(), cc.xy(1, 1)); 679 top.add(tableAndOptionalFilter, cc.xy(1, 3)); 680 681 return top; 682 } 683 684 /** 685 * Returns the commandGroup that should be used to create the popup menu for the table. 686 */ 687 protected CommandGroup getTablePopupMenuCommandGroup() 688 { 689 return CommandGroup.createCommandGroup(new Object[]{getEditRowCommand(), "separator", 690 getAddRowCommand(), getCloneRowCommand(), getRemoveRowsCommand(), "separator", 691 getRefreshCommand(), "separator", getCopySelectedRowsToClipboardCommand()}); 692 } 693 694 private JComponent getFilterPanel() 695 { 696 if (!isFilterSupported()) 697 { 698 return null; 699 } 700 701 ColumnSpec[] columnSpecs = new ColumnSpec[]{FILL_COLUMN_SPEC}; 702 RowSpec[] rowSpecs = new RowSpec[]{new RowSpec(RowSpec.FILL, Sizes.DEFAULT, FormSpec.DEFAULT_GROW), // form gewrapped in een scrollpane. 703 FormFactory.RELATED_GAP_ROWSPEC, FormFactory.DEFAULT_ROWSPEC, // buttons 704 }; 705 JPanel filterPanel = new JPanel(new FormLayout(columnSpecs, rowSpecs)); 706 707 JComponent filterFormControl = getFilterForm().getControl(); 708 filterFormControl.setBorder(Borders.DIALOG_BORDER); 709 710 final JScrollPane filterScroller = new JScrollPane(filterFormControl, 711 JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 712 713 filterScroller.getHorizontalScrollBar().getModel().addChangeListener(new ChangeListener() 714 { 715 716 public void stateChanged(ChangeEvent e) 717 { 718 if (filterScroller.getVerticalScrollBar().isVisible()) 719 { 720 filterScroller.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.gray)); 721 } 722 else 723 { 724 filterScroller.setBorder(null); 725 } 726 } 727 }); 728 729 // setting minimum size: always keep width of panel + scrollbar width 730 Dimension scrollDimension = new Dimension(filterScroller.getPreferredSize().width 731 + filterScroller.getVerticalScrollBar().getPreferredSize().width, 50); 732 filterScroller.setMinimumSize(scrollDimension); 733 734 filterPanel.add(filterScroller, cc.xy(1, 1)); 735 JComponent filterControlPanel = getFilterControlPanel(); 736 filterControlPanel.setBorder(Borders.DIALOG_BORDER); 737 filterPanel.add(filterControlPanel, cc.xy(1, 3)); 738 739 // register a default command to use when focus is on the filterPanel 740 DefaultButtonFocusListener.setDefaultButton(filterPanel, getExecuteFilterCommand()); 741 742 return filterPanel; 743 } 744 745 private JComponent getFilterControlPanel() 746 { 747 CommandGroup controlCommands = CommandGroup.createCommandGroup(new Object[]{ 748 getExecuteFilterCommand(), getEmptyFilterCommand()}); 749 750 return controlCommands.createButtonBar((Size) null, (Border) null); 751 } 752 753 private JCheckBox quickAddCheckBox; 754 755 private ActionCommand copySelectedRowsCommand; 756 757 private JComponent getTableFilterControlPanel() 758 { 759 CommandGroup tableFilterControlCommands = isFilterSupported() 760 ? getTableFilterControlCommands() 761 : null; 762 ColumnSpec[] columnSpecs = getTableColumnSpecs(tableFilterControlCommands != null 763 && tableFilterControlCommands.size() > 0); 764 RowSpec[] rowSpecs = new RowSpec[]{FILL_ROW_SPEC}; 765 FormLayout formLayout = new FormLayout(columnSpecs, rowSpecs); 766 JPanel buttonPanel = new JPanel(formLayout); 767 int columnCounter = 1; 768 if (isAddRowSupported()) 769 { 770 buttonPanel.add(getAddRowCommand().createButton(), cc.xy(columnCounter, 1)); 771 columnCounter += 2; 772 } 773 if (isRemoveRowsSupported()) 774 { 775 buttonPanel.add(getRemoveRowsCommand().createButton(), cc.xy(columnCounter, 1)); 776 columnCounter += 2; 777 } 778 if (isAddRowSupported() || isRemoveRowsSupported()) 779 { 780 buttonPanel.add(new JSeparator(SwingConstants.VERTICAL), cc.xy(columnCounter, 1)); 781 columnCounter += 2; 782 } 783 buttonPanel.add(getRefreshCommand().createButton(), cc.xy(columnCounter, 1)); 784 columnCounter += 2; 785 buttonPanel.add(new JSeparator(SwingConstants.VERTICAL), cc.xy(columnCounter, 1)); 786 columnCounter += 2; 787 textFilterField = getTableWidget().getTextFilterField(); 788 if (textFilterField != null) 789 { 790 textFilterField.setText(searchString); 791 textFilterField.setPreferredSize(new Dimension(50, 20)); 792 buttonPanel.add(textFilterField, cc.xy(columnCounter, 1)); 793 columnCounter += 2; 794 } 795 if (isFilterSupported() && tableFilterControlCommands.size() > 0) 796 { 797 buttonPanel.add(new JSeparator(SwingConstants.VERTICAL), cc.xy(columnCounter, 1)); 798 columnCounter += 2; 799 buttonPanel.add(tableFilterControlCommands.createButtonBar(new ColumnSpec("fill:pref:nogrow"), 800 new RowSpec("fill:default:nogrow"), null), cc.xy(columnCounter, 1)); 801 } 802 803 return buttonPanel; 804 } 805 806 protected CommandGroup getTableFilterControlCommands() 807 { 808 CommandGroup group = new CommandGroup(); 809 group.add(getToggleFilterCommand()); 810 group.add(getClearFilterCommand()); 811 return group; 812 } 813 814 private ColumnSpec[] getTableColumnSpecs(boolean addFilterCommands) 815 { 816 java.util.List<ColumnSpec> columnSpecs = new ArrayList<ColumnSpec>(); 817 if (isAddRowSupported()) 818 { 819 columnSpecs.add(FILL_NOGROW_COLUMN_SPEC);// add btn 820 columnSpecs.add(FormFactory.RELATED_GAP_COLSPEC); // gap 821 } 822 if (isRemoveRowsSupported()) 823 { 824 columnSpecs.add(FILL_NOGROW_COLUMN_SPEC); // remove btn 825 columnSpecs.add(FormFactory.RELATED_GAP_COLSPEC); // gap 826 } 827 if (isAddRowSupported() || isRemoveRowsSupported()) 828 { 829 columnSpecs.add(FormFactory.DEFAULT_COLSPEC); // separator // 830 // -------------------- 831 columnSpecs.add(FormFactory.RELATED_GAP_COLSPEC); // gap 832 } 833 columnSpecs.add(FILL_NOGROW_COLUMN_SPEC); // refresh btn 834 columnSpecs.add(FormFactory.RELATED_GAP_COLSPEC); // gap 835 columnSpecs.add(FormFactory.DEFAULT_COLSPEC); // separator 836 // -------------------- 837 columnSpecs.add(FormFactory.RELATED_GAP_COLSPEC); // gap 838 columnSpecs.add(FILL_COLUMN_SPEC); // glue space |>> glazed-list-text 839 // filter 840 if (addFilterCommands) 841 { 842 columnSpecs.add(FormFactory.RELATED_GAP_COLSPEC); // gap 843 columnSpecs.add(FormFactory.DEFAULT_COLSPEC); // separator 844 // -------------------- 845 columnSpecs.add(FormFactory.RELATED_GAP_COLSPEC); // gap 846 columnSpecs.add(FILL_NOGROW_COLUMN_SPEC); // filter commands panel 847 } 848 return columnSpecs.toArray(new ColumnSpec[]{}); 849 } 850 851 private final class TableMouseListener extends MouseAdapter 852 { 853 854 private final JTable table; 855 private final JPopupMenu menu; 856 857 private TableMouseListener(JTable table, JPopupMenu menu) 858 { 859 this.table = table; 860 this.menu = menu; 861 } 862 863 private void handlePopupTrigger(MouseEvent e) 864 { 865 if (e.isPopupTrigger()) 866 { 867 Point p = e.getPoint(); 868 int row = this.table.rowAtPoint(p); 869 int column = this.table.columnAtPoint(p); 870 // The autoscroller can generate drag events outside the 871 // Table's range. 872 if (!this.table.isRowSelected(row) && (column != -1)) 873 { 874 this.table.changeSelection(row, column, e.isControlDown(), e.isShiftDown()); 875 } 876 877 this.menu.show(e.getComponent(), e.getX(), e.getY()); 878 } 879 } 880 881 @Override 882 public void mousePressed(MouseEvent e) 883 { 884 handlePopupTrigger(e); 885 } 886 887 @Override 888 public void mouseReleased(MouseEvent e) 889 { 890 handlePopupTrigger(e); 891 } 892 893 @Override 894 public void mouseClicked(MouseEvent e) 895 { 896 if (e.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(e)) 897 { 898 if (selectionCommand != null) 899 { 900 selectionCommand.execute(); 901 } 902 else if (isUpdateRowSupported()) 903 { 904 getEditRowCommand().execute(); 905 } 906 } 907 } 908 } 909 910 class RowObjectReplacer implements Runnable 911 { 912 913 private final Object oldObject; 914 915 private final Object newObject; 916 917 public RowObjectReplacer(final Object oldObject, final Object newObject) 918 { 919 this.oldObject = oldObject; 920 this.newObject = newObject; 921 } 922 923 public void run() 924 { 925 getTableWidget().replaceRowObject(oldObject, newObject, tableSelectionObserver); 926 } 927 } 928 929 protected void onRowSelection(Object rowObject) 930 { 931 if (rowObject instanceof Object[]) 932 { 933 getEditRowCommand().setEnabled(false); 934 getCloneRowCommand().setEnabled(false); 935 } 936 else 937 { 938 getEditRowCommand().setEnabled(true && isUpdateRowSupported()); 939 getCloneRowCommand().setEnabled(true && isCloneRowSupported()); 940 } 941 } 942 943 protected void newRow(Object newClone) 944 { 945 getTableWidget().unSelectAll(); 946 selectedRowObject = null; 947 AbstractForm detailForm = getDetailForm(); 948 949 if (detailForm instanceof NewFormObjectAware) 950 { 951 ((NewFormObjectAware) detailForm).setNewFormObject(newClone); 952 } 953 else 954 { 955 detailForm.setFormObject(newClone); 956 } 957 958 if (saveUpdateSwitcher != null) 959 { 960 DefaultButtonFocusListener.setDefaultButton(detailForm.getControl(), getCreateCommand()); 961 saveUpdateSwitcher.show(saveUpdatePanel, CREATE_COMMAND_ID); 962 } 963 if (detailForm instanceof Focussable) 964 { 965 ((Focussable) detailForm).grabFocus(); 966 } 967 } 968 969 protected void removeRows() 970 { 971 Object[] selectedRows = getTableWidget().getSelectedRows(); 972 973 if (selectedRows.length == 0) 974 { 975 return; 976 } 977 978 int answer = RcpSupport.showWarningDialog(getComponent(), REMOVE_CONFIRMATION_ID, 979 new Object[]{Integer.valueOf(selectedRows.length)}, JOptionPane.YES_NO_OPTION); 980 int nextSelectionIndex = getTableWidget().getTable().getSelectionModel().getMinSelectionIndex(); 981 982 for (int i = 0; i < selectedRows.length && (answer == JOptionPane.YES_OPTION); i++) 983 { 984 Object objectToRemove = selectedRows[i]; 985 986 try 987 { 988 removeEntity(objectToRemove); 989 } 990 catch (RuntimeException e) 991 { 992 log.error("Error removing row in DataEditor of type " + this.getClass().getName(), e); 993 994 RcpSupport.handleException(e); 995 996 int remaining = selectedRows.length - i - 1; 997 if (remaining > 0) 998 { 999 String ttl = RcpSupport 1000 .getMessage(getId(), REMOVE_CONTINUE_AFTER_ERROR, RcpSupport.TITLE); 1001 String errMsg = RcpSupport.getMessage(getId(), REMOVE_CONTINUE_AFTER_ERROR, 1002 RcpSupport.TEXT, new Object[]{Integer.valueOf(remaining)}); 1003 answer = JOptionPane.showConfirmDialog(getComponent(), errMsg, ttl, 1004 JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); 1005 } 1006 } 1007 } 1008 int nrOfRows = getTableWidget().nrOfRows(); 1009 if (nrOfRows > 0 && (getTableWidget().getSelectedRows().length == 0)) 1010 { 1011 if (nextSelectionIndex >= nrOfRows) 1012 { 1013 nextSelectionIndex = nrOfRows - 1; 1014 } 1015 getTableWidget().selectRowObject(nextSelectionIndex, null); 1016 } 1017 } 1018 1019 public AbstractCommand getToggleDetailCommand() 1020 { 1021 return toggleDetailCommand; 1022 } 1023 1024 public SplitPaneExpansionToggleCommand getToggleFilterCommand() 1025 { 1026 return toggleFilterCommand; 1027 } 1028 1029 protected ActionCommand getAddRowCommand() 1030 { 1031 if (this.addRowCommand == null) 1032 { 1033 this.addRowCommand = createAddRowCommand(); 1034 } 1035 return this.addRowCommand; 1036 } 1037 protected ActionCommand createAddRowCommand() 1038 { 1039 ActionCommand addRowCommand = new ActionCommand("addrow") 1040 { 1041 1042 @Override 1043 protected void doExecuteCommand() 1044 { 1045 if (canClose()) 1046 { 1047 newRow(null); 1048 toggleDetailCommand.doShow(); 1049 } 1050 } 1051 }; 1052 addRowCommand.setSecurityControllerId(getId() + ".addrow"); 1053 commandConfigurer.configure(addRowCommand); 1054 addRowCommand.setEnabled(isAddRowSupported()); 1055 return addRowCommand; 1056 } 1057 1058 protected ActionCommand getEditRowCommand() 1059 { 1060 if (this.editRowCommand == null) 1061 { 1062 this.editRowCommand = createEditRowCommand(); 1063 } 1064 return this.editRowCommand; 1065 } 1066 1067 protected ActionCommand createEditRowCommand() 1068 { 1069 ActionCommand editRow = new ActionCommand("editrow") 1070 { 1071 1072 @Override 1073 protected void doExecuteCommand() 1074 { 1075 toggleDetailCommand.doShow(); 1076 if (getDetailForm() instanceof Focussable) 1077 { 1078 ((Focussable) getDetailForm()).grabFocus(); 1079 } 1080 } 1081 }; 1082 editRow.setSecurityControllerId(getId() + ".editrow"); 1083 commandConfigurer.configure(editRow); 1084 editRow.setEnabled(isUpdateRowSupported()); 1085 return editRow; 1086 } 1087 1088 protected ActionCommand getCloneRowCommand() 1089 { 1090 if (this.cloneRowCommand == null) 1091 { 1092 this.cloneRowCommand = createCloneRowCommand(); 1093 } 1094 return this.cloneRowCommand; 1095 } 1096 1097 protected ActionCommand createCloneRowCommand() 1098 { 1099 ActionCommand cloneRow = new ActionCommand("clonerow") 1100 { 1101 1102 @Override 1103 protected void doExecuteCommand() 1104 { 1105 Object newClone = cloneEntity(selectedRowObject); 1106 newRow(newClone); 1107 toggleDetailCommand.doShow(); 1108 } 1109 1110 }; 1111 cloneRow.setSecurityControllerId(getId() + ".clonerow"); 1112 commandConfigurer.configure(cloneRow); 1113 cloneRow.setEnabled(isCloneRowSupported()); 1114 return cloneRow; 1115 } 1116 1117 protected ActionCommand getRemoveRowsCommand() 1118 { 1119 if (this.removeRowsCommand == null) 1120 { 1121 this.removeRowsCommand = createRemoveRowCommand(); 1122 } 1123 return this.removeRowsCommand; 1124 } 1125 1126 protected ActionCommand createRemoveRowCommand() 1127 { 1128 ActionCommand removeRowCommand = new ActionCommand("removerow") 1129 { 1130 1131 @Override 1132 protected void doExecuteCommand() 1133 { 1134 removeRows(); 1135 } 1136 }; 1137 removeRowCommand.setSecurityControllerId(getId() + ".removerow"); 1138 commandConfigurer.configure(removeRowCommand); 1139 removeRowCommand.setEnabled(isRemoveRowsSupported()); 1140 return removeRowCommand; 1141 } 1142 1143 public ActionCommand getRefreshCommand() 1144 { 1145 if (this.refreshCommand == null) 1146 { 1147 this.refreshCommand = createRefreshCommand(); 1148 } 1149 return this.refreshCommand; 1150 } 1151 1152 public ActionCommand getCopySelectedRowsToClipboardCommand() 1153 { 1154 if (this.copySelectedRowsCommand == null) 1155 { 1156 this.copySelectedRowsCommand = createCopySelectedRowsToClipboardCommand(); 1157 } 1158 return this.copySelectedRowsCommand; 1159 } 1160 1161 private ActionCommand createCopySelectedRowsToClipboardCommand() 1162 { 1163 ActionCommand command = new ActionCommand("copyToClipboard") 1164 { 1165 1166 @Override 1167 protected void doExecuteCommand() 1168 { 1169 String clipboardContent = createTabDelimitedSelectedRowsContent(); 1170 StringSelection selection = new StringSelection(clipboardContent); 1171 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); 1172 } 1173 1174 private String createTabDelimitedSelectedRowsContent() 1175 { 1176 java.util.List<java.util.List<String>> formattedRowList = new ArrayList<java.util.List<String>>(); 1177 JXTable jxTable = (JXTable) getTableWidget().getTable(); 1178 java.util.List<String> headerList = new ArrayList<String>(); 1179 for (TableColumn tableColumn : jxTable.getColumns()) 1180 { 1181 Object headerValue = tableColumn.getHeaderValue(); 1182 headerList.add(headerValue == null ? "" : headerValue.toString()); 1183 } 1184 formattedRowList.add(headerList); 1185 for (int rowIndex : jxTable.getSelectedRows()) 1186 { 1187 java.util.List<String> columnList = new ArrayList<String>(); 1188 for (TableColumn tableColumn : jxTable.getColumns()) 1189 { 1190 Object unformattedValue = jxTable.getModel().getValueAt(rowIndex, 1191 tableColumn.getModelIndex()); 1192 int columnViewIndex = jxTable.convertColumnIndexToView(tableColumn.getModelIndex()); 1193 TableCellRenderer renderer = jxTable.getCellRenderer(rowIndex, columnViewIndex); 1194 Component component = renderer.getTableCellRendererComponent(jxTable, 1195 unformattedValue, false, false, rowIndex, columnViewIndex); 1196 1197 columnList.add(getFormattedValue(component)); 1198 } 1199 formattedRowList.add(columnList); 1200 } 1201 1202 StringBuilder builder = new StringBuilder(formattedRowList.size() * 200); 1203 for (java.util.List<String> row : formattedRowList) 1204 { 1205 builder.append(StringUtils.join(row.iterator(), "\t") + "\n"); 1206 } 1207 return builder.toString(); 1208 } 1209 1210 private String getFormattedValue(Component component) 1211 { 1212 if (component instanceof JLabel) 1213 { 1214 return ((JLabel) component).getText(); 1215 } 1216 else if (component instanceof JTextComponent) 1217 { 1218 return ((JTextComponent) component).getText(); 1219 } 1220 else if (component instanceof JToggleButton) 1221 { 1222 JToggleButton button = (JToggleButton) component; 1223 return RcpSupport.getMessage("boolean.yesno." + button.isSelected()); 1224 } 1225 else 1226 { 1227 return component.toString(); 1228 } 1229 } 1230 1231 }; 1232 1233 return command; 1234 } 1235 1236 protected ActionCommand createRefreshCommand() 1237 { 1238 return makeExecuteFilterCommand("refresh", false); 1239 } 1240 1241 public ActionCommand getClearFilterCommand() 1242 { 1243 if (this.clearFilterCommand == null) 1244 { 1245 this.clearFilterCommand = createClearFilterCommand(); 1246 } 1247 return this.clearFilterCommand; 1248 } 1249 1250 protected ActionCommand createClearFilterCommand() 1251 { 1252 ActionCommand newCommand = makeClearFilterCommand("clearfilter", true); 1253 newCommand.setEnabled(isFilterSupported()); 1254 return newCommand; 1255 } 1256 1257 private ActionCommand makeClearFilterCommand(String id, final boolean refreshAndHideAfterClear) 1258 { 1259 ActionCommand newCommand = new ActionCommand(id) 1260 { 1261 1262 @Override 1263 protected void doExecuteCommand() 1264 { 1265 getFilterForm().resetCriteria(); 1266 if (refreshAndHideAfterClear) 1267 { 1268 executeFilter(); 1269 AbstractDataEditorWidget.this.toggleFilterCommand.doHide(); 1270 } 1271 } 1272 }; 1273 commandConfigurer.configure(newCommand); 1274 return newCommand; 1275 } 1276 1277 public ActionCommand getExecuteFilterCommand() 1278 { 1279 if (this.executeFilterCommand == null) 1280 { 1281 this.executeFilterCommand = createExecuteFilterCommand(); 1282 } 1283 return this.executeFilterCommand; 1284 } 1285 1286 protected ActionCommand createExecuteFilterCommand() 1287 { 1288 ActionCommand newCommand = makeExecuteFilterCommand("executefilter", true); 1289 newCommand.setEnabled(isFilterSupported()); 1290 return newCommand; 1291 } 1292 1293 private ActionCommand makeExecuteFilterCommand(String id, final boolean commitFilter) 1294 { 1295 ActionCommand newCommand = new ActionCommand(id) 1296 { 1297 1298 @Override 1299 protected void doExecuteCommand() 1300 { 1301 if (textFilterField != null) 1302 { 1303 textFilterField.setText(""); 1304 } 1305 if (commitFilter && isFilterSupported()) 1306 { 1307 getFilterForm().commit(); 1308 } 1309 executeFilter(); 1310 } 1311 }; 1312 if (isFilterSupported()) 1313 { 1314 new FormGuard(getFilterForm().getFormModel(), newCommand, FormGuard.ON_NOERRORS); 1315 } 1316 commandConfigurer.configure(newCommand); 1317 return newCommand; 1318 } 1319 1320 protected ActionCommand getEmptyFilterCommand() 1321 { 1322 if (this.emptyFilterCommand == null) 1323 { 1324 this.emptyFilterCommand = createEmptyFilterCommand(); 1325 } 1326 return this.emptyFilterCommand; 1327 } 1328 1329 protected ActionCommand createEmptyFilterCommand() 1330 { 1331 ActionCommand newCommand = makeClearFilterCommand("emptyfilter", false); 1332 newCommand.setEnabled(isFilterSupported()); 1333 return newCommand; 1334 } 1335 1336 protected AbstractCommand getHelpCommand() 1337 { 1338 return RcpSupport.createDummyCommand("help", "Behulpzaam"); 1339 } 1340 1341 private AbstractCommand getCloseCommand() 1342 { 1343 return RcpSupport.createDummyCommand("exit", "deuren toe."); 1344 } 1345 1346 protected Object[] getFilterCriteria() 1347 { 1348 Object[] criteria; 1349 criteria = new Object[1]; 1350 criteria[0] = getFilterForm().getFilterCriteria(); 1351 return criteria; 1352 } 1353 1354 protected ActionCommand getSelectCommand() 1355 { 1356 return RcpSupport.createDummyCommand("select", "Chosen!"); 1357 } 1358 1359 protected AbstractCommand[] getControlCommands() 1360 { 1361 if (isSelectMode()) 1362 { 1363 return new AbstractCommand[]{getSelectCommand(), // select 1364 getCloseCommand() 1365 // close 1366 }; 1367 } 1368 1369 return new AbstractCommand[]{ 1370 getCloseCommand() 1371 // close 1372 }; 1373 } 1374 1375 protected abstract boolean isFilterSupported(); 1376 1377 protected abstract boolean isUpdateRowSupported(); 1378 1379 protected abstract boolean isAddRowSupported(); 1380 1381 protected abstract boolean isCloneRowSupported(); 1382 1383 protected abstract boolean isRemoveRowsSupported(); 1384 1385 protected abstract FilterForm getFilterForm(); 1386 1387 public abstract AbstractForm getDetailForm(); 1388 1389 public abstract Widget createDetailWidget(); 1390 1391 public abstract TableWidget getTableWidget(); 1392 1393 protected abstract void executeFilter(); 1394 1395 public abstract void executeFilter(Map<String, Object> parameters); 1396 1397 protected final Object loadEntityDetails(Object baseObject) 1398 { 1399 return loadEntityDetails(baseObject, false); 1400 } 1401 1402 /** 1403 * Fetch the detailed object from the back-end. If the baseObject is already detailed, the baseObject can 1404 * be returned directly if and only if no forceLoad is requested. This logic is also apparent in the 1405 * {@link org.springframework.richclient.widget.editor.provider.DataProvider} class. 1406 * 1407 * @param baseObject object containing enough information to fetch a detailed version. 1408 * @param forceLoad if <code>true</code> always load the detailed object from the back-end, if 1409 * <code>false</code> a shortcut can be implemented by returning the baseObject directly. 1410 * @return the detailed object 1411 */ 1412 protected abstract Object loadEntityDetails(Object baseObject, boolean forceLoad); 1413 1414 protected abstract Object saveEntity(Object committedObject); 1415 1416 protected abstract Object createNewEntity(Object committedObject); 1417 1418 protected abstract Object cloneEntity(Object sampleObject); 1419 1420 protected abstract void removeEntity(Object objectToRemove); 1421 1422 @Override 1423 public boolean canClose() 1424 { 1425 boolean userBreak = false; 1426 int answer = JOptionPane.NO_OPTION; 1427 1428 FormModel detailFormModel = getDetailForm().getFormModel(); 1429 1430 if (detailFormModel.isEnabled() && detailFormModel.isDirty()) 1431 { 1432 if (detailFormModel.isCommittable()) 1433 { 1434 answer = RcpSupport.showWarningDialog(getComponent(), UNSAVEDCHANGES_WARNING_ID, 1435 JOptionPane.YES_NO_CANCEL_OPTION); 1436 } 1437 else // form is uncomittable, change it or revert it 1438 { 1439 answer = RcpSupport.showWarningDialog(getComponent(), 1440 UNSAVEDCHANGES_UNCOMMITTABLE_WARNING_ID, JOptionPane.YES_NO_OPTION); 1441 // the following might seem strange, but it aligns the answer with the other part of this if construction 1442 // if we said 'yes keep editing': don't discard changes, continue editing to save it later on == CANCEL in previous if 1443 // if we said 'no discard changes': discard changed and switch to other row == NO in previous if 1444 answer = answer == JOptionPane.YES_OPTION ? JOptionPane.CANCEL_OPTION : JOptionPane.NO_OPTION; 1445 } 1446 1447 switch (answer) 1448 { 1449 case JOptionPane.CANCEL_OPTION: 1450 userBreak = true; 1451 break; 1452 case JOptionPane.YES_OPTION: 1453 getCommitCommand().execute(); 1454 break; 1455 case JOptionPane.NO_OPTION: 1456 detailFormModel.revert(); 1457 break; 1458 } 1459 } 1460 1461 return !userBreak; 1462 } 1463 1464 private boolean setDetailFormObject(Object rowObject, Observer reportingObserver, boolean forceLoad) 1465 { 1466 // quick check to avoid multiple runs 1467 if (!forceLoad && (rowObject == selectedRowObject)) 1468 { 1469 return true; 1470 } 1471 1472 if (saveUpdateSwitcher != null) 1473 { 1474 DefaultButtonFocusListener.setDefaultButton(getDetailForm().getControl(), getUpdateCommand()); 1475 saveUpdateSwitcher.show(saveUpdatePanel, UPDATE_COMMAND_ID); 1476 } 1477 1478 // check on current detailObject, if user isn't ready to switch, select 1479 // previous object again 1480 if (!canClose()) 1481 { 1482 getTableWidget().selectRowObject(selectedRowObject, reportingObserver); 1483 return false; 1484 } 1485 1486 boolean success = true; 1487 1488 // nothing is stopping us from setting the newly selected object 1489 if (rowObject != null) 1490 { 1491 Object detailedObject = loadEntityDetails(rowObject, forceLoad); 1492 // if null, remove from list, set rowObject null and fall through (exception will be displayed) 1493 if (detailedObject == null) 1494 { 1495 getTableWidget().removeRowObject(rowObject); 1496 rowObject = null; 1497 success = false; 1498 } 1499 else if (detailedObject != rowObject) 1500 { 1501 replaceRowObject(rowObject, detailedObject); 1502 rowObject = detailedObject; 1503 } 1504 } 1505 getDetailForm().setFormObject(rowObject); 1506 selectedRowObject = rowObject; 1507 onRowSelection(rowObject); 1508 return success; 1509 } 1510 1511 protected void replaceRowObject(Object oldRowObject, Object newRowObject) 1512 { 1513 EventQueue.invokeLater(new RowObjectReplacer(oldRowObject, newRowObject)); 1514 } 1515 1516 public final void setDataProviderEventSources(java.util.List dataProviderEventSources) 1517 { 1518 if (this.dataProviderSources == null) 1519 { 1520 this.dataProviderSources = new HashMap(); 1521 } 1522 for (Iterator sourceIter = dataProviderEventSources.iterator(); sourceIter.hasNext();) 1523 { 1524 DataProviderEventSource source = (DataProviderEventSource) sourceIter.next(); 1525 this.dataProviderSources.put(source.getClass(), source); 1526 } 1527 } 1528 1529 public final void addDataProviderListener(Class dataProviderEventSource, DataProviderListener listener) 1530 { 1531 Object source = this.dataProviderSources.get(dataProviderEventSource); 1532 if (source != null) 1533 { 1534 ((DataProviderEventSource) source).addDataProviderListener(listener); 1535 } 1536 } 1537 1538 public final void removeDataProviderListener(Class dataProviderEventSource, DataProviderListener listener) 1539 { 1540 Object source = this.dataProviderSources.get(dataProviderEventSource); 1541 if (source != null) 1542 { 1543 ((DataProviderEventSource) source).removeDataProviderListener(listener); 1544 } 1545 } 1546 1547 /** 1548 * @inheritDoc 1549 */ 1550 public Object getSelection() 1551 { 1552 return getTableWidget().getSelectedRows(); 1553 } 1554 1555 /** 1556 * @inheritDoc 1557 */ 1558 public void setSelectionCommand(ActionCommand command) 1559 { 1560 setSelectMode(AbstractDataEditorWidget.ON); 1561 selectionCommand = command; 1562 enableSelectButton(getSelection()); 1563 } 1564 1565 public void setMultipleSelectionInSelectMode(boolean multipleSelection) 1566 { 1567 multipleSelectionInSelectMode = multipleSelection; 1568 } 1569 1570 /** 1571 * @inheritDoc 1572 */ 1573 public void removeSelectionCommand() 1574 { 1575 selectionCommand = null; 1576 setSelectMode(AbstractDataEditorWidget.OFF); 1577 } 1578 1579 private void enableSelectButton(Object selection) 1580 { 1581 if (selectionCommand != null) 1582 { 1583 if (selection == null) 1584 { 1585 selectionCommand.setEnabled(false); 1586 } 1587 else if (multipleSelectionInSelectMode == ON) 1588 { 1589 selectionCommand.setEnabled(true); 1590 } 1591 else 1592 { 1593 selectionCommand.setEnabled(!(selection instanceof Object[]) 1594 || (((Object[]) selection).length == 1)); 1595 } 1596 } 1597 } 1598 1599 private class ListSelectionObserver implements Observer 1600 { 1601 1602 public void update(Observable o, Object rowObject) 1603 { 1604 AbstractDataEditorWidget.this.setDetailFormObject(rowObject instanceof Object[] 1605 ? null 1606 : rowObject, tableSelectionObserver, false); 1607 enableSelectButton(rowObject); 1608 } 1609 } 1610 }