Bug 512289 - Add sign in and sign out in help menu

Adds a new menu and toolbar for managing USS-related activities,
under Help > Eclipse User Storage.  Menu has ID
org.eclipse.userstorage.accounts and provides slots for
`actions` and `additions`.

Change-Id: I43c6f15df1b8165c6a2e388c635e490e2a9f15e6
diff --git a/org.eclipse.userstorage.oauth/about.html b/org.eclipse.userstorage.oauth/about.html
index d35d5ae..c0e66f7 100644
--- a/org.eclipse.userstorage.oauth/about.html
+++ b/org.eclipse.userstorage.oauth/about.html
@@ -24,5 +24,21 @@
 indicated below, the terms and conditions of the EPL still apply to any source code in the Content
 and such source code may be obtained at <a href="http://www.eclipse.org/">http://www.eclipse.org</a>.</p>
 
+<h3>Third Party Content</h3>
+
+<p>The Content includes items that have been sourced from third parties
+as set out below. If you did not receive this Content directly from
+the Eclipse Foundation, the following is provided for informational
+purposes only, and you should look to the Redistributor's license
+for terms and conditions of use.</p>
+
+
+<h4>FontAwesome 4.5.0</h4>
+
+<p>This plugin includes images created from rasterized glyphs from
+<a href="https://fortawesome.github.io/Font-Awesome/">Font Awesome
+4.5.0</a> at different resolutions.  FontAwesome is available under
+the <a href="http://scripts.sil.org/OFL">SIL Open Font License</a>.</p>
+
 </body>
 </html>
diff --git a/org.eclipse.userstorage.oauth/icons/UserAccount.png b/org.eclipse.userstorage.oauth/icons/UserAccount.png
new file mode 100644
index 0000000..2bc2505
--- /dev/null
+++ b/org.eclipse.userstorage.oauth/icons/UserAccount.png
Binary files differ
diff --git a/org.eclipse.userstorage.oauth/icons/UserAccount@2x.png b/org.eclipse.userstorage.oauth/icons/UserAccount@2x.png
new file mode 100644
index 0000000..7770d21
--- /dev/null
+++ b/org.eclipse.userstorage.oauth/icons/UserAccount@2x.png
Binary files differ
diff --git a/org.eclipse.userstorage.oauth/plugin.xml b/org.eclipse.userstorage.oauth/plugin.xml
index 14d440a..b85897a 100644
--- a/org.eclipse.userstorage.oauth/plugin.xml
+++ b/org.eclipse.userstorage.oauth/plugin.xml
@@ -11,5 +11,60 @@
             name="Linked Accounts">
       </page>
    </extension>
-
+   
+   <extension
+         point="org.eclipse.ui.menus">
+      <menuContribution
+            allPopups="false"
+            locationURI="menu:help?after=additions">
+         <menu
+               id="org.eclipse.userstorage.accounts"
+               label="Eclipse User Storage">
+            <command
+                  commandId="org.eclipse.ui.browser.openBrowser"
+                  label="Open My Account"
+                  style="push"
+                  tooltip="Open the Eclipse Account page">
+               <parameter
+                     name="url"
+                     value="https://accounts.eclipse.org">
+               </parameter>
+            </command>
+            <separator
+                  name="actions">
+            </separator>
+            <separator
+                  name="accounts"
+                  visible="true">
+            </separator>
+            <dynamic
+                  class="org.eclipse.userstorage.internal.oauth.ui.AccountDetails"
+                  id="org.eclipse.userstorage.ui.oauth.signout">
+            </dynamic>
+            <separator
+                  name="additions"
+                  visible="true">
+            </separator>
+         </menu>
+      </menuContribution>
+      <menuContribution
+            allPopups="false"
+            locationURI="toolbar:org.eclipse.ui.main.toolbar?after=additions">
+         <toolbar
+               id="org.eclipse.userstorage.accounts"
+               label="Eclipse User Storage">
+         <command
+               commandId="org.eclipse.userstorage.ui.showDropDown"
+               id="org.eclipse.userstorage.accounts"
+               icon="icons/UserAccount.png"
+               tooltip="Eclipse User Storage"
+               style="pulldown">
+            <parameter
+                  name="intoolbar"
+                  value="true">
+            </parameter>
+         </command>
+         </toolbar>
+      </menuContribution>
+   </extension>
 </plugin>
diff --git a/org.eclipse.userstorage.oauth/src/org/eclipse/userstorage/internal/oauth/ui/AccountDetails.java b/org.eclipse.userstorage.oauth/src/org/eclipse/userstorage/internal/oauth/ui/AccountDetails.java
new file mode 100644
index 0000000..0f5c59b
--- /dev/null
+++ b/org.eclipse.userstorage.oauth/src/org/eclipse/userstorage/internal/oauth/ui/AccountDetails.java
@@ -0,0 +1,142 @@
+package org.eclipse.userstorage.internal.oauth.ui;
+
+import org.eclipse.userstorage.internal.oauth.OAuthCredentialsPersistence;
+import org.eclipse.userstorage.internal.oauth.OAuthCredentialsPersistence.LinkedAccount;
+
+import org.eclipse.core.runtime.IExtensionRegistry;
+import org.eclipse.jface.action.ContributionItem;
+import org.eclipse.jface.preference.PreferenceDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.ui.dialogs.PreferencesUtil;
+import org.eclipse.ui.menus.IWorkbenchContribution;
+import org.eclipse.ui.services.IServiceLocator;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Display a dynamic menu area with a USS sign-out item
+ * and a list of items showing currently-logged in apps.
+ */
+public class AccountDetails extends ContributionItem implements IWorkbenchContribution
+{
+  private OAuthCredentialsPersistence persister;
+
+  private Collection<OAuthApplicationData> appData;
+
+  private IServiceLocator serviceLocator;
+
+  @Override
+  public void initialize(IServiceLocator serviceLocator)
+  {
+    this.serviceLocator = serviceLocator;
+    IExtensionRegistry registry = serviceLocator.getService(IExtensionRegistry.class);
+    persister = OAuthCredentialsPersistence.standard();
+    appData = OAuthApplicationData.load(registry);
+  }
+
+  @Override
+  public void fill(final Menu menu, int index)
+  {
+    List<LinkedAccount> eclipseAccounts = getEclipseAccounts();
+    if (!eclipseAccounts.isEmpty())
+    {
+      MenuItem item = new MenuItem(menu, SWT.PUSH, index++);
+      item.setText("Authorized Applications");
+      item.addSelectionListener(new SelectionListener()
+      {
+        @Override
+        public void widgetSelected(SelectionEvent e)
+        {
+          PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn(menu.getShell(), OAuthAccountsPreferencePage.PAGE_ID, null, null);
+          dialog.open();
+        }
+
+        @Override
+        public void widgetDefaultSelected(SelectionEvent e)
+        {
+          widgetSelected(e);
+        }
+      });
+
+      for (LinkedAccount account : eclipseAccounts)
+      {
+        MenuItem accountItem = new MenuItem(menu, SWT.PUSH, index++);
+        accountItem.setText("  " + getApplicationName(account));
+        accountItem.setToolTipText(account.email);
+        accountItem.setEnabled(false);
+      }
+    }
+
+    MenuItem item = new MenuItem(menu, SWT.PUSH, index++);
+    item.setText("Sign Out");
+    item.setToolTipText("Discard all USS logins");
+    if (eclipseAccounts.isEmpty())
+    {
+      item.setEnabled(false);
+      new MenuItem(menu, SWT.SEPARATOR, ++index);
+      return;
+    }
+    item.addSelectionListener(new SelectionListener()
+    {
+      @Override
+      public void widgetSelected(SelectionEvent e)
+      {
+        signOut();
+      }
+
+      @Override
+      public void widgetDefaultSelected(SelectionEvent e)
+      {
+        signOut();
+      }
+    });
+  }
+
+  @Override
+  public boolean isDynamic()
+  {
+    return true;
+  }
+
+  private void signOut()
+  {
+    persister.removeLinkedAccounts(getEclipseAccounts());
+  }
+
+  private List<LinkedAccount> getEclipseAccounts()
+  {
+    List<LinkedAccount> eclipseAccounts = new ArrayList<LinkedAccount>();
+    for (LinkedAccount account : persister.getLinkedAccounts())
+    {
+      if ("https://accounts.eclipse.org/".equals(account.authURI))
+      {
+        eclipseAccounts.add(account);
+      }
+    }
+    return eclipseAccounts;
+  }
+
+  private OAuthApplicationData lookup(LinkedAccount account)
+  {
+    for (OAuthApplicationData app : appData)
+    {
+      if (account.clientId.equals(app.getClientId()) && account.authURI.equals(app.getAuthURI()))
+      {
+        return app;
+      }
+    }
+    return null;
+  }
+
+  private String getApplicationName(LinkedAccount account)
+  {
+    OAuthApplicationData app = lookup(account);
+    return app != null ? app.getApplicationName() : account.authURI + " - " + account.clientId;
+  }
+}
diff --git a/org.eclipse.userstorage.oauth/src/org/eclipse/userstorage/internal/oauth/ui/OAuthAccountsPreferencePage.java b/org.eclipse.userstorage.oauth/src/org/eclipse/userstorage/internal/oauth/ui/OAuthAccountsPreferencePage.java
index 9316e91..5179b31 100644
--- a/org.eclipse.userstorage.oauth/src/org/eclipse/userstorage/internal/oauth/ui/OAuthAccountsPreferencePage.java
+++ b/org.eclipse.userstorage.oauth/src/org/eclipse/userstorage/internal/oauth/ui/OAuthAccountsPreferencePage.java
@@ -41,6 +41,8 @@
 
 public class OAuthAccountsPreferencePage extends PreferencePage implements IWorkbenchPreferencePage
 {
+  static final String PAGE_ID = "org.eclipse.userstorage.ui.oauth.credentials";
+
   private IWorkbench workbench;
 
   private Collection<OAuthApplicationData> appData;
diff --git a/org.eclipse.userstorage.ui/plugin.xml b/org.eclipse.userstorage.ui/plugin.xml
index bbb7894..fd23151 100644
--- a/org.eclipse.userstorage.ui/plugin.xml
+++ b/org.eclipse.userstorage.ui/plugin.xml
@@ -23,5 +23,20 @@
             id="org.eclipse.userstorage.ui.ServicesPreferencePage"
             name="User Storage Service"/>
    </extension>
+   
+   <extension
+         point="org.eclipse.ui.commands">
+      <command
+            id="org.eclipse.userstorage.ui.showPullDown"
+            categoryId="org.eclipse.ui.category.help"
+            name="Show Pull Down Menu"
+            defaultHandler="org.eclipse.userstorage.ui.internal.ShowPullDownMenu">
+         <commandParameter
+               id="intoolbar"
+               name="In Tool Bar"
+               optional="false">
+         </commandParameter>
+      </command>
+   </extension>
 
 </plugin>
diff --git a/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/ShowPullDownMenu.java b/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/ShowPullDownMenu.java
new file mode 100644
index 0000000..b2033ab
--- /dev/null
+++ b/org.eclipse.userstorage.ui/src/org/eclipse/userstorage/ui/internal/ShowPullDownMenu.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2017 Manumitting Technologies Inc and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Manumitting Technologies Inc - initial API and implementation
+ */
+package org.eclipse.userstorage.ui.internal;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.jface.action.ContributionItem;
+import org.eclipse.jface.action.IMenuListener2;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.ui.handlers.HandlerUtil;
+import org.eclipse.ui.menus.IMenuService;
+import org.eclipse.ui.services.IServiceLocator;
+
+/**
+ * A simple handler that opens a pulldown menu associated with a tool item.
+ * Required as Eclipse only opens the drop-down menu when clicking on a tool item's
+ * drop-down indicator.
+ * <ul>
+ * <li> As is the norm for a {@code <command style="pulldown">} items, must
+ *    set the {@code id} attribute to the id of a menu contribution to show.</li>
+ * <li> Must provide an {@code intoolbar} parameter to any value, required to
+ *    prevent the command from appearing within the Quick Access.</li>
+ * </ul>
+ * <pre>
+ * &lt;extension point=&quot;org.eclipse.ui.menus&quot;&gt;
+ *   &lt;menuContribution allPopups=&quot;false&quot;
+ *       locationURI=&quot;toolbar:org.eclipse.ui.main.toolbar?after=additions&quot;&gt;
+ *     &lt;toolbar id=&quot;my.toolbar&quot;&gt;
+ *       &lt;command
+ *           commandId=&quot;org.eclipse.userstorage.ui.showPullDown&quot;
+ *           id=&quot;<b>org.eclipse.userstorage.ui.accounts</b>&quot;
+ *           tooltip=&quot;Linked Accounts&quot;
+ *           icon=&quot;icons/UserAccount.png&quot;
+ *           style=&quot;pulldown&quot; /&gt;
+ *     &lt;/toolbar&gt;
+ *   &lt;/menuContribution&gt;
+ *   &lt;menuContribution allPopups=&quot;false&quot;
+ *         locationURI=&quot;menu:<b>org.eclipse.userstorage.ui.accounts</b>&quot;&gt;
+ *     &lt;!-- menu definition --&gt;
+ *   &lt;/menuContribution&gt;
+ * &lt;/extension&gt;
+ * </pre>
+ */
+public class ShowPullDownMenu extends AbstractHandler
+{
+  @Override
+  public Object execute(ExecutionEvent event) throws ExecutionException
+  {
+    final IMenuService menuService = getService(event, IMenuService.class);
+
+    PopupMenu popup = new PopupMenu();
+    popup.configure(menuService, event);
+    popup.show();
+    return null;
+  }
+
+  /* @NonNull */
+  private <T> T getService(ExecutionEvent event, Class<T> clazz) throws ExecutionException
+  {
+    T service;
+    IServiceLocator locator = HandlerUtil.getActiveSite(event);
+    if (locator != null && (service = locator.getService(clazz)) != null)
+    {
+      return service;
+    }
+    locator = HandlerUtil.getActiveWorkbenchWindow(event);
+    if (locator != null && (service = locator.getService(clazz)) != null)
+    {
+      return service;
+    }
+    throw new ExecutionException("Unable to locate service " + clazz);
+  }
+
+  private static class PopupMenu
+  {
+    private IMenuService menuService;
+
+    private String menuId;
+
+    private Control parent;
+
+    private Point location;
+
+    /** Configure the popup menu id and location. */
+    private void configure(IMenuService menuService, ExecutionEvent event) throws ExecutionException
+    {
+      this.menuService = menuService;
+
+      // Support use of this command in org.eclipse.ui.menus's
+      // <command style=pulldown> and find the command id from the
+      // corresponding ContributionItem
+      if (!(event.getTrigger() instanceof Event) || ((Event)event.getTrigger()).type != SWT.Selection
+          || !(((Event)event.getTrigger()).widget instanceof ToolItem))
+      {
+        throw new ExecutionException("Unable to determine menu id");
+      }
+      ToolItem toolItem = (ToolItem)((Event)event.getTrigger()).widget;
+      if (!(toolItem.getData() instanceof ContributionItem))
+      {
+        throw new ExecutionException("Unable to determine menu id");
+      }
+      menuId = ((ContributionItem)toolItem.getData()).getId();
+
+      // place menu near the drop-down indicator on right-side
+      parent = toolItem.getParent();
+      Rectangle itemBounds = toolItem.getBounds(); // relative to toolbar
+      location = parent.toDisplay(new Point(itemBounds.x, itemBounds.y + itemBounds.height));
+    }
+
+    private void show()
+    {
+      final MenuManager menuManager = new MenuManager();
+      Menu menu = menuManager.createContextMenu(parent);
+      menuManager.addMenuListener(new IMenuListener2()
+      {
+        @Override
+        public void menuAboutToHide(IMenuManager manager)
+        {
+          parent.getDisplay().asyncExec(new Runnable()
+          {
+            @Override
+            public void run()
+            {
+              menuService.releaseContributions(menuManager);
+              menuManager.dispose();
+            }
+          });
+        }
+
+        @Override
+        public void menuAboutToShow(IMenuManager manager)
+        {
+          menuService.populateContributionManager(menuManager, "menu:" + menuId);
+        }
+      });
+      menu.setLocation(location);
+      menu.setVisible(true);
+    }
+  }
+}