Add support for project aware TopMenu extensions

Currently TopMenu extensions are not context aware, which is not a
problem for most of them, but in case of the 'Projects' section this
could be problematic.

With this approach plugins cannot contribute TopMenu items that will
only be shown when a project is selected and the url (and the history
item) contains a project name.

This change improves this by adding a possibility to provide a menu
item within the 'Projects' context without the previously mentioned
limitation.

Now when a TopMenu item contains a '${projectName}' placeholder it
will be replaced with name of project that is selected in the
'Projects' section.

Change-Id: I516f129dc73b5b98fd6c2d385690e51febfc00a3
Signed-off-by: Dariusz Luksza <dariusz@luksza.org>
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 507e0e4..d27e8ac 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -1259,6 +1259,26 @@
 }
 ----
 
+`MenuItems` that are bound for the `MenuEntry` with the name
+`GerritTopMenu.PROJECTS` can contain a `${projectName}` placeholder
+which is automatically replaced by the actual project name.
+
+E.g. plugins may register an link:#http[HTTP Servlet] to handle project
+specific requests and add an menu item for this:
+
+[source,java]
+---
+  new MenuItem("My Screen", "/plugins/myplugin/project/${projectName}");
+---
+
+This also enables plugins to provide menu items for project aware
+screens:
+
+[source,java]
+---
+  new MenuItem("My Screen", "/x/my-screen/for/${projectName}");
+---
+
 If no Guice modules are declared in the manifest, the top menu extension may use
 auto-registration by providing an `@Listen` annotation:
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index 5b96ba0..d74b744 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -51,6 +51,7 @@
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.AccountGeneralPreferences;
 import com.google.gerrit.reviewdb.client.AuthType;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.aria.client.Roles;
 import com.google.gwt.core.client.EntryPoint;
 import com.google.gwt.core.client.GWT;
@@ -764,14 +765,21 @@
       public void onSuccess(TopMenuList result) {
         List<TopMenu> topMenuExtensions = Natives.asList(result);
         for (TopMenu menu : topMenuExtensions) {
-          LinkMenuBar existingBar = menuBars.get(menu.getName());
+          String name = menu.getName();
+          LinkMenuBar existingBar = menuBars.get(name);
           LinkMenuBar bar = existingBar != null ? existingBar : new LinkMenuBar();
-          for (TopMenuItem item : Natives.asList(menu.getItems())) {
-            addExtensionLink(bar, item);
+          if (GerritTopMenu.PROJECTS.menuName.equals(name)) {
+            for (TopMenuItem item : Natives.asList(menu.getItems())) {
+              addProjectLink(bar, item);
+            }
+          } else {
+            for (TopMenuItem item : Natives.asList(menu.getItems())) {
+              addExtensionLink(bar, item);
+            }
           }
           if (existingBar == null) {
-            menuBars.put(menu.getName(), bar);
-            menuLeft.add(bar, menu.getName());
+            menuBars.put(name, bar);
+            menuLeft.add(bar, name);
           }
         }
       }
@@ -890,6 +898,40 @@
       });
   }
 
+  private static LinkMenuItem addProjectLink(LinkMenuBar m, TopMenuItem item) {
+    LinkMenuItem i = new ProjectLinkMenuItem(item.getName(), item.getUrl()) {
+        @Override
+        protected void onScreenLoad(Project.NameKey project) {
+          String p = panel.replace("${projectName}", project.get());
+          if (panel.startsWith("/x/")) {
+            setTargetHistoryToken(p);
+          } else if (isAbsolute(panel)) {
+            getElement().setPropertyString("href", p);
+          } else {
+            getElement().setPropertyString("href", selfRedirect(p));
+          }
+        }
+
+        @Override
+        public void go() {
+          String href = getElement().getPropertyString("href");
+          if (href.startsWith("#")) {
+            super.go();
+          } else {
+            Window.open(href, getElement().getPropertyString("target"), "");
+          }
+        }
+      };
+    if (item.getTarget() != null && !item.getTarget().isEmpty()) {
+      i.getElement().setAttribute("target", item.getTarget());
+    }
+    if (item.getId() != null) {
+      i.getElement().setAttribute("id", item.getId());
+    }
+    m.addItem(i);
+    return i;
+  }
+
   private static void addDiffLink(final LinkMenuBar m, final String text,
       final PatchScreen.Type type) {
     m.addItem(new LinkMenuItem(text, "") {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLinkMenuItem.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLinkMenuItem.java
index 2917505..119f5ef 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLinkMenuItem.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ProjectLinkMenuItem.java
@@ -19,7 +19,7 @@
 import com.google.gerrit.reviewdb.client.Project;
 
 public class ProjectLinkMenuItem extends LinkMenuItem {
-  private final String panel;
+  protected final String panel;
 
   public ProjectLinkMenuItem(String text, String panel) {
     super(text, "");
@@ -38,10 +38,14 @@
 
     if (projectKey != null) {
       setVisible(true);
-      setTargetHistoryToken(Dispatcher.toProjectAdmin(projectKey, panel));
+      onScreenLoad(projectKey);
     } else {
       setVisible(false);
     }
     super.onScreenLoad(event);
   }
+
+  protected void onScreenLoad(Project.NameKey project) {
+    setTargetHistoryToken(Dispatcher.toProjectAdmin(project, panel));
+  }
 }