/*******************************************************************************
* Copyright (c) 2007, 2014 compeople AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* compeople AG - initial API and implementation
*******************************************************************************/
package org.eclipse.riena.ui.ridgets.swt;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.databinding.BindingException;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.BeansObservables;
import org.eclipse.core.databinding.beans.PojoObservables;
import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.value.ComputedValue;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.runtime.Assert;
import org.eclipse.swt.widgets.Control;
import org.eclipse.riena.core.annotationprocessor.AnnotationProcessor;
import org.eclipse.riena.core.marker.IMarkable;
import org.eclipse.riena.ui.core.marker.ErrorMarker;
import org.eclipse.riena.ui.core.marker.MandatoryMarker;
import org.eclipse.riena.ui.ridgets.AbstractCompositeRidget;
import org.eclipse.riena.ui.ridgets.IActionListener;
import org.eclipse.riena.ui.ridgets.IActionRidget;
import org.eclipse.riena.ui.ridgets.IBasicMarkableRidget;
import org.eclipse.riena.ui.ridgets.IMarkableRidget;
import org.eclipse.riena.ui.ridgets.IMasterDetailsActionRidgetFacade;
import org.eclipse.riena.ui.ridgets.IMasterDetailsDelegate;
import org.eclipse.riena.ui.ridgets.IMasterDetailsRidget;
import org.eclipse.riena.ui.ridgets.IRidget;
import org.eclipse.riena.ui.ridgets.IRidgetContainer;
import org.eclipse.riena.ui.ridgets.IStatuslineRidget;
import org.eclipse.riena.ui.ridgets.ITableRidget;
import org.eclipse.riena.ui.swt.AbstractMasterDetailsComposite;
/**
* Common functionality that is shared between implementations of the {@link IMasterDetailsRidget}.
* <p>
* This class defines several widget-specific abstract methods, which must be implemented by clients. It is expected that clients will write widget-specific
* subclass of {@link AbstractMasterDetailsComposite}.
*
* @since 1.2
*/
public abstract class AbstractMasterDetailsRidget extends AbstractCompositeRidget implements IMasterDetailsRidget {
private static final GlobalMandatoryMarker GLOBAL_MARKER = new GlobalMandatoryMarker();
private IObservableList rowObservables;
private IMasterDetailsDelegate delegate;
private DataBindingContext dbc;
/**
* A blank model entry for clearing the details area. Will be initialized lazily.
*/
private Object blankEntry;
/**
* The object we are currently editing; null if not editing
*/
private Object editable;
/**
* All ridgets from the details area.
*/
private IRidgetContainer detailRidgets;
/**
* If true, Apply can only complete when there are no errors in the details.
*/
private boolean applyRequiresNoErrors;
/**
* If true, Apply can only complete when there are no enabled mandatory markers in the details.
*/
private boolean applyRequiresNoMandatories;
/**
* If true, successful completion of an 'Apply' action will trigger creating a new entry.
*/
private boolean applyTriggersNew;
/**
* True when the details area is enabled, false otherwise.
*/
private boolean detailsEnabled;
/**
* If true, change events will not be processed.
*/
private boolean ignoreChanges;
/**
* True when direct writing is on. In that case any value change will be immediately be applied back to the (master) editable.
*/
private boolean isDirectWriting;
/**
* True when mandatory and error markers should be hidden when the user invokes the 'New' action or an entry is suggested programmatically. The global
* mandatory marker is excluded from hiding. The markers will re-enable as soon as the user changes a value in the details area.
* <p>
* This is the user preference for this ridget not the current state.
*/
private boolean isHideMarkersOnNew;
/**
* The ridget state of hiding (true) or not hiding markers.
*/
private boolean isHidingMarkersOnNew;
/**
* True if the global mandatory marker should be visible, false otherwise.
*
* @see GlobalMandatoryMarker
*/
private boolean isShowingGlobalMarker;
/**
* True when the editable was suggested via #suggestNewEntry. Suggested editables are always considered modified.
*/
private boolean isSuggestedEditable;
/**
* If true, the 'Remove' action will be enabled while editing a new entry. Hitting Remove will abort editing the new entry and restore the last selection.
*/
private boolean removeCancelsNew;
/**
* If true, successful completion of an 'Remove' action will trigger creating a new entry.
*/
private boolean removeTriggersNew;
/**
* If removeCancelsNew is enabled, this variable stores the element that was selected before pressing 'New'. Null if no selection is stored.
*/
private StoredSelection preNewSelection;
/**
* Facade for convenient access to {@link IActionRidget}s
*/
private ActionRidgetFacade actionRidgetFacade;
public AbstractMasterDetailsRidget() {
actionRidgetFacade = new ActionRidgetFacade();
addPropertyChangeListener(null, new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent evt) {
final String propertyName = evt.getPropertyName();
if (!hasApplyButton() || isDirectWriting || ignoreChanges || delegate == null
|| editable == null
// ignore these events:
|| (!applyRequiresNoErrors && !applyRequiresNoMandatories && IBasicMarkableRidget.PROPERTY_MARKER.equals(propertyName))
|| IRidget.PROPERTY_ENABLED.equals(propertyName)
|| "textInternal".equals(propertyName) //$NON-NLS-1$
|| IMarkableRidget.PROPERTY_OUTPUT_ONLY.equals(propertyName) || IBasicMarkableRidget.PROPERTY_MARKER_HIDING.equals(propertyName)
|| (IBasicMarkableRidget.PROPERTY_MARKER.equals(propertyName) && getApplyButtonRidget() == evt.getSource())) {
return;
}
// traceEvent(evt);
updateMarkers(evt);
updateApplyButton();
}
});
}
public final void bindToModel(final IObservableList rowObservables, final Class<? extends Object> rowClass, final String[] columnPropertyNames,
final String[] columnHeaders) {
this.rowObservables = rowObservables;
bindTableToModel(rowObservables, rowClass, columnPropertyNames, columnHeaders);
}
public final void bindToModel(final Object listHolder, final String listPropertyName, final Class<? extends Object> rowClass,
final String[] columnPropertyNames, final String[] headerNames) {
IObservableList rowObservableList;
if (AbstractSWTWidgetRidget.isBean(rowClass)) {
rowObservableList = BeansObservables.observeList(listHolder, listPropertyName);
} else {
rowObservableList = PojoObservables.observeList(listHolder, listPropertyName);
}
bindToModel(rowObservableList, rowClass, columnPropertyNames, headerNames);
}
/**
* @since 3.0
*/
public boolean canSuggest() {
boolean result = true;
if (areDetailsChanged()) {
result = getUIControl().confirmDiscardChanges();
}
return result;
}
@Override
public void configureRidgets() {
configureTableRidget();
if (hasNewButton()) {
getNewButtonRidget().addListener(new IActionListener() {
public void callback() {
if (canAdd()) {
handleAdd();
}
}
});
}
if (hasRemoveButton()) {
getRemoveButtonRidget().addListener(new IActionListener() {
public void callback() {
if (hasPreNewSelection()) {
if (canCancel()) {
handleCancel();
}
} else if (canRemove()) {
handleRemove();
}
}
});
}
if (hasApplyButton()) {
getApplyButtonRidget().addListener(new IActionListener() {
public void callback() {
if (canApply()) {
handleApply();
}
}
});
}
setEnabled(false, detailsEnabled);
final IObservableValue viewerSelection = getSelectionObservable();
Assert.isLegal(dbc == null);
if (hasRemoveButton()) {
dbc = new DataBindingContext();
bindEnablementToValue(dbc, getRemoveButtonRidget(), new ComputedValue(Boolean.TYPE) {
@Override
protected Object calculate() {
return Boolean.valueOf(viewerSelection.getValue() != null);
}
});
}
final PropertyChangeListener directWritingPCL = new DirectWritingPropertyChangeListener();
for (final IRidget ridget : getDetailRidgetContainer().getRidgets()) {
ridget.addPropertyChangeListener(directWritingPCL);
}
}
private IRidgetContainer getDetailRidgetContainer() {
if (detailRidgets == null) {
detailRidgets = new DetailRidgetContainer();
}
return detailRidgets;
}
public final IMasterDetailsDelegate getDelegate() {
return this.delegate;
}
public final Object getSelection() {
return getSelectionObservable().getValue();
}
@Override
public AbstractMasterDetailsComposite getUIControl() {
return (AbstractMasterDetailsComposite) super.getUIControl();
}
public boolean isApplyRequiresNoErrors() {
return applyRequiresNoErrors;
}
public boolean isApplyRequiresNoMandatories() {
return applyRequiresNoMandatories;
}
public boolean isApplyTriggersNew() {
return applyTriggersNew && hasNewButton();
}
public boolean isDirectWriting() {
return isDirectWriting;
}
/**
* @since 3.0
*/
public boolean isHideMandatoryAndErrorMarkersOnNewEntries() {
return isHideMarkersOnNew;
}
/**
* @since 3.0
*/
public boolean isRemoveCancelsNew() {
return removeCancelsNew && hasRemoveButton();
}
/**
* @since 3.0
*/
public boolean isRemoveTriggersNew() {
return removeTriggersNew && hasRemoveButton();
}
public void setApplyRequiresNoErrors(final boolean requiresNoErrors) {
if (applyRequiresNoErrors != requiresNoErrors) {
applyRequiresNoErrors = requiresNoErrors;
updateApplyButton();
}
}
public void setApplyRequiresNoMandatories(final boolean requiresNoMandatories) {
if (applyRequiresNoMandatories != requiresNoMandatories) {
applyRequiresNoMandatories = requiresNoMandatories;
updateApplyButton();
}
}
public void setApplyTriggersNew(final boolean triggersNew) {
applyTriggersNew = triggersNew;
}
public void setColumnWidths(final Object[] widths) {
((ITableRidget) getTableRidget()).setColumnWidths(widths);
}
public final void setDelegate(final IMasterDetailsDelegate delegate) {
Assert.isLegal(this.delegate == null, "setDelegate can only be called once"); //$NON-NLS-1$
Assert.isLegal(delegate != null, "delegate cannot be null"); //$NON-NLS-1$
this.delegate = delegate;
delegate.configureRidgets(getDetailRidgetContainer());
AnnotationProcessor.getInstance().processMethods(getDetailRidgetContainer(), delegate);
}
public void setDirectWriting(final boolean directWriting) {
if (directWriting != isDirectWriting) {
isDirectWriting = directWriting;
if (hasApplyButton()) {
getApplyButtonRidget().setVisible(!directWriting);
}
}
}
/**
* @since 3.0
*/
public void setHideMandatoryAndErrorMarkersOnNewEntries(final boolean hideMarkers) {
isHideMarkersOnNew = hideMarkers;
}
/**
* @since 3.0
*/
public void setRemoveCancelsNew(final boolean cancelsNew) {
removeCancelsNew = cancelsNew;
}
/**
* @since 3.0
*/
public void setRemoveTriggersNew(final boolean triggersNew) {
removeTriggersNew = triggersNew;
}
public void setSelection(final Object newSelection) {
setTableSelection(newSelection);
handleSelectionChange(newSelection);
final AbstractMasterDetailsComposite control = getUIControl();
if (control != null) {
revealTableSelection();
}
}
/**
* @since 3.0
*/
public void suggestNewEntry() {
final Object entry = delegate.createMasterEntry();
suggestNewEntry(entry, false);
}
public void suggestNewEntry(final Object entry) {
suggestNewEntry(entry, true);
}
/**
* @since 3.0
*/
public void suggestNewEntry(final Object entry, final boolean treatAsDirty) {
ignoreChanges = true;
try {
if (!isDirectWriting) {
clearTableSelection();
editable = entry;
delegate.prepareItemSelected(editable);
updateMarkers(isHideMarkersOnNew, isShowGlobalMarker());
setEnabled(treatAsDirty, true);
isSuggestedEditable = treatAsDirty;
updateDetails(editable);
ignoreChanges = true;
delegate.itemSelected(editable);
} else {
editable = entry;
delegate.itemCreated(editable);
rowObservables.add(editable);
getTableRidget().updateFromModel();
setSelection(editable);
isSuggestedEditable = false; // direct writing -> not dirty
updateMarkers(isHideMarkersOnNew, isShowGlobalMarker());
setEnabled(false, true); // directy writing -> disable apply
updateDetails(editable);
}
// set focus to first focusable ridget in detailsarea
if (!setFocusToFirstDetailsRidget()) {
getApplyButtonRidget().requestFocus();
}
} finally {
ignoreChanges = false;
}
}
/**
*
*/
private boolean setFocusToFirstDetailsRidget() {
final Collection<? extends IRidget> ridgets = getDetailRidgetContainer().getRidgets();
final IRidget[] ridgetArray = ridgets.toArray(new IRidget[ridgets.size()]);
if (!ridgets.isEmpty()) {
for (int i = ridgetArray.length - 1; i >= 0; i--) {
final IRidget current = ridgetArray[i];
if (current.isEnabled() && current.isFocusable()) {
if (current instanceof IMarkableRidget && ((IMarkableRidget) current).isOutputOnly()) {
continue;
}
current.requestFocus();
return true;
}
}
}
return false;
}
public final void updateApplyButton() {
if (!hasApplyButton()) {
return;
}
if (applyRequiresNoErrors || applyRequiresNoMandatories) {
// inlined for performance
// isEnabled = areDetailsChanged() && noErrors && noMandatories
final boolean isEnabled = areDetailsChanged() && (applyRequiresNoErrors ? !hasErrors(getDetailRidgetContainer()) : true)
&& (applyRequiresNoMandatories ? !hasMandatories(getDetailRidgetContainer()) : true);
getApplyButtonRidget().setEnabled(isEnabled);
} else {
getApplyButtonRidget().setEnabled(areDetailsChanged());
}
if (delegate != null) {
delegate.updateMasterDetailsActionRidgets(actionRidgetFacade, editable);
}
}
@Override
public void updateFromModel() {
checkDelegate();
super.updateFromModel();
}
// protected methods
// //////////////////
@Override
protected void checkUIControl(final Object uiControl) {
checkType(uiControl, AbstractMasterDetailsComposite.class);
}
protected abstract void bindTableToModel(IObservableList rowObservables, Class<? extends Object> rowClass, String[] columnPropertyNames,
String[] columnHeaders);
protected abstract void configureTableRidget();
protected abstract void clearTableSelection();
protected abstract Object getTableSelection();
protected abstract IObservableValue getSelectionObservable();
protected abstract void revealTableSelection();
protected abstract void setTableSelection(Object value);
protected final boolean areDetailsChanged() {
if (detailsEnabled) {
if (editable != null) {
return isSuggestedEditable || delegate.isChanged(editable, delegate.getWorkingCopy());
}
}
return false;
}
protected void handleSelectionChange(final Object newSelection) {
final IMarkableRidget table = getRidget(IMarkableRidget.class, AbstractMasterDetailsComposite.BIND_ID_TABLE);
final Collection<ErrorMarker> errorMarkers = table.getMarkersOfType(ErrorMarker.class);
for (final ErrorMarker m : errorMarkers) {
table.removeMarker(m);
}
updateBorder(table);
ignoreChanges = true;
try {
delegate.prepareItemSelected(newSelection);
updateBorder(table);
delegate.updateMasterDetailsActionRidgets(actionRidgetFacade, newSelection);
if (newSelection != null) { // selection changed
if (editable == null) {
updateMarkers(true, false); // workaround for 327177
}
editable = newSelection;
setEnabled(false, true);
updateDetails(editable);
updateMarkers(false, false);
} else { // nothing selected
setEnabled(false, false); // workaround for 327177
clearSelection();
setEnabled(false, false);
updateMarkers(false, false);
}
ignoreChanges = true;
delegate.itemSelected(newSelection);
clearPreNewSelection();
} finally {
ignoreChanges = false;
for (final ErrorMarker m : errorMarkers) {
table.addMarker(m);
}
updateBorder(table);
}
}
/**
* Trying to fix an ugly border decoration bug
*
* @param ridget
* Ridget whose UI control should be updated
*/
private void updateBorder(final IRidget ridget) {
if (ridget == null || !(ridget.getUIControl() instanceof Control)) {
return;
}
((Control) ridget.getUIControl()).getDisplay().update();
}
@Override
protected boolean isUIControlVisible() {
return getUIControl().isVisible();
}
@Override
protected final void updateEnabled() {
final AbstractMasterDetailsComposite control = getUIControl();
if (control != null) {
if (!isEnabled()) {
clearSelection();
clearTableSelection();
final Collection<? extends IRidget> ridgets = getRidgets();
for (final IRidget ridget : ridgets) {
ridget.setEnabled(false);
}
} else {
if (getTableRidget() != null) {
getTableRidget().setEnabled(true);
}
if (getNewButtonRidget() != null) {
getNewButtonRidget().setEnabled(true);
}
}
control.setEnabled(isEnabled());
}
}
@Override
protected final void updateToolTipText() {
final AbstractMasterDetailsComposite control = getUIControl();
if (control != null) {
control.setToolTipText(getToolTipText());
}
}
@Override
protected final void updateVisible() {
final AbstractMasterDetailsComposite control = getUIControl();
if (control != null) {
control.setVisible(!isMarkedHidden());
}
}
// helping methods
// ////////////////
private void assertIsBoundToModel() {
if (rowObservables == null) {
throw new BindingException("ridget not bound to model"); //$NON-NLS-1$
}
}
private void bindEnablementToValue(final DataBindingContext dbc, final IRidget ridget, final IObservableValue value) {
Assert.isNotNull(ridget);
Assert.isNotNull(value);
final IObservableValue ridgetObservable = BeansObservables.observeValue(ridget, IRidget.PROPERTY_ENABLED);
dbc.bindValue(ridgetObservable, value, null, null);
ridgetObservable.addChangeListener(new IChangeListener() {
public void handleChange(final ChangeEvent event) {
if (delegate != null) {
delegate.updateMasterDetailsActionRidgets(actionRidgetFacade, getTableSelection());
}
}
});
}
private boolean canAdd() {
boolean result = true;
if (areDetailsChanged()) {
result = getUIControl().confirmDiscardChanges();
}
return result;
}
private boolean canApply() {
final String reason = delegate.isValid(getDetailRidgetContainer());
if (reason != null) {
getUIControl().warnApplyFailed(reason);
}
return reason == null;
}
private boolean canApplyDirectly() {
final boolean noErrors = applyRequiresNoErrors ? !hasErrors(getDetailRidgetContainer()) : true;
final boolean noMandatories = applyRequiresNoMandatories ? !hasMandatories(getDetailRidgetContainer()) : true;
return noErrors && noMandatories && (delegate.isValid(getDetailRidgetContainer()) == null);
}
private boolean canCancel() {
boolean result = true;
if (areDetailsChanged()) {
result = getUIControl().confirmDiscardChanges();
}
return result;
}
private boolean canRemove() {
final Object selection = getSelection();
Assert.isNotNull(selection);
final String reason = delegate.isRemovable(selection);
if (reason != null) {
getUIControl().warnRemoveFailed(reason);
return false;
}
return getUIControl().confirmRemove(selection);
}
private void checkDelegate() {
if (delegate == null) {
throw new IllegalStateException("no delegate: call setDelegate(...)"); //$NON-NLS-1$
}
}
private void clearPreNewSelection() {
preNewSelection = null;
}
/**
* @since 3.0
*/
protected void clearSelection() {
updateDetails(getBlankEntry());
editable = null;
}
private Object getBlankEntry() {
if (blankEntry == null) {
blankEntry = delegate.createMasterEntry();
}
return blankEntry;
}
private Control getDetailsControl() {
Control result = null;
final AbstractMasterDetailsComposite control = getUIControl();
if (control != null) {
result = control.getDetails();
}
return result;
}
private IRidget getTableRidget() {
// this is not necessarily an ITableRidget, can be any IRidget
return getRidget(AbstractMasterDetailsComposite.BIND_ID_TABLE);
}
private IActionRidget getNewButtonRidget() {
return getRidget(IActionRidget.class, AbstractMasterDetailsComposite.BIND_ID_NEW);
}
private IActionRidget getRemoveButtonRidget() {
return getRidget(IActionRidget.class, AbstractMasterDetailsComposite.BIND_ID_REMOVE);
}
private IActionRidget getApplyButtonRidget() {
return getRidget(IActionRidget.class, AbstractMasterDetailsComposite.BIND_ID_APPLY);
}
private boolean hasErrors(final IRidgetContainer ridgetContainer) {
boolean foundMarker = hasErrors(ridgetContainer.getRidgets());
if (!foundMarker) {
final Iterator<? extends IRidget> iter = ridgetContainer.getRidgets().iterator();
while (!foundMarker && iter.hasNext()) {
final IRidget ridget = iter.next();
if (ridget instanceof IRidgetContainer) {
foundMarker = hasErrors((IRidgetContainer) ridget);
}
}
}
return foundMarker;
}
private boolean hasErrors(final Collection<? extends IRidget> ridgets) {
for (final IRidget ridget : ridgets) {
if (ridget instanceof IMarkableRidget) {
final IMarkableRidget markableRidget = (IMarkableRidget) ridget;
if (markableRidget.isEnabled() && markableRidget.getMarkersOfType(ErrorMarker.class).size() > 0) {
return true;
}
}
}
return false;
}
private boolean hasMandatories(final IRidgetContainer ridgetContainer) {
boolean foundMarker = hasMandatories(ridgetContainer.getRidgets());
if (!foundMarker) {
final Iterator<? extends IRidget> iter = ridgetContainer.getRidgets().iterator();
while (!foundMarker && iter.hasNext()) {
final IRidget ridget = iter.next();
if (ridget instanceof IRidgetContainer) {
foundMarker = hasMandatories((IRidgetContainer) ridget);
}
}
}
return foundMarker;
}
private boolean hasMandatories(final Collection<? extends IRidget> ridgets) {
for (final IRidget ridget : ridgets) {
if (ridget instanceof IMarkableRidget) {
final IMarkableRidget markableRidget = (IMarkableRidget) ridget;
if (markableRidget.isEnabled()) {
for (final MandatoryMarker marker : markableRidget.getMarkersOfType(MandatoryMarker.class)) {
if (!marker.isDisabled()) {
return true;
}
}
}
}
}
return false;
}
private boolean hasNewButton() {
return getNewButtonRidget() != null;
}
private boolean hasPreNewSelection() {
return preNewSelection != null;
}
private boolean hasRemoveButton() {
return getRemoveButtonRidget() != null;
}
private boolean hasApplyButton() {
return getApplyButtonRidget() != null;
}
private boolean isShowGlobalMarker() {
return !delegate.isValidMaster(this);
}
private void setEnabled(final boolean applyEnabled, final boolean detailsEnabled) {
ignoreChanges = true;
try {
if (hasApplyButton()) {
getApplyButtonRidget().setEnabled(applyEnabled);
}
if (!applyEnabled) {
isSuggestedEditable = false;
}
this.detailsEnabled = detailsEnabled;
for (final IRidget ridget : getDetailRidgetContainer().getRidgets()) {
ridget.setEnabled(detailsEnabled);
}
} finally {
ignoreChanges = false;
}
}
private void setFocusToDetails() {
final Control focusable = getDetailsControl();
if (focusable != null) {
focusable.getDisplay().asyncExec(new Runnable() {
public void run() {
if (!focusable.isDisposed()) {
clearTableSelection();
focusable.setFocus();
}
}
});
}
}
private void setFocusToTable() {
final AbstractMasterDetailsComposite control = getUIControl();
if (control != null) {
final Control table = control.getTable();
table.getDisplay().asyncExec(new Runnable() {
public void run() {
if (!table.isDisposed()) {
table.setFocus();
// this has to be after table.setFocus() otherwise
// the 'focus' rectangle is sometimes lost
clearTableSelection();
}
}
});
}
}
private void storePreNewSelection() {
Assert.isTrue(removeCancelsNew);
final Object tableSelection = getTableSelection();
if (preNewSelection == null || tableSelection != null) {
// the clause above ensures the last selection is kept, if 'New' button
// is pressed several times.
preNewSelection = new StoredSelection(tableSelection);
}
clearTableSelection();
if (hasRemoveButton()) {
getRemoveButtonRidget().setEnabled(true);
}
}
@SuppressWarnings("unused")
private void traceEvent(final PropertyChangeEvent evt) {
final String className = evt.getSource().getClass().getSimpleName();
System.out.println(String.format("prop: %s %s", evt.getPropertyName(), className)); //$NON-NLS-1$
}
/**
* @since 3.0
*/
protected void updateDetails(final Object masterEntry) {
Assert.isNotNull(masterEntry);
ignoreChanges = true;
try {
delegate.copyMasterEntry(masterEntry, delegate.getWorkingCopy());
delegate.updateDetails(getDetailRidgetContainer());
} finally {
ignoreChanges = false;
}
}
@SuppressWarnings("unchecked")
private synchronized void updateMarkers(final boolean hideMarkers, final boolean showGlobalMarker) {
if (isHidingMarkersOnNew != hideMarkers) {
isHidingMarkersOnNew = hideMarkers;
for (final IRidget ridget : getDetailRidgetContainer().getRidgets()) {
if (ridget instanceof IBasicMarkableRidget) {
final IBasicMarkableRidget markableRidget = (IBasicMarkableRidget) ridget;
if (isHidingMarkersOnNew) {
if (showGlobalMarker) {
markableRidget.hideMarkersOfType(ErrorMarker.class);
} else {
markableRidget.hideMarkersOfType(ErrorMarker.class, MandatoryMarker.class);
}
} else {
markableRidget.showMarkersOfType(ErrorMarker.class, MandatoryMarker.class);
}
}
}
}
if (isShowingGlobalMarker != showGlobalMarker) {
isShowingGlobalMarker = showGlobalMarker;
for (final IRidget ridget : getDetailRidgetContainer().getRidgets()) {
if (!(ridget instanceof IMarkable)) {
continue;
}
final IMarkable markable = (IMarkable) ridget;
if (isShowingGlobalMarker) {
markable.addMarker(GLOBAL_MARKER);
} else {
markable.removeMarker(GLOBAL_MARKER);
}
}
}
}
private void updateMarkers(final PropertyChangeEvent evt) {
final String propertyName = evt.getPropertyName();
if (evt.getSource() == getTableRidget() || IRidget.PROPERTY_SHOWING.equals(propertyName)) {
return;
}
updateMarkers(false, false);
}
/**
* Non API; public for testing only.
*/
public void handleAdd() {
if (!isDirectWriting) {
// create the editable and update the details
editable = delegate.createMasterEntry();
delegate.itemCreated(editable);
updateMarkers(isHideMarkersOnNew, isShowGlobalMarker());
setEnabled(false, true);
updateDetails(editable);
if (isRemoveCancelsNew()) {
storePreNewSelection();
} else {
clearTableSelection();
}
getUIControl().getDetails().setFocus();
} else {
// create the editable, add it to the table, update the details
editable = delegate.createMasterEntry();
delegate.itemCreated(editable);
rowObservables.add(editable);
getTableRidget().updateFromModel();
setSelection(editable);
updateMarkers(isHideMarkersOnNew, isShowGlobalMarker());
setEnabled(false, true);
updateDetails(editable);
getUIControl().getDetails().setFocus();
}
}
/**
* Non API; public for testing only.
*/
public void handleApply() {
assertIsBoundToModel();
Assert.isNotNull(editable);
delegate.prepareItemApplied(editable);
delegate.copyWorkingCopy(delegate.getWorkingCopy(), editable);
if (!rowObservables.contains(editable)) { // add to table
rowObservables.add(editable);
getTableRidget().updateFromModel();
setTableSelection(editable);
} else { // update
getTableRidget().updateFromModel();
}
revealTableSelection();
delegate.itemApplied(editable);
clearPreNewSelection();
if (!isDirectWriting && isApplyTriggersNew()) {
handleAdd(); // automatically hit the 'new/add' button
setFocusToDetails();
} else {
setEnabled(false, false);
updateMarkers(false, false);
setFocusToTable();
}
}
/**
* Non API; public for testing only.
*
* @since 3.0
*/
public void handleCancel() {
assertIsBoundToModel();
if (hasRemoveButton()) {
getRemoveButtonRidget().setEnabled(false);
}
final Object oldSelection = preNewSelection.getSelection();
if (oldSelection == null && !isDirectWriting() && isRemoveTriggersNew()) {
clearPreNewSelection();
handleAdd(); // automatically hit the 'new/add' button
setFocusToDetails();
} else {
setSelection(oldSelection);
clearPreNewSelection();
}
}
/**
* Non API; public for testing only.
*/
public void handleRemove() {
assertIsBoundToModel();
final Object selection = getSelection();
Assert.isNotNull(selection);
rowObservables.remove(selection);
setEnabled(false, false); // workaround for 327177
clearSelection();
clearTableSelection();
getTableRidget().updateFromModel();
setEnabled(false, false);
delegate.itemRemoved(selection);
if (!isDirectWriting && isRemoveTriggersNew()) {
handleAdd(); // automatically hit the 'new/add' button
setFocusToDetails();
}
}
// helping classes
// ////////////////
private final class ActionRidgetFacade implements IMasterDetailsActionRidgetFacade {
public IActionRidget getAddActionRidget() {
return getRidget(IActionRidget.class, AbstractMasterDetailsComposite.BIND_ID_NEW);
}
public IActionRidget getApplyActionRidget() {
return getRidget(IActionRidget.class, AbstractMasterDetailsComposite.BIND_ID_APPLY);
}
public IActionRidget getRemoveActionRidget() {
return getRidget(IActionRidget.class, AbstractMasterDetailsComposite.BIND_ID_REMOVE);
}
}
/**
* IRidgetContainer exposing the 'detail' ridgets only (instead of all ridgets).
*/
private final class DetailRidgetContainer implements IRidgetContainer {
private List<IRidget> detailRidgets;
private boolean configured = false;
public void addRidget(final String id, final IRidget ridget) {
throw new UnsupportedOperationException("not supported"); //$NON-NLS-1$
}
public boolean removeRidget(final String id) {
throw new UnsupportedOperationException("not supported"); //$NON-NLS-1$
}
public void configureRidgets() {
throw new UnsupportedOperationException("not supported"); //$NON-NLS-1$
}
public <R extends IRidget> R getRidget(final String id) {
return (R) AbstractMasterDetailsRidget.this.getRidget(id);
}
public <R extends IRidget> R getRidget(final Class<R> ridgetClazz, final String id) {
return AbstractMasterDetailsRidget.this.getRidget(ridgetClazz, id);
}
public Collection<? extends IRidget> getRidgets() {
if (detailRidgets == null || detailRidgets.isEmpty()) {
detailRidgets = getDetailRidgets();
}
return detailRidgets;
}
private List<IRidget> getDetailRidgets() {
final List<IRidget> result = new ArrayList<IRidget>(AbstractMasterDetailsRidget.this.getRidgets());
result.remove(getNewButtonRidget());
result.remove(getRemoveButtonRidget());
result.remove(getApplyButtonRidget());
result.remove(getTableRidget());
return result;
}
/**
* {@inheritDoc}
*/
public void setConfigured(final boolean configured) {
this.configured = configured;
}
/**
* {@inheritDoc}
*/
public boolean isConfigured() {
return configured;
}
public void setStatuslineToShowMarkerMessages(final IStatuslineRidget statuslineToShowMarkerMessages) {
// ignore
}
}
private final class DirectWritingPropertyChangeListener implements PropertyChangeListener {
public void propertyChange(final PropertyChangeEvent evt) {
final String propertyName = evt.getPropertyName();
if (!isDirectWriting || ignoreChanges || delegate == null || editable == null
// ignore these events:
|| (!applyRequiresNoErrors && !applyRequiresNoMandatories && IBasicMarkableRidget.PROPERTY_MARKER.equals(propertyName))
|| IRidget.PROPERTY_ENABLED.equals(propertyName) || "textInternal".equals(propertyName) //$NON-NLS-1$
|| IMarkableRidget.PROPERTY_OUTPUT_ONLY.equals(propertyName) || IBasicMarkableRidget.PROPERTY_MARKER_HIDING.equals(propertyName)) {
return;
}
// traceEvent(evt);
updateMarkers(evt);
if (canApplyDirectly()) {
delegate.prepareItemApplied(editable);
// this is only reached when direct writing is on and one of 'interesting' events happens
delegate.copyWorkingCopy(delegate.getWorkingCopy(), editable);
delegate.itemApplied(editable);
getTableRidget().updateFromModel();
// we are already editing, so we want to invoke getTR().setSelection(editable) instead
// of setSelection(editable). This will just select the editable in the table.
setTableSelection(editable);
}
}
}
/**
* This {@link MandatoryMarker} is always enabled and is shared with all ridgets in the details area. It is added to the ridgets in the details when the
* result of {@link IMasterDetailsDelegate#isValidMaster(IMasterDetailsRidget)} is false.
*/
private static final class GlobalMandatoryMarker extends MandatoryMarker {
GlobalMandatoryMarker() {
super(false);
}
/**
* Returns false always.
*/
@Override
public boolean isDisabled() {
return false;
}
}
/**
* A stored selection.
*/
private static final class StoredSelection {
private final Object selection;
StoredSelection(final Object selection) {
this.selection = selection;
}
private Object getSelection() {
return selection;
}
}
}