Allow in preview diff screens to select patch sets to be compared

Similar to the normal diff screens the preview diff screens have now a
header that shows the patch sets so that the user can change the
patch sets that are compared.

Change-Id: Ibb9d87ef8ee301170586f2b4b533ca086a04144d
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/ChangeInfo.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/ChangeInfo.java
index 9aece0a..d02913e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/ChangeInfo.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/ChangeInfo.java
@@ -15,7 +15,12 @@
 package com.googlesource.gerrit.plugins.xdocs.client;
 
 import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+import java.util.Collections;
+import java.util.Comparator;
 
 public class ChangeInfo extends JavaScriptObject {
   public final native String project() /*-{ return this.project; }-*/;
@@ -28,7 +33,44 @@
 
   public static class RevisionInfo extends JavaScriptObject {
     public final native int _number() /*-{ return this._number; }-*/;
+    public final native String name() /*-{ return this.name; }-*/;
     public final native String ref() /*-{ return this.ref; }-*/;
+    public final native boolean is_edit() /*-{ return this._number == 0; }-*/;
+    public final native String edit_base() /*-{ return this.edit_base; }-*/;
+
+    public static int findEditParent(JsArray<RevisionInfo> list) {
+      for (int i = 0; i < list.length(); i++) {
+        // edit under revisions?
+        RevisionInfo editInfo = list.get(i);
+        if (editInfo.is_edit()) {
+          String parentRevision = editInfo.edit_base();
+          // find parent
+          for (int j = 0; j < list.length(); j++) {
+            RevisionInfo parentInfo = list.get(j);
+            String name = parentInfo.name();
+            if (name.equals(parentRevision)) {
+              // found parent patch set number
+              return parentInfo._number();
+            }
+          }
+        }
+      }
+      return -1;
+    }
+
+    public static void sortRevisionInfoByNumber(JsArray<RevisionInfo> list) {
+      final int editParent = findEditParent(list);
+      Collections.sort(Natives.asList(list), new Comparator<RevisionInfo>() {
+        @Override
+        public int compare(RevisionInfo a, RevisionInfo b) {
+          return num(a) - num(b);
+        }
+
+        private int num(RevisionInfo r) {
+          return !r.is_edit() ? 2 * (r._number() - 1) + 1 : 2 * editParent;
+        }
+      });
+    }
 
     protected RevisionInfo () {
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/PatchSetSelectBox.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/PatchSetSelectBox.java
new file mode 100644
index 0000000..8ced54b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/PatchSetSelectBox.java
@@ -0,0 +1,160 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// 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.googlesource.gerrit.plugins.xdocs.client;
+
+import com.google.gerrit.plugin.client.Plugin;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.http.client.URL;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.InlineHyperlink;
+import com.google.gwt.user.client.ui.Label;
+
+import com.googlesource.gerrit.plugins.xdocs.client.ChangeInfo.RevisionInfo;
+
+public class PatchSetSelectBox extends FlowPanel {
+
+  public enum DisplaySide {
+    A, B
+  }
+
+  public static enum DiffView {
+    SIDE_BY_SIDE,
+    UNIFIED_DIFF
+  }
+
+  private final DiffView diffView;
+  private final DisplaySide side;
+  private final ChangeInfo change;
+  private final Integer basePatchSet;
+  private final int patchSet;
+  private final String path;
+
+  public PatchSetSelectBox(DiffView diffView, DisplaySide side,
+      ChangeInfo change, Integer basePatchSet, int patchSet, String path) {
+    this.diffView = diffView;
+    this.side = side;
+    this.change = change;
+    this.path = path;
+    this.basePatchSet = basePatchSet;
+    this.patchSet = patchSet;
+
+    init();
+  }
+
+  private void init() {
+    setStyleName("xdocs-patch-set-select-box");
+    if (isSideBySideDiff()) {
+      addStyleName("xdocs-patch-set-select-box-side-by-side");
+    }
+
+    addPatchSetLabel();
+
+    if (sideA()) {
+      add(createBaseLink());
+    } else if (isUnifiedDiff()) {
+      // create hidden 'Base' link to align the patch set links with side A
+      add(createHiddenBaseLink());
+    }
+
+    JsArray<RevisionInfo> list = change.revisions().values();
+    RevisionInfo.sortRevisionInfoByNumber(list);
+    for (int i = 0; i < list.length(); i++) {
+      add(createLink(list.get(i)));
+    }
+  }
+
+  private void addPatchSetLabel() {
+    add(new Label("Patch Set"));
+    if (isUnifiedDiff()) {
+      Label l = new Label(sideA() ? "(-)" : "(+)");
+      l.addStyleName("xdocs-monospace");
+      add(l);
+    }
+  }
+
+  private boolean isSideBySideDiff() {
+    return diffView == DiffView.SIDE_BY_SIDE;
+  }
+
+  private boolean isUnifiedDiff() {
+    return diffView == DiffView.UNIFIED_DIFF;
+  }
+
+  private boolean sideA() {
+    return side == DisplaySide.A;
+  }
+
+  private InlineHyperlink createBaseLink() {
+    InlineHyperlink link = new InlineHyperlink("Base",
+        getUrl(change._number(), null, patchSet, path, diffView));
+    if (isBaseSelected()) {
+      link.setStyleName("xdocs-patch-set-select-box-selected");
+    }
+    return link;
+  }
+
+  private InlineHyperlink createHiddenBaseLink() {
+    InlineHyperlink link = new InlineHyperlink("Base", null);
+    link.addStyleName("xdocs-hidden");
+    return link;
+  }
+
+  private InlineHyperlink createLink(RevisionInfo r) {
+    String label = r.is_edit() ? "edit" : Integer.toString(r._number());
+    Integer patchSetIdA = sideA() ? Integer.valueOf(r._number()) : basePatchSet;
+    int patchSetIdB = sideA() ? patchSet : r._number();
+    InlineHyperlink link = new InlineHyperlink(label,
+        getUrl(change._number(), patchSetIdA, patchSetIdB, path, diffView));
+    if (isSelected(r._number())) {
+      link.setStyleName("xdocs-patch-set-select-box-selected");
+    }
+    return link;
+  }
+
+  private Integer getSelectedPatchSet() {
+    return sideA() ? basePatchSet : Integer.valueOf(patchSet);
+  }
+
+  private boolean isBaseSelected() {
+    return getSelectedPatchSet() == null;
+  }
+
+  private boolean isSelected(int ps) {
+    return getSelectedPatchSet() != null
+        && getSelectedPatchSet().intValue() == ps;
+  }
+
+  private static String getUrl(int changeId, Integer patchSetIdA,
+      int patchSetIdB, String path, DiffView diffView) {
+    StringBuilder url = new StringBuilder();
+    url.append("/x/");
+    url.append(Plugin.get().getName());
+    url.append("/c/");
+    url.append(changeId);
+    url.append("/");
+    if (patchSetIdA != null) {
+      url.append(patchSetIdA);
+      url.append("..");
+    }
+    url.append(patchSetIdB);
+    url.append("/");
+    url.append(URL.encode(path));
+    if (diffView == DiffView.UNIFIED_DIFF) {
+      url.append(",unified");
+    }
+    return url.toString();
+  }
+
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocDiffScreen.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocDiffScreen.java
index 59fef0c..8f308cb 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocDiffScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocDiffScreen.java
@@ -21,6 +21,7 @@
 import com.google.gwt.resources.client.ImageResource;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.FlexTable;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HorizontalPanel;
 import com.google.gwt.user.client.ui.InlineHyperlink;
@@ -277,4 +278,10 @@
     l.setStyleName("xdocs-error");
     add(l);
   }
+
+  protected static int addRow(FlexTable table) {
+    int row = table.getRowCount();
+    table.insertRow(row);
+    return row;
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocSideBySideDiffScreen.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocSideBySideDiffScreen.java
index 3e57b4e..d96c6ab 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocSideBySideDiffScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocSideBySideDiffScreen.java
@@ -16,8 +16,11 @@
 
 import com.google.gerrit.plugin.client.screen.Screen;
 import com.google.gwt.http.client.URL;
+import com.google.gwt.user.client.ui.FlexTable;
 import com.google.gwt.user.client.ui.Frame;
-import com.google.gwt.user.client.ui.HorizontalPanel;
+
+import com.googlesource.gerrit.plugins.xdocs.client.PatchSetSelectBox.DiffView;
+import com.googlesource.gerrit.plugins.xdocs.client.PatchSetSelectBox.DisplaySide;
 
 public class XDocSideBySideDiffScreen extends XDocDiffScreen {
   static class Factory implements Screen.EntryPoint {
@@ -50,12 +53,17 @@
     frameB.getElement().setId(frameIdB);
     XDocScreen.resize(frameB, frameIdB);
 
-    HorizontalPanel p = new HorizontalPanel();
-    p.setVerticalAlignment(ALIGN_MIDDLE);
-    p.setStyleName("xdocs-sidebyside-panel");
-    p.add(frameA);
-    p.add(frameB);
-    add(p);
+    FlexTable t = new FlexTable();
+    t.setStyleName("xdocs-diff-table");
+    int row = addRow(t);
+    t.setWidget(row, 0, new PatchSetSelectBox(
+        DiffView.SIDE_BY_SIDE, DisplaySide.A, change, base, patchSet, path));
+    t.setWidget(row, 1, new PatchSetSelectBox(
+        DiffView.SIDE_BY_SIDE,  DisplaySide.B, change, base, patchSet, path));
+    row = addRow(t);
+    t.setWidget(row, 0, frameA);
+    t.setWidget(row, 1, frameB);
+    add(t);
   }
 
   private String getRevisionSideA() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocUnifiedDiffScreen.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocUnifiedDiffScreen.java
index d1c6548..f97f527 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocUnifiedDiffScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocUnifiedDiffScreen.java
@@ -16,8 +16,12 @@
 
 import com.google.gerrit.plugin.client.screen.Screen;
 import com.google.gwt.http.client.URL;
+import com.google.gwt.user.client.ui.FlexTable;
 import com.google.gwt.user.client.ui.Frame;
 
+import com.googlesource.gerrit.plugins.xdocs.client.PatchSetSelectBox.DiffView;
+import com.googlesource.gerrit.plugins.xdocs.client.PatchSetSelectBox.DisplaySide;
+
 public class XDocUnifiedDiffScreen extends XDocDiffScreen {
   static class Factory implements Screen.EntryPoint {
     @Override
@@ -41,7 +45,16 @@
         new Frame(XDocApi.getUrl(change.project(), getRevision(), getPath()));
     frame.getElement().setId(frameId);
     XDocScreen.resize(frame, frameId);
-    add(frame);
+
+    FlexTable t = new FlexTable();
+    t.setStyleName("xdocs-diff-table");
+    t.addStyleName("xdocs-unified-diff-table");
+    t.setWidget(addRow(t), 0, new PatchSetSelectBox(
+        DiffView.UNIFIED_DIFF, DisplaySide.A, change, base, patchSet, path));
+    t.setWidget(addRow(t), 0, new PatchSetSelectBox(
+        DiffView.UNIFIED_DIFF, DisplaySide.B, change, base, patchSet, path));
+    t.setWidget(addRow(t), 0, frame);
+    add(t);
   }
 
   private String getRevision() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/public/xdocs.css b/src/main/java/com/googlesource/gerrit/plugins/xdocs/public/xdocs.css
index cc538e2..b04ffd3 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/public/xdocs.css
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/public/xdocs.css
@@ -49,16 +49,61 @@
   font-weight: bold;
 }
 
-.xdocs-sidebyside-panel {
+.xdocs-patch-set-select-box {
+  background-color: #F7F7F7;
+  color: #353535;
+  font-size: 12px;
+  white-space: normal;
+  font-family: sans-serif;
+  font-weight: bold;
+}
+.xdocs-patch-set-select-box-side-by-side {
+  text-align: center;
+}
+.xdocs-patch-set-select-box div {
+  padding-left: 3px;
+  padding-right: 3px;
+  vertical-align: middle;
+  display: inline-block;
+}
+.xdocs-patch-set-select-box a {
+  padding-left: 3px;
+  padding-right: 3px;
+  text-decoration: none;
+  vertical-align: middle;
+  display: inline-block;
+}
+
+.xdocs-patch-set-select-box-selected {
+  font-weight: bold;
+  background-color: #D8EDF9;
+}
+
+.xdocs-diff-table {
   width: 100%;
+  color: #DDD;
   border-collapse: collapse;
 }
 
-.xdocs-sidebyside-panel td {
+.xdocs-diff-table tr:first-child td,
+.xdocs-unified-diff-table tr:nth-child(2) td {
+  color: #F7F7F7;
+}
+
+.xdocs-diff-table td {
+  padding: 0px;
   border-width: 1px;
   border-style: solid;
 }
 
-.xdocs-sidebyside-panel iframe {
+.xdocs-diff-table iframe {
   border-style: none;
 }
+
+.xdocs-monospace {
+  font-family: monospace;
+}
+
+.xdocs-hidden {
+  visibility: hidden;
+}