Author: yurize
Date: Fri Nov 18 11:56:28 2011
New Revision: 1203614

URL: http://svn.apache.org/viewvc?rev=1203614&view=rev
Log:
Adds auto focusing on the oldest unread/newest modified blip when opening a 
wave. http://codereview.waveprotocol.org/610001/show

Added:
    
incubator/wave/trunk/src/org/waveprotocol/wave/client/wavepanel/impl/focus/FocusBlipSelector.java
   (with props)
Modified:
    
incubator/wave/trunk/src/org/waveprotocol/box/webclient/client/StageTwoProvider.java
    
incubator/wave/trunk/src/org/waveprotocol/box/webclient/client/StagesProvider.java
    incubator/wave/trunk/src/org/waveprotocol/wave/client/StageTwo.java
    
incubator/wave/trunk/src/org/waveprotocol/wave/client/wavepanel/impl/reader/Reader.java

Modified: 
incubator/wave/trunk/src/org/waveprotocol/box/webclient/client/StageTwoProvider.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/src/org/waveprotocol/box/webclient/client/StageTwoProvider.java?rev=1203614&r1=1203613&r2=1203614&view=diff
==============================================================================
--- 
incubator/wave/trunk/src/org/waveprotocol/box/webclient/client/StageTwoProvider.java
 (original)
+++ 
incubator/wave/trunk/src/org/waveprotocol/box/webclient/client/StageTwoProvider.java
 Fri Nov 18 11:56:28 2011
@@ -25,14 +25,20 @@ import org.waveprotocol.wave.client.Stag
 import org.waveprotocol.wave.client.StageTwo;
 import org.waveprotocol.wave.client.account.ProfileManager;
 import org.waveprotocol.wave.client.common.util.AsyncHolder;
+import org.waveprotocol.wave.client.wavepanel.impl.focus.FocusBlipSelector;
+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.concurrencycontrol.channel.WaveViewService;
+import org.waveprotocol.wave.model.conversation.ConversationView;
 import org.waveprotocol.wave.model.id.IdGenerator;
-import org.waveprotocol.wave.model.id.WaveId;
 import org.waveprotocol.wave.model.schema.SchemaProvider;
 import org.waveprotocol.wave.model.schema.conversation.ConversationSchemas;
 import org.waveprotocol.wave.model.wave.ParticipantId;
 import org.waveprotocol.wave.model.wave.data.WaveViewData;
 import org.waveprotocol.wave.model.wave.data.impl.WaveViewDataImpl;
+import org.waveprotocol.wave.model.waveref.WaveRef;
 
 /**
  * Provides stage 2 of the staged loading of the wave panel
@@ -41,7 +47,7 @@ import org.waveprotocol.wave.model.wave.
  */
 public class StageTwoProvider extends StageTwo.DefaultProvider {
 
-  private final WaveId waveId;
+  private final WaveRef waveRef;
   private final RemoteViewServiceMultiplexer channel;
   private final boolean isNewWave;
   // TODO: Remove this after WebClientBackend is deleted.
@@ -61,12 +67,13 @@ public class StageTwoProvider extends St
    * @param channel communication channel
    * @param idGenerator
    */
-  public StageTwoProvider(StageOne stageOne, WaveId waveId, 
RemoteViewServiceMultiplexer channel,
+  public StageTwoProvider(StageOne stageOne, WaveRef waveRef, 
RemoteViewServiceMultiplexer channel,
       boolean isNewWave, IdGenerator idGenerator, ProfileManager profiles) {
     super(stageOne);
     Preconditions.checkArgument(stageOne != null);
-    Preconditions.checkArgument(waveId != null);
-    this.waveId = waveId;
+    Preconditions.checkArgument(waveRef != null);
+    Preconditions.checkArgument(waveRef.getWaveId() != null);
+    this.waveRef = waveRef;
     this.channel = channel;
     this.isNewWave = isNewWave;
     this.idGenerator = idGenerator;
@@ -95,7 +102,7 @@ public class StageTwoProvider extends St
 
   @Override
   protected WaveViewService createWaveViewService() {
-    return new RemoteWaveViewService(waveId, channel, getDocumentRegistry());
+    return new RemoteWaveViewService(waveRef.getWaveId(), channel, 
getDocumentRegistry());
   }
 
   /**
@@ -129,9 +136,12 @@ public class StageTwoProvider extends St
 
           // Install eager UI.
           installFeatures();
+          Reader reader = installReader();
 
           // Rendering, and therefore the whole stage is now ready.
           whenReady.use(StageTwoProvider.this);
+
+          selectAndFocusOnBlip(reader);
         }
       });
     }
@@ -155,6 +165,22 @@ public class StageTwoProvider extends St
 
   @Override
   protected void fetchWave(final AsyncHolder.Accessor<WaveViewData> whenReady) 
{
-    whenReady.use(WaveViewDataImpl.create(waveId));
+    whenReady.use(WaveViewDataImpl.create(waveRef.getWaveId()));
+  }
+
+  /**
+   * Finds the blip that should receive the focus and selects it.
+   */
+  private void selectAndFocusOnBlip(Reader reader) {
+    ModelAsViewProvider views = getModelAsViewProvider();
+    ConversationView wave = getConversations();
+    StageOne one = getStageOne();
+    FocusBlipSelector blipSelector =
+        FocusBlipSelector.create(waveRef, wave, views, reader, new 
ViewTraverser());
+    BlipView blipUi = blipSelector.select();
+    // Focus on the selected blip.
+    if (blipUi != null) {
+      one.getFocusFrame().focus(blipUi);
+    }
   }
 }

Modified: 
incubator/wave/trunk/src/org/waveprotocol/box/webclient/client/StagesProvider.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/src/org/waveprotocol/box/webclient/client/StagesProvider.java?rev=1203614&r1=1203613&r2=1203614&view=diff
==============================================================================
--- 
incubator/wave/trunk/src/org/waveprotocol/box/webclient/client/StagesProvider.java
 (original)
+++ 
incubator/wave/trunk/src/org/waveprotocol/box/webclient/client/StagesProvider.java
 Fri Nov 18 11:56:28 2011
@@ -34,11 +34,8 @@ import org.waveprotocol.wave.client.comm
 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.Conversation;
-import org.waveprotocol.wave.model.conversation.ConversationBlip;
 import org.waveprotocol.wave.model.conversation.ConversationView;
 import org.waveprotocol.wave.model.id.IdGenerator;
-import org.waveprotocol.wave.model.id.ModernIdSerialiser;
 import org.waveprotocol.wave.model.waveref.WaveRef;
 
 /**
@@ -118,7 +115,7 @@ public class StagesProvider extends Stag
   @Override
   protected AsyncHolder<StageTwo> createStageTwoLoader(StageOne one) {
     return haltIfClosed(new StageTwoProvider(
-        this.one = one, waveRef.getWaveId(), channel, isNewWave, idGenerator, 
profiles));
+        this.one = one, waveRef, channel, isNewWave, idGenerator, profiles));
   }
 
   @Override
@@ -172,33 +169,8 @@ public class StagesProvider extends Stag
   }
 
   private void handleExistingWave(StageThree three) {
-    // If there's blip reference then focus on that blip.
-    String documentId = waveRef.getDocumentId();
-    if (documentId != null) {
-      ModelAsViewProvider views = two.getModelAsViewProvider();
-      BlipQueueRenderer blipQueue = two.getBlipQueue();
-      ConversationView wave = two.getConversations();
-      blipQueue.flush();
-      // Find conversation
-      Conversation conversation;
-      if (waveRef.hasWaveletId()) {
-        String id = 
ModernIdSerialiser.INSTANCE.serialiseWaveletId(waveRef.getWaveletId());
-        conversation = wave.getConversation(id);
-      } else {
-        // Unspecified wavelet means root.
-        conversation = wave.getRoot();
-      }
-      if (conversation != null) {
-        // Find selected blip.
-        ConversationBlip blip = wave.getRoot().getBlip(documentId);
-        if (blip != null) {
-          BlipView blipUi = views.getBlipView(blip);
-          if (blipUi != null) {
-            two.getStageOne().getFocusFrame().focus(blipUi);
-          }
-        }
-      }
-    }
+    BlipQueueRenderer blipQueue = two.getBlipQueue();
+    blipQueue.flush();
   }
 
   public void destroy() {

Modified: incubator/wave/trunk/src/org/waveprotocol/wave/client/StageTwo.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/src/org/waveprotocol/wave/client/StageTwo.java?rev=1203614&r1=1203613&r2=1203614&view=diff
==============================================================================
--- incubator/wave/trunk/src/org/waveprotocol/wave/client/StageTwo.java 
(original)
+++ incubator/wave/trunk/src/org/waveprotocol/wave/client/StageTwo.java Fri Nov 
18 11:56:28 2011
@@ -690,6 +690,7 @@ public interface StageTwo {
 
       // Install eager UI features
       installFeatures();
+      installReader();
 
       // Activate liveness.
       getConnector().connect(null);
@@ -711,8 +712,13 @@ public interface StageTwo {
      */
     protected void installFeatures() {
       // Eagerly install some features.
-      Reader.install(getSupplement(), stageOne.getFocusFrame(), 
getModelAsViewProvider(),
+    }
+
+    protected Reader installReader() {
+      Reader reader =
+          Reader.install(getSupplement(), stageOne.getFocusFrame(), 
getModelAsViewProvider(),
           getDocumentRegistry());
+      return reader;
     }
   }
 }

Added: 
incubator/wave/trunk/src/org/waveprotocol/wave/client/wavepanel/impl/focus/FocusBlipSelector.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/src/org/waveprotocol/wave/client/wavepanel/impl/focus/FocusBlipSelector.java?rev=1203614&view=auto
==============================================================================
--- 
incubator/wave/trunk/src/org/waveprotocol/wave/client/wavepanel/impl/focus/FocusBlipSelector.java
 (added)
+++ 
incubator/wave/trunk/src/org/waveprotocol/wave/client/wavepanel/impl/focus/FocusBlipSelector.java
 Fri Nov 18 11:56:28 2011
@@ -0,0 +1,153 @@
+/**
+ * Copyright 2011 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.focus;
+
+import 
org.waveprotocol.wave.client.wavepanel.impl.focus.FocusFramePresenter.FocusOrder;
+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.model.conversation.Conversation;
+import org.waveprotocol.wave.model.conversation.ConversationBlip;
+import org.waveprotocol.wave.model.conversation.ConversationView;
+import org.waveprotocol.wave.model.id.ModernIdSerialiser;
+import org.waveprotocol.wave.model.util.CollectionUtils;
+import org.waveprotocol.wave.model.waveref.WaveRef;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Selects the blip that should should receive the focus.
+ *
+ * @author [email protected] (Yuri Zelikov)
+ */
+public class FocusBlipSelector {
+
+  /** The reference to the current wave. */
+  private final WaveRef waveRef;
+
+  /** The conversation. */
+  private final ConversationView wave;
+
+  /** The model with views. */
+  private final ModelAsViewProvider views;
+
+  /** The blip ordering. */
+  private final  ViewTraverser traverser;
+
+  /** The ordering for focus frame movement. */
+  private final Reader reader;
+
+  /**
+   * Creates a {@link FocusBlipSelector}.
+   *
+   * @param waveRef the reference to the current wave that includes the blip id
+   *        in case the wave was loaded due to a click on a link to this wave.
+   * @param wave the he conversation.
+   * @param views the model with views.
+   * @param traverser the blip ordering.
+   * @param reader the ordering of focus frame movement.
+   * @return the focus blip selector.
+   */
+  public static FocusBlipSelector create(WaveRef waveRef, ConversationView 
wave,
+      ModelAsViewProvider views, Reader reader, ViewTraverser traverser) {
+    return new FocusBlipSelector(waveRef, wave, views, reader, traverser);
+  }
+
+  FocusBlipSelector(WaveRef waveRef, ConversationView wave,
+      ModelAsViewProvider models, Reader reader, ViewTraverser traverser) {
+    this.waveRef = waveRef;
+    this.wave = wave;
+    this.views = models;
+    this.reader = reader;
+    this.traverser = traverser;
+  }
+
+  /**
+   * @return the blip that should receive the focus or {@code null} if it was
+   *         impossible to compute it. The strategy for blip selection is like
+   *         follows:
+   *         <ul>
+   *         <li>Try to select the oldest unread blip using {@link 
FocusOrder}.</li>
+   *         <li>If all blips are already read, then select the most recently
+   *         modified blip.</li>
+   *         </ul>
+   */
+  public BlipView select() {
+    // Determine if waveRef has a documentId in it - if so, the referenced blip
+    // should receive the focus on wave load.
+    // First find conversation
+    Conversation conversation;
+    if (waveRef.hasWaveletId()) {
+      String id = 
ModernIdSerialiser.INSTANCE.serialiseWaveletId(waveRef.getWaveletId());
+      conversation = wave.getConversation(id);
+    } else {
+      // Unspecified wavelet means root.
+      conversation = wave.getRoot();
+    }
+    if (conversation == null) {
+      return null;
+    } else {
+      ConversationBlip blip = null;
+      // If there's blip reference then focus on that blip.
+      String documentId = waveRef.getDocumentId();
+      // Find selected blip.
+      if (documentId != null) {
+        blip = wave.getRoot().getBlip(documentId);
+        if (blip != null) {
+          return views.getBlipView(blip);
+        } else {
+          return null;
+        }
+      } else {
+        blip = wave.getRoot().getRootThread().getFirstBlip();
+        BlipView rootBlipUi = views.getBlipView(blip);
+        if (rootBlipUi == null) {
+          return null;
+        }
+        if (reader != null) {
+          if (!reader.isRead(rootBlipUi)) {
+            // Return the root blip since it is unread.
+            return rootBlipUi;
+          }
+          // If no blip was referenced, then try to find the next blip starting
+          // from the root according to the order.
+          BlipView tempUi = reader.getNext(rootBlipUi);
+          if (tempUi != null && !tempUi.getId().equals(rootBlipUi.getId())) {
+            return tempUi;
+          } else {
+            return findMostRecentlyModified(rootBlipUi);
+          }
+        } else {
+          return findMostRecentlyModified(rootBlipUi);
+        }
+      }
+    }
+  }
+
+  private BlipView findMostRecentlyModified(BlipView start) {
+    BlipView blipUi = start;
+    Map<Long, BlipView> blips = CollectionUtils.newHashMap();
+    while (blipUi != null) {
+      ConversationBlip blip = views.getBlip(blipUi);
+      blips.put(blip.getLastModifiedTime() , blipUi);
+      blipUi = traverser.getNext(blipUi);
+    }
+    long lmt = Collections.max(blips.keySet());
+    return blips.get(lmt);
+  }
+}

Propchange: 
incubator/wave/trunk/src/org/waveprotocol/wave/client/wavepanel/impl/focus/FocusBlipSelector.java
------------------------------------------------------------------------------
    svn:executable = *

Modified: 
incubator/wave/trunk/src/org/waveprotocol/wave/client/wavepanel/impl/reader/Reader.java
URL: 
http://svn.apache.org/viewvc/incubator/wave/trunk/src/org/waveprotocol/wave/client/wavepanel/impl/reader/Reader.java?rev=1203614&r1=1203613&r2=1203614&view=diff
==============================================================================
--- 
incubator/wave/trunk/src/org/waveprotocol/wave/client/wavepanel/impl/reader/Reader.java
 (original)
+++ 
incubator/wave/trunk/src/org/waveprotocol/wave/client/wavepanel/impl/reader/Reader.java
 Fri Nov 18 11:56:28 2011
@@ -86,7 +86,7 @@ public final class Reader implements Foc
     }
   }
 
-  private boolean isRead(BlipView blipUi) {
+  public boolean isRead(BlipView blipUi) {
     return !supplement.isUnread(models.getBlip(blipUi));
   }
 


Reply via email to