/** * 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 org.waveprotocol.wave.client.wavepanel.impl.edit; import com.google.gwt.user.client.Window; import org.waveprotocol.wave.client.common.util.WaveRefConstants; import org.waveprotocol.wave.client.editor.content.ContentDocument; import org.waveprotocol.wave.client.wave.InteractiveDocument; import org.waveprotocol.wave.client.wave.WaveDocuments; import org.waveprotocol.wave.client.wavepanel.impl.focus.FocusFramePresenter; import org.waveprotocol.wave.client.wavepanel.view.BlipLinkPopupView; import org.waveprotocol.wave.client.wavepanel.view.BlipView; import org.waveprotocol.wave.client.wavepanel.view.ThreadView; 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.ConversationBlip; import org.waveprotocol.wave.model.conversation.ConversationThread; import org.waveprotocol.wave.model.id.DualIdSerialiser; import org.waveprotocol.wave.model.id.InvalidIdException; import org.waveprotocol.wave.model.id.WaveId; import org.waveprotocol.wave.model.id.WaveletId; import org.waveprotocol.wave.model.waveref.WaveRef; import org.waveprotocol.wave.util.escapers.GwtWaverefEncoder; /** * Defines the UI actions that can be performed as part of the editing feature. * This includes editing, replying, and deleting blips in a conversation. * */ public final class ActionsImpl implements Actions { private final ModelAsViewProvider views; private final WaveDocuments<? extends InteractiveDocument> documents; private final BlipQueueRenderer blipQueue; private final FocusFramePresenter focus; private final EditSession edit; ActionsImpl(ModelAsViewProvider views, WaveDocuments<? extends InteractiveDocument> documents, BlipQueueRenderer blipQueue, FocusFramePresenter focus, EditSession edit) { this.views = views; this.documents = documents; this.blipQueue = blipQueue; this.focus = focus; this.edit = edit; } /** * Creates an action performer. * * @param views view provider * @param documents collection of documents in the wave * @param blipQueue blip renderer * @param focus focus-frame feature * @param edit blip-content editing feature */ public static ActionsImpl create(ModelAsViewProvider views, WaveDocuments<? extends InteractiveDocument> documents, BlipQueueRenderer blipQueue, FocusFramePresenter focus, EditSession edit) { return new ActionsImpl(views, documents, blipQueue, focus, edit); } @Override public void startEditing(BlipView blipUi) { focusAndEdit(blipUi); } @Override public void stopEditing() { edit.stopEditing(); } @Override public void reply(BlipView blipUi) { ConversationBlip blip = views.getBlip(blipUi); ContentDocument doc = documents.get(blip).getDocument(); // Insert the reply at a good spot near the current selection, or use the // end of the document as a fallback. int location = DocumentUtil.getLocationNearSelection(doc); if (location == -1) { location = blip.getContent().size() - 1; } ConversationBlip reply = blip.addReplyThread(location).appendBlip(); blipQueue.flush(); focusAndEdit(views.getBlipView(reply)); } @Override public void addContinuation(ThreadView threadUi) { ConversationThread thread = views.getThread(threadUi); ConversationBlip continuation = thread.appendBlip(); blipQueue.flush(); focusAndEdit(views.getBlipView(continuation)); } @Override public void delete(BlipView blipUi) { // If focus is on the blip that is being deleted, move focus somewhere else. // If focus is on a blip inside the blip being deleted, don't worry about it // (checking could get too expensive). if (blipUi.equals(focus.getFocusedBlip())) { // Move to next blip in thread if there is one, otherwise previous blip in // thread, otherwise previous blip in traversal order. ThreadView parentUi = blipUi.getParent(); BlipView nextUi = parentUi.getBlipAfter(blipUi); if (nextUi == null) { nextUi = parentUi.getBlipBefore(blipUi); } if (nextUi != null) { focus.focus(nextUi); } else { focus.moveUp(); } } views.getBlip(blipUi).delete(); } @Override public void delete(ThreadView threadUi) { views.getThread(threadUi).delete(); } /** * Moves focus to a blip, and starts editing it. */ private void focusAndEdit(BlipView blipUi) { edit.stopEditing(); focus.focus(blipUi); edit.startEditing(blipUi); } @Override public void popupLink(BlipView blipUi) { ConversationBlip blip = views.getBlip(blipUi); // TODO(Yuri Z.) Change to use the conversation model when the Conversation // exposes a reference to its ConversationView. WaveId waveId = blip.hackGetRaw().getWavelet().getWaveId(); WaveletId waveletId; try { waveletId = DualIdSerialiser.MODERN.deserialiseWaveletId(blip.getConversation().getId()); } catch (InvalidIdException e) { Window.alert( "Unable to link to this blip, invalid conversation id " + blip.getConversation().getId()); return; } WaveRef waveRef = WaveRef.of(waveId, waveletId, blip.getId()); final String waveRefStringValue = WaveRefConstants.WAVE_URI_PREFIX + GwtWaverefEncoder.encodeToUriPathSegment(waveRef); BlipLinkPopupView blipLinkPopupView = blipUi.createLinkPopup(); blipLinkPopupView.setLinkInfo(waveRefStringValue); blipLinkPopupView.show(); } }