| /* |
| * 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); |
| } |
| } |
| } |