Show a popup with user information as tooltip on avatar images

Hovering over an avatar image opens a popup that shows information for
this user. At the moment only the image, the name and the email address
are shown, but this can be extended further. The popup is shown only
after a short delay when keeping the mouse over the image (similar to
how tooltips appear with a delay). By this it's avoided that popups are
unwantedly shown when moving the mouse cursor over the screen. The
popup automatically disappears when the mouse cursor is moved away from
the avatar image, but the popup stays open if the mouse cursor is moved
over the popup to allow text selection for copy & paste or clicking on
links in the popup if we add any later.

Change-Id: I0c17f8bd9c76a05d2c7562579018a4dac3a337e8
This commit is contained in:
Edwin Kempin
2013-04-23 12:57:50 +02:00
parent 902f67939d
commit d46919f89e
8 changed files with 140 additions and 27 deletions

View File

@@ -14,28 +14,49 @@
package com.google.gerrit.client;
import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gwt.event.dom.client.ErrorEvent;
import com.google.gwt.event.dom.client.ErrorHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.UIObject;
public class AvatarImage extends Image {
/** A default sized avatar image. */
public AvatarImage(String email) {
this(email, 0);
public AvatarImage(AccountInfo account) {
this(account, 0);
}
/**
* An avatar image for the given account using the requested size.
*
* @param email The email address of the account in which we are interested
* @param account The account in which we are interested
* @param size A requested size. Note that the size can be ignored depending
* on the avatar provider. A size <= 0 indicates to let the provider
* decide a default size.
*/
public AvatarImage(String email, int size) {
super(url(email, size));
public AvatarImage(AccountInfo account, int size) {
this(account, size, true);
}
/**
* An avatar image for the given account using the requested size.
*
* @param account The account in which we are interested
* @param size A requested size. Note that the size can be ignored depending
* on the avatar provider. A size <= 0 indicates to let the provider
* decide a default size.
* @param addPopup show avatar popup with user info on hovering over the
* avatar image
*/
public AvatarImage(AccountInfo account, int size, boolean addPopup) {
super(url(account.email(), size));
if (size > 0) {
// If the provider does not resize the image, force it in the browser.
@@ -50,6 +71,13 @@ public class AvatarImage extends Image {
setVisible(false);
}
});
if (addPopup) {
UserPopupPanel userPopup = new UserPopupPanel(account, false, false);
PopupHandler popupHandler = new PopupHandler(userPopup, this);
addMouseOverHandler(popupHandler);
addMouseOutHandler(popupHandler);
}
}
private static String url(String email, int size) {
@@ -68,4 +96,79 @@ public class AvatarImage extends Image {
}
return api.url();
}
private class PopupHandler implements MouseOverHandler, MouseOutHandler {
private final UserPopupPanel popup;
private final UIObject target;
private Timer showTimer;
private Timer hideTimer;
public PopupHandler(UserPopupPanel popup, UIObject target) {
this.popup = popup;
this.target = target;
popup.addDomHandler(new MouseOverHandler() {
@Override
public void onMouseOver(MouseOverEvent event) {
scheduleShow();
}
}, MouseOverEvent.getType());
popup.addDomHandler(new MouseOutHandler() {
@Override
public void onMouseOut(MouseOutEvent event) {
scheduleHide();
}
}, MouseOutEvent.getType());
}
@Override
public void onMouseOver(MouseOverEvent event) {
scheduleShow();
}
@Override
public void onMouseOut(MouseOutEvent event) {
scheduleHide();
}
private void scheduleShow() {
if (hideTimer != null) {
hideTimer.cancel();
hideTimer = null;
}
if ((popup.isShowing() && popup.isVisible()) || showTimer != null) {
return;
}
showTimer = new Timer() {
@Override
public void run() {
if (!popup.isShowing() || !popup.isVisible()) {
popup.showRelativeTo(target);
}
}
};
showTimer.schedule(600);
}
private void scheduleHide() {
if (showTimer != null) {
showTimer.cancel();
showTimer = null;
}
if (!popup.isShowing() || !popup.isVisible() || hideTimer != null) {
return;
}
hideTimer = new Timer() {
@Override
public void run() {
popup.hide();
}
};
hideTimer.schedule(50);
}
}
}

View File

@@ -178,7 +178,7 @@ public class FormatUtil {
return nameEmail(ai);
}
private static AccountInfo asInfo(Account acct) {
public static AccountInfo asInfo(Account acct) {
if (acct == null) {
return AccountInfo.create(0, null, null);
}

View File

@@ -19,6 +19,7 @@ import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
import com.google.gerrit.client.account.AccountCapabilities;
import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.client.admin.ProjectScreen;
import com.google.gerrit.client.changes.ChangeConstants;
import com.google.gerrit.client.changes.ChangeListScreen;
@@ -249,6 +250,11 @@ public class Gerrit implements EntryPoint {
return myAccount;
}
/** @return the currently signed in user's account data; empty account data if no account */
public static AccountInfo getUserAccountInfo() {
return FormatUtil.asInfo(myAccount);
}
/** @return access token to prove user identity during REST API calls. */
public static String getXGerritAuth() {
return xGerritAuth;
@@ -762,9 +768,9 @@ public class Gerrit implements EntryPoint {
}
private static void whoAmI(boolean canLogOut) {
Account account = getUserAccount();
final CurrentUserPopupPanel userPopup =
new CurrentUserPopupPanel(account, canLogOut);
AccountInfo account = getUserAccountInfo();
final UserPopupPanel userPopup =
new UserPopupPanel(account, canLogOut, true);
final FlowPanel userSummaryPanel = new FlowPanel();
class PopupHandler implements KeyDownHandler, ClickHandler {
private void showHidePopup() {
@@ -791,7 +797,7 @@ public class Gerrit implements EntryPoint {
final PopupHandler popupHandler = new PopupHandler();
final InlineLabel l = new InlineLabel(FormatUtil.name(account));
l.setStyleName(RESOURCES.css().menuBarUserName());
final AvatarImage avatar = new AvatarImage(account.getPreferredEmail(), 26);
final AvatarImage avatar = new AvatarImage(account, 26, false);
avatar.setStyleName(RESOURCES.css().menuBarUserNameAvatar());
userSummaryPanel.setStyleName(RESOURCES.css().menuBarUserNamePanel());
userSummaryPanel.add(l);

View File

@@ -14,8 +14,8 @@
package com.google.gerrit.client;
import com.google.gerrit.client.account.AccountInfo;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
@@ -24,8 +24,8 @@ import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.user.client.PluginSafePopupPanel;
public class CurrentUserPopupPanel extends PluginSafePopupPanel {
interface Binder extends UiBinder<Widget, CurrentUserPopupPanel> {
public class UserPopupPanel extends PluginSafePopupPanel {
interface Binder extends UiBinder<Widget, UserPopupPanel> {
}
private static final Binder binder = GWT.create(Binder.class);
@@ -41,9 +41,10 @@ public class CurrentUserPopupPanel extends PluginSafePopupPanel {
@UiField
Anchor settings;
public CurrentUserPopupPanel(Account account, boolean canLogOut) {
public UserPopupPanel(AccountInfo account, boolean canLogOut,
boolean showSettingsLink) {
super(/* auto hide */true, /* modal */false);
avatar = new AvatarImage(account.getPreferredEmail(), 100);
avatar = new AvatarImage(account, 100, false);
setWidget(binder.createAndBindUi(this));
// We must show and then hide this popup so that it is part of the DOM.
// Otherwise the image does not get any events. Calling hide() would
@@ -51,17 +52,21 @@ public class CurrentUserPopupPanel extends PluginSafePopupPanel {
show();
setVisible(false);
setStyleName(Gerrit.RESOURCES.css().userInfoPopup());
if (account.getFullName() != null) {
userName.setText(account.getFullName());
if (account.name() != null) {
userName.setText(account.name());
}
if (account.getPreferredEmail() != null) {
userEmail.setText(account.getPreferredEmail());
if (account.email() != null) {
userEmail.setText(account.email());
}
if (canLogOut) {
logout.setHref(Gerrit.selfRedirect("/logout"));
} else {
logout.setVisible(false);
}
settings.setHref(Gerrit.selfRedirect(PageLinks.SETTINGS));
if (showSettingsLink) {
settings.setHref(Gerrit.selfRedirect(PageLinks.SETTINGS));
} else {
settings.setVisible(false);
}
}
}

View File

@@ -360,10 +360,10 @@ public class PublishCommentScreen extends AccountScreen implements
final CommentEditorPanel editor =
new CommentEditorPanel(c, commentLinkProcessor);
if (c.getLine() == AbstractPatchContentTable.R_HEAD) {
editor.setAuthorNameText(Gerrit.getUserAccount().getPreferredEmail(),
editor.setAuthorNameText(Gerrit.getUserAccountInfo(),
Util.C.fileCommentHeader());
} else {
editor.setAuthorNameText(Gerrit.getUserAccount().getPreferredEmail(),
editor.setAuthorNameText(Gerrit.getUserAccountInfo(),
Util.M.lineHeader(c.getLine()));
}
editor.setOpen(true);

View File

@@ -65,8 +65,7 @@ public class CommentEditorPanel extends CommentPanel implements ClickHandler,
comment = plc;
addStyleName(Gerrit.RESOURCES.css().commentEditorPanel());
setAuthorNameText(Gerrit.getUserAccount().getPreferredEmail(),
PatchUtil.C.draft());
setAuthorNameText(Gerrit.getUserAccountInfo(), PatchUtil.C.draft());
setMessageText(plc.getMessage());
addDoubleClickHandler(this);

View File

@@ -63,7 +63,7 @@ public class CommentPanel extends Composite implements HasDoubleClickHandlers,
this(commentLinkProcessor);
setMessageText(message);
setAuthorNameText(author.email(), FormatUtil.name(author));
setAuthorNameText(author, FormatUtil.name(author));
setDateText(FormatUtil.shortFormatDayTime(when));
final CellFormatter fmt = header.getCellFormatter();
@@ -126,8 +126,8 @@ public class CommentPanel extends Composite implements HasDoubleClickHandlers,
SafeHtml.set(messageText, buf);
}
public void setAuthorNameText(final String authorEmail, final String nameText) {
header.setWidget(0, 0, new AvatarImage(authorEmail, 26));
public void setAuthorNameText(final AccountInfo author, final String nameText) {
header.setWidget(0, 0, new AvatarImage(author, 26));
header.setText(0, 1, nameText);
}