/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.waveprotocol.box.webclient.client;
import com.google.gwt.dom.client.Element;
import org.waveprotocol.box.webclient.search.WaveStore;
import org.waveprotocol.box.webclient.widget.frame.FramedPanel;
import org.waveprotocol.wave.client.StageOne;
import org.waveprotocol.wave.client.StageThree;
import org.waveprotocol.wave.client.StageTwo;
import org.waveprotocol.wave.client.StageZero;
import org.waveprotocol.wave.client.Stages;
import org.waveprotocol.wave.client.account.ProfileManager;
import org.waveprotocol.wave.client.common.util.AsyncHolder;
import org.waveprotocol.wave.client.common.util.AsyncHolder.Accessor;
import org.waveprotocol.wave.client.common.util.LogicalPanel;
import org.waveprotocol.wave.client.wavepanel.impl.focus.FocusBlipSelector;
import org.waveprotocol.wave.client.wavepanel.impl.focus.FocusFramePresenter;
import org.waveprotocol.wave.client.wavepanel.impl.focus.ViewTraverser;
import org.waveprotocol.wave.client.wavepanel.impl.reader.Reader;
import org.waveprotocol.wave.client.wavepanel.view.BlipView;
import org.waveprotocol.wave.client.wavepanel.view.dom.ModelAsViewProvider;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.BlipQueueRenderer;
import org.waveprotocol.wave.model.conversation.ConversationView;
import org.waveprotocol.wave.model.document.WaveContext;
import org.waveprotocol.wave.model.id.IdGenerator;
import org.waveprotocol.wave.model.wave.ParticipantId;
import org.waveprotocol.wave.model.waveref.WaveRef;
import java.util.Set;
/**
* Stages for loading the undercurrent Wave Panel
*
* @author zdwang@google.com (David Wang)
*/
public class StagesProvider extends Stages {
private final static AsyncHolder<Object> HALT = new AsyncHolder<Object>() {
@Override
public void call(Accessor<Object> accessor) {
// Never ready, so never notify the accessor.
}
};
private final Element wavePanelElement;
private final Element unsavedIndicatorElement;
private final FramedPanel waveFrame;
private final LogicalPanel rootPanel;
private final WaveRef waveRef;
private final RemoteViewServiceMultiplexer channel;
private final IdGenerator idGenerator;
private final ProfileManager profiles;
private final WaveStore waveStore;
private final boolean isNewWave;
private final String localDomain;
private boolean closed;
private StageOne one;
private StageTwo two;
private StageThree three;
private WaveContext wave;
private Set<ParticipantId> participants;
/**
* @param wavePanelElement the DOM element to become the wave panel.
* @param unsavedIndicatorElement the element that displays the wave saved state.
* @param rootPanel a panel that this an ancestor of wavePanelElement. This is
* used for adopting to the GWT widget tree.
* @param waveFrame the wave frame.
* @param waveRef the id of the wave to open. If null, it means, create a new
* wave.
* @param channel the communication channel.
* @param isNewWave true if the wave is a new client-created wave
* @param idGenerator
* @param participants the participants to add to the newly created wave. null
* if only the creator should be added
*/
public StagesProvider(Element wavePanelElement, Element unsavedIndicatorElement,
LogicalPanel rootPanel, FramedPanel waveFrame, WaveRef waveRef, RemoteViewServiceMultiplexer channel,
IdGenerator idGenerator, ProfileManager profiles, WaveStore store, boolean isNewWave,
String localDomain, Set<ParticipantId> participants) {
this.wavePanelElement = wavePanelElement;
this.unsavedIndicatorElement = unsavedIndicatorElement;
this.waveFrame = waveFrame;
this.rootPanel = rootPanel;
this.waveRef = waveRef;
this.channel = channel;
this.idGenerator = idGenerator;
this.profiles = profiles;
this.waveStore = store;
this.isNewWave = isNewWave;
this.localDomain = localDomain;
this.participants = participants;
}
@Override
protected AsyncHolder<StageZero> createStageZeroLoader() {
return haltIfClosed(super.createStageZeroLoader());
}
@Override
protected AsyncHolder<StageOne> createStageOneLoader(StageZero zero) {
return haltIfClosed(new StageOne.DefaultProvider(zero) {
@Override
protected Element createWaveHolder() {
return wavePanelElement;
}
@Override
protected LogicalPanel createWaveContainer() {
return rootPanel;
}
});
}
@Override
protected AsyncHolder<StageTwo> createStageTwoLoader(StageOne one) {
return haltIfClosed(new StageTwoProvider(this.one = one, waveRef, channel, isNewWave,
idGenerator, profiles, new SavedStateIndicator(unsavedIndicatorElement), participants));
}
@Override
protected AsyncHolder<StageThree> createStageThreeLoader(final StageTwo two) {
return haltIfClosed(new StageThree.DefaultProvider(this.two = two) {
@Override
protected void create(final Accessor<StageThree> whenReady) {
// Prepend an init wave flow onto the stage continuation.
super.create(new Accessor<StageThree>() {
@Override
public void use(StageThree x) {
onStageThreeLoaded(x, whenReady);
}
});
}
@Override
protected String getLocalDomain() {
return localDomain;
}
});
}
private void onStageThreeLoaded(StageThree x, Accessor<StageThree> whenReady) {
if (closed) {
// Stop the loading process.
return;
}
three = x;
if (isNewWave) {
initNewWave(x);
} else {
handleExistingWave(x);
}
wave = new WaveContext(
two.getWave(), two.getConversations(), two.getSupplement(), two.getReadMonitor());
waveStore.add(wave);
install();
whenReady.use(x);
}
private void initNewWave(StageThree three) {
// Do the new-wave flow.
ModelAsViewProvider views = two.getModelAsViewProvider();
BlipQueueRenderer blipQueue = two.getBlipQueue();
ConversationView wave = two.getConversations();
// Force rendering to finish.
blipQueue.flush();
BlipView blipUi = views.getBlipView(wave.getRoot().getRootThread().getFirstBlip());
three.getEditActions().startEditing(blipUi);
}
private void handleExistingWave(StageThree three) {
if (waveRef.hasDocumentId()) {
BlipQueueRenderer blipQueue = two.getBlipQueue();
blipQueue.flush();
selectAndFocusOnBlip(two.getReader(), two.getModelAsViewProvider(), two.getConversations(),
one.getFocusFrame(), waveRef);
}
}
/**
* A hook to install features that are not dependent an a certain stage.
*/
protected void install() {
WindowTitleHandler.install(waveStore, waveFrame);
}
public void destroy() {
if (wave != null) {
waveStore.remove(wave);
wave = null;
}
if (three != null) {
three.getEditActions().stopEditing();
three = null;
}
if (two != null) {
two.getConnector().close();
two = null;
}
if (one != null) {
one.getWavePanel().destroy();
one = null;
}
closed = true;
}
/**
* Finds the blip that should receive the focus and selects it.
*/
private static void selectAndFocusOnBlip(Reader reader, ModelAsViewProvider views,
ConversationView wave, FocusFramePresenter focusFrame, WaveRef waveRef) {
FocusBlipSelector blipSelector =
FocusBlipSelector.create(wave, views, reader, new ViewTraverser());
BlipView blipUi = blipSelector.selectBlipByWaveRef(waveRef);
// Focus on the selected blip.
if (blipUi != null) {
focusFrame.focus(blipUi);
}
}
/**
* @return a halting provider if this stage is closed. Otherwise, returns the
* given provider.
*/
@SuppressWarnings("unchecked") // HALT is safe as a holder for any type
private <T> AsyncHolder<T> haltIfClosed(AsyncHolder<T> provider) {
return closed ? (AsyncHolder<T>) HALT : provider;
}
}