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 }