// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.client.editor.simple.palette;
import com.google.appinventor.client.Images;
import com.google.appinventor.client.Ode;
import static com.google.appinventor.client.Ode.MESSAGES;
import com.google.appinventor.client.ComponentsTranslation;
import com.google.appinventor.client.utils.PZAwarePositionCallback;
import com.google.common.base.Strings;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupListener;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* Defines a widget that has the appearance of a question mark and
* creates a popup with information about a component when it is clicked on.
*
*/
public final class ComponentHelpWidget extends Image {
private static ImageResource imageResource = null;
// Keep track of the last time (in milliseconds) of the last closure
// so we don't reopen a popup too soon after closing it. Specifically,
// if a user clicks on the question-mark icon to close a popup, we
// don't want the question-mark click to reopen it.
private long lastClosureTime = 0;
private class ComponentHelpPopup extends PopupPanel {
private ComponentHelpPopup(final SimpleComponentDescriptor scd,
final Widget sender) {
// Create popup panel.
super(true);
setStyleName("ode-ComponentHelpPopup");
setTitle(scd.getName());
// Create title from component name.
Label titleBar = new Label(ComponentsTranslation.getComponentName(scd.getName()));
setTitle(scd.getName());
titleBar.setStyleName("ode-ComponentHelpPopup-TitleBar");
// Create content from help string.
String helpTextKey = scd.getExternal() ? scd.getHelpString() : scd.getName();
HTML helpText = new HTML(ComponentsTranslation.getComponentHelpString(helpTextKey));
helpText.setStyleName("ode-ComponentHelpPopup-Body");
// Create panel to hold the above three widgets and act as the
// popup's widget.
VerticalPanel inner = new VerticalPanel();
inner.add(titleBar);
inner.add(helpText);
// Create link to more information. This would be cleaner if
// GWT supported String.format.
String referenceComponentsUrl = Ode.getSystemConfig().getReferenceComponentsUrl();
String url = null;
if (scd.getExternal()) { // extensions will not have documentation hosted in ai2
url = scd.getHelpUrl().isEmpty() ? null : scd.getHelpUrl();
if (url != null) {
if (!url.startsWith("http:") && !url.startsWith("https:")) {
url = null;
} else {
// prevent embedded HTML tags, e.g. <script> in the URL
url = url.replaceAll("<", "%3C")
.replaceAll(">", "%3E")
.replaceAll("\"", "%22");
}
}
} else if (!Strings.isNullOrEmpty(referenceComponentsUrl)) {
if (!referenceComponentsUrl.endsWith("/")) {
referenceComponentsUrl += "/";
}
String categoryDocUrlString = scd.getCategoryDocUrlString();
url = (categoryDocUrlString == null)
? referenceComponentsUrl + "index.html"
: referenceComponentsUrl + categoryDocUrlString + ".html#" + scd.getName();
}
if (url != null) { // only show if there is a relevant URL
HTML link = new HTML("<a href=\"" + url + "\" target=\"_blank\">" +
MESSAGES.moreInformation() + "</a>");
link.setStyleName("ode-ComponentHelpPopup-Link");
inner.add(link);
}
setWidget(inner);
// When the panel is closed, save the time in milliseconds.
// This will help us avoid immediately reopening it if the user
// closed it by clicking on the question-mark icon.
addPopupListener(new PopupListener() {
@Override
public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
lastClosureTime = System.currentTimeMillis();
}
});
// Use a Pinch Zoom aware PopupPanel.PositionCallback to handle positioning to
// avoid the Google Chrome Pinch Zoom bug.
setPopupPositionAndShow(new PZAwarePositionCallback(sender.getElement()) {
@Override
public void setPosition(int offsetWidth, int offsetHeight) {
// Position the upper-left of the panel just to the right of the
// question-mark icon, unless that would make it too low.
final int X_OFFSET = 20;
final int Y_OFFSET = -5;
if(Window.Navigator.getUserAgent().contains("Chrome") && isPinchZoomed()) {
setPopupPosition(getTrueAbsoluteLeft() + 1 + X_OFFSET,
Math.min(getTrueAbsoluteTop() + 1 + Y_OFFSET,
Math.max(0, Window.getClientHeight()
- offsetHeight + Y_OFFSET)));
} else {
setPopupPosition(sender.getAbsoluteLeft() + X_OFFSET,
Math.min(sender.getAbsoluteTop() + Y_OFFSET,
Math.max(0, Window.getClientHeight()
- offsetHeight + Y_OFFSET)));
}
}
});
}
}
public ComponentHelpWidget(final SimpleComponentDescriptor scd) {
if (imageResource == null) {
Images images = Ode.getImageBundle();
imageResource = images.help();
}
AbstractImagePrototype.create(imageResource).applyTo(this);
addClickListener(new ClickListener() {
@Override
public void onClick(Widget sender) {
final long MINIMUM_MS_BETWEEN_SHOWS = 250; // .25 seconds
if (System.currentTimeMillis() - lastClosureTime >=
MINIMUM_MS_BETWEEN_SHOWS) {
new ComponentHelpPopup(scd, sender);
}
}
}
);
}
}