/*******************************************************************************
* 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.views;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.riena.ui.ridgets.IMarkableRidget;
import org.eclipse.riena.ui.ridgets.IRidget;
import org.eclipse.riena.ui.ridgets.IRidgetContainer;
import org.eclipse.riena.ui.ridgets.controller.AbstractWindowController.Blocker;
import org.eclipse.riena.ui.ridgets.controller.ControllerHelper;
import org.eclipse.riena.ui.swt.utils.SwtUtilities;
/**
* Manages the blocking behavior of navigation nodes and dialogs.
*
* @since 6.0
*/
public abstract class BlockHelper implements Blocker {
private boolean blocked;
private Cursor oldCursor;
/**
* Keep a reference to the control that was last focused for a given controller id.
*
* @see #getControllerId()
* @see #canRestoreFocus()
*/
private final Map<Integer, Control> focusControlMap = new HashMap<Integer, Control>(1);
private Composite contentComposite;
@Override
public void setBlocked(final boolean blocked) {
if (!getParentComposite().isDisposed()) {
if (blocked) {
blockView();
} else {
unBlockView();
}
}
}
@Override
public boolean isBlocked() {
return blocked;
}
/**
* @param contentComposite
*/
public void registerOnContentComposite(final Composite contentComposite) {
this.contentComposite = contentComposite;
final FocusListener focusListener = new FocusListener();
contentComposite.getDisplay().addFilter(SWT.FocusIn, focusListener);
contentComposite.addDisposeListener(new DisposeListener() {
public void widgetDisposed(final DisposeEvent event) {
event.widget.getDisplay().removeFilter(SWT.FocusIn, focusListener);
}
});
}
private void blockView() {
if (!blocked) {
oldCursor = getParentComposite().getCursor();
if (getController() != null) {
for (final IRidget ridget : getController().getRidgets()) {
if (ridget.hasFocus()) {
final Object uiControl = ridget.getUIControl();
if (uiControl instanceof Control) {
saveFocus((Control) uiControl);
}
}
}
}
getParentComposite().setCursor(getWaitCursor());
contentComposite.setEnabled(false);
blocked = true;
}
}
private void unBlockView() {
blocked = false;
getParentComposite().setCursor(oldCursor);
contentComposite.setEnabled(true);
contentComposite.setRedraw(false);
contentComposite.setRedraw(true);
if (shouldRestoreFocus() && canRestoreFocus()) {
setFocus();
}
oldCursor = null;
// Update markers here, because for some controls (ChoiceComposite, CComboRidget, DatePickerComposite) the mandatory marker was not visualized
// after unblock when set while block
// ruv 356
if (getController() != null) {
for (final IRidget ridget : getController().getRidgets()) {
if (ridget instanceof IMarkableRidget) {
((IMarkableRidget) ridget).updateMarkers();
}
}
ControllerHelper.restoreFocusRequestFromRidget(getController().getRidgets(), blocked);
// getController().restoreFocusRequestFromRidget(getController().getRidgets());
}
}
/**
* This implementation will automatically focus on the control that had previously the focus, or, the first focusable control.
* <p>
* You may overwrite it, but it typically is not necessary to do so. If you still want to use the 'restore focus to last control' functionality, check
* {@link #canRestoreFocus()} and the invoke this method.
*/
public void setFocus() {
if (canRestoreFocus()) {
final Integer id = Integer.valueOf(getControllerId());
final Control lastFocusedControl = focusControlMap.get(id);
lastFocusedControl.setFocus();
} else if (canFocusOnRidget()) {
getFocusRidget().requestFocus();
} else {
contentComposite.setFocus();
}
}
protected abstract IRidgetContainer getController();
protected abstract Control getParentComposite();
protected abstract boolean shouldRestoreFocus();
protected abstract IRidget getFocusRidget();
private boolean canFocusOnRidget() {
boolean result = false;
final IRidget ridget = getFocusRidget();
if (ridget != null) {
result = ridget.isFocusable() && ridget.isEnabled() && ridget.isVisible();
if (ridget instanceof IMarkableRidget) {
result &= !((IMarkableRidget) ridget).isOutputOnly();
}
}
return result;
}
private void saveFocus(final Control control) {
final int id = getControllerId();
if (id != 0) {
focusControlMap.put(Integer.valueOf(id), control);
}
}
/**
* Returns the id (hashcode) of the controller if available, or zero.
*/
private int getControllerId() {
final IRidgetContainer controller = getController();
return controller == null ? 0 : controller.hashCode();
}
/**
* Returns true if {@link #setFocus()} can restore the focus to the control that last had the focus in this view; false otherwise.
*
*/
public final boolean canRestoreFocus() {
final Integer id = Integer.valueOf(getControllerId());
final Control control = focusControlMap.get(id);
return !SwtUtilities.isDisposed(control);
}
private Cursor getWaitCursor() {
return contentComposite.getDisplay().getSystemCursor(SWT.CURSOR_WAIT);
}
/**
* Keeps track of the last focused control within this view.
*/
private final class FocusListener implements Listener {
public void handleEvent(final Event event) {
if (contentComposite.isVisible() && event.widget instanceof Control) {
final Control control = (Control) event.widget;
if (contains(contentComposite, control)) {
saveFocus(control);
}
}
}
private boolean contains(final Composite container, final Control control) {
boolean result = false;
Composite parent = control.getParent();
// what if the contentComposite fires the event itself?
while (!result && parent != null) {
result = container == parent;
parent = parent.getParent();
}
return result;
}
}
}