blob: e595583d06da1e2faf56e7bd5a0a77a1da642e30 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.app.place;
import com.google.gwt.app.place.Activity.Display;
import com.google.gwt.event.shared.HandlerManager;
/**
* <p>
* <span style="color:red">Experimental API: This class is still under rapid
* development, and is very likely to be deleted. Use it at your own risk.
* </span>
* </p>
* Manages {@link Activity} objects that should be kicked off in response to
* {@link PlaceChangeEvent} events. Each activity can start itself
* asynchronously, and provides a widget to be shown when it's ready to run.
*
* @param <P> the type of {@link Place} objects that this ActivityManager can
* map to Activities
*/
public class ActivityManager<P extends Place> implements
PlaceChangeEvent.Handler<P>, PlaceChangeRequestedEvent.Handler<P> {
/**
* Wraps our real display to prevent an Activity from taking it over if it is
* not the currentActivity.
*/
private class ProtectedDisplay implements Display {
private final Activity activity;
ProtectedDisplay(Activity activity) {
this.activity = activity;
}
public void showActivityWidget(IsWidget view) {
if (this.activity == ActivityManager.this.currentActivity) {
startingNext = false;
display.showActivityWidget(view);
}
}
}
private final ActivityMapper<P> mapper;
private final HandlerManager eventBus;
private Activity currentActivity;
private Activity.Display display;
private boolean startingNext = false;
/**
* Create an ActivityManager. Next call {@link #setDisplay} and
* {@link #activate}.
*
* @param mapper finds the {@link Activity} for a given {@link Place}
* @param eventBus source of {@link PlaceChangeEvent} and
* {@link PlaceChangeRequestedEvent} events.
*/
public ActivityManager(ActivityMapper<P> mapper, HandlerManager eventBus) {
this.mapper = mapper;
this.eventBus = eventBus;
}
/**
* Deactive the current activity, find the next one from our ActivityMapper,
* and start it.
*
* @see PlaceChangeEvent.Handler#onPlaceChange(PlaceChangeEvent)
*/
public void onPlaceChange(PlaceChangeEvent<P> event) {
Activity nextActivity = mapper.getActivity(event.getNewPlace());
if (currentActivity == nextActivity) {
return;
}
if (startingNext) {
currentActivity.onCancel();
currentActivity = null;
startingNext = false;
} else if (currentActivity != null) {
/*
* TODO until caching is in place, relying on stopped activities to be
* good citizens to reduce flicker. This makes me very nervous.
*/
// display.showActivityWidget(null);
currentActivity.onStop();
}
if (nextActivity == null) {
display.showActivityWidget(null);
currentActivity = null;
return;
}
currentActivity = nextActivity;
startingNext = true;
/*
* Now start the thing. Wrap the actual display with a per-call instance
* that protects the display from canceled or stopped activities, and which
* maintain our startingNext state.
*/
currentActivity.start(new ProtectedDisplay(currentActivity));
}
/**
* Reject the place change if the current is not willing to stop.
*
* @see PlaceChangeRequestedEvent.Handler#onPlaceChangeRequested(PlaceChangeRequestedEvent)
*/
public void onPlaceChangeRequested(PlaceChangeRequestedEvent<P> event) {
if (!event.isRejected()) {
/*
* TODO Allow asynchronous willClose check? Could have the event object
* vend callbacks. Place change doesn't happen until all vended callbacks,
* if any, reply with yes. Would likely need to add onPlaceChangeCanceled?
*
* Complicated, but I really want to keep AM and PC isolated. Alternative
* is to mash them together and take place conversation off the event bus.
* And it's still complicated, just very slightly less so.
*
* Let's see if a real use case pops up.
*/
if (currentActivity != null && !currentActivity.willStop()) {
event.reject();
}
}
}
/**
* Sets the display for the receiver, and has the side effect of starting or
* stopping its monitoring the event bus for place change events.
* <p>
* If you are disposing of an ActivityManager, it is important to call
* setDisplay(null) to get it to deregister from the event bus, so that it can
* be garbage collected.
*
* @param display
*/
public void setDisplay(Activity.Display display) {
boolean wasActive = (null != this.display);
boolean willBeActive = (null != display);
this.display = display;
if (wasActive != willBeActive) {
updateHandlers(willBeActive);
}
}
private void updateHandlers(boolean activate) {
if (activate) {
eventBus.addHandler(PlaceChangeEvent.TYPE, this);
eventBus.addHandler(PlaceChangeRequestedEvent.TYPE, this);
} else {
eventBus.removeHandler(PlaceChangeEvent.TYPE, this);
eventBus.removeHandler(PlaceChangeRequestedEvent.TYPE, this);
}
}
}