package org.geogebra.web.web.gui.inputbar;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.TreeSet;
import org.geogebra.common.gui.SetLabels;
import org.geogebra.common.gui.inputbar.InputBarHelpPanel;
import org.geogebra.common.gui.util.TableSymbols;
import org.geogebra.common.main.App;
import org.geogebra.common.main.Localization;
import org.geogebra.common.move.views.BooleanRenderable;
import org.geogebra.web.html5.gui.inputfield.AutoCompleteW;
import org.geogebra.web.html5.main.AppW;
import org.geogebra.web.web.gui.GuiManagerW;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.TextAlign;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SplitLayoutPanel;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* @author G. Sturr
*
*/
public class InputBarHelpPanelW extends VerticalPanel implements SetLabels, BooleanRenderable {
private AppW app;
private Tree indexTree;
private VerticalPanel syntaxPanel;
private Button btnOnlineHelp;
private Button btnClose;
private LocaleSensitiveComparator comparator;
private SplitLayoutPanel sp;
private ScrollPanel detailScroller;
private InlineLabel lblSyntax;
private MyTreeItem itmFunction;
private AutoCompleteW inputField;
/**
* @param app
* application
*/
public InputBarHelpPanelW(AppW app) {
super();
this.app = app;
comparator = new LocaleSensitiveComparator();
createGUI();
setLabels();
}
/**
* @param field
* input field
*/
public void setInputField(AutoCompleteW field) {
this.inputField = field;
}
private void createGUI() {
// create syntax panel
syntaxPanel = new VerticalPanel();
// button panel
FlowPanel pnlButton = new FlowPanel();
// create help button if not in exam mode
btnOnlineHelp = new Button(app.getLocalization().getMenu(
"ShowOnlineHelp"));
btnOnlineHelp.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
openOnlineHelp();
}
});
render(app.getNetworkOperation().isOnline());
app.getNetworkOperation().getView().add(this);
btnOnlineHelp.addStyleName("inputHelp-OnlineHelpBtn");
pnlButton.add(btnOnlineHelp);
pnlButton.getElement().getStyle().setFloat(Style.Float.RIGHT);
// create close button
btnClose = new Button(app.getLocalization().getMenu("Close"));
btnClose.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
hide();
}
});
btnClose.setStyleName("inputHelp-CancelBtn");
pnlButton.add(btnClose);
// create detail title panel
lblSyntax = new InlineLabel();
lblSyntax.getElement().getStyle().setTextAlign(TextAlign.LEFT);
HorizontalPanel detailTitlePanel = new HorizontalPanel();
detailTitlePanel
.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);
detailTitlePanel.add(lblSyntax);
detailTitlePanel.add(pnlButton);
detailTitlePanel.addStyleName("inputHelp-detailPanelTitle");
add(detailTitlePanel);
// create the detail panel
VerticalPanel detailPanel = new VerticalPanel();
detailPanel.add(syntaxPanel);
detailPanel.setWidth("100%");
// create the index tree and put it in a scroll panel
indexTree = new Tree() {
@Override
public void setSelectedItem(TreeItem item, boolean fireEvents) {
if (item == null) {
super.setSelectedItem(null, fireEvents);
return;
}
onSelectionNative(item, fireEvents);
}
private native void onSelectionNative(TreeItem item, boolean fireEvents)/*-{
this.@com.google.gwt.user.client.ui.Tree::onSelection(Lcom/google/gwt/user/client/ui/TreeItem;ZZ)(item, fireEvents, false);
}-*/;
};
indexTree.addStyleName("inputHelp-tree");
indexTree.setAnimationEnabled(true);
// show only mathematical functions for exam simple calculator
if (app.getArticleElement().hasDataParamEnableGraphing()
&& !app.getArticleElement().getDataParamEnableGraphing(true)) {
detailScroller = new ScrollPanel(detailPanel);
detailScroller.setStyleName("AVHelpDetailScroller");
add(detailScroller);
} else {
ScrollPanel treeScroller = new ScrollPanel(indexTree);
treeScroller.setSize("100%", "100%");
// put the detail panel and index tree side by side in a
// SplitLayoutPanel
sp = new SplitLayoutPanel();
sp.addStyleName("ggbdockpanelhack");
sp.addEast(treeScroller, 280);
sp.add(new ScrollPanel(detailPanel));
// now add the split panel to our main panel
add(sp);
}
}
@Override
public void render(boolean online) {
btnOnlineHelp.setEnabled(online);
}
private void showOnlineHelpButton(boolean show) {
btnOnlineHelp.setVisible(show);
}
// =================================================================
// Getters/Setters & Event Handlers
// =================================================================
/**
* Opens browser with online help
*/
protected void openOnlineHelp() {
if (getSelectedCommand() == null) {
((GuiManagerW) app.getGuiManager()).openHelp("InputBar");
} else if (getSelectedCommand().equals(
app.getLocalization().getMenu("MathematicalFunctions"))) {
((GuiManagerW) app.getGuiManager()).openHelp(App.WIKI_OPERATORS);
} else {
((GuiManagerW) app.getGuiManager())
.openCommandHelp(getSelectedCommand());
}
}
/**
* Hide the parent popup
*/
protected void hide() {
((InputBarHelpPopup) this.getParent()).hide();
}
/**
* @return local command name
*/
public String getSelectedCommand() {
if (indexTree == null || indexTree.getSelectedItem() == null
|| indexTree.getSelectedItem().getChildCount() > 0) {
return null;
}
return indexTree.getSelectedItem().getWidget().getElement()
.getInnerText();
}
@Override
public void setLabels() {
setCommands();
// show Mathematical Functions tree item initially
indexTree.setSelectedItem(itmFunction);
updateDetailPanel();
btnOnlineHelp.setText(app.getLocalization().getPlain("ShowOnlineHelp"));
}
/**
* Adjusts the panel size relative to the current application panel size
*
* @param maxOffsetHeight
* max height
* @param scale
* transform scale
*/
public void updateGUI(int maxOffsetHeight, double scale) {
showOnlineHelpButton(!app.isExam());
int h = (int) (maxOffsetHeight * scale
- 60);
double width = ((GuiManagerW) app.getGuiManager()).getRootComponent()
.getOffsetWidth() * scale - 60;
if (app.getArticleElement().hasDataParamEnableGraphing()
&& !app.getArticleElement().getDataParamEnableGraphing(true)) {
int w = (int) Math.min(400, width);
detailScroller.setPixelSize(w, h);
} else {
int w = (int) Math.min(700, width);
sp.setPixelSize(w, h);
}
}
public int getPreferredWidth(double scale) {
double width = ((GuiManagerW) app.getGuiManager()).getRootComponent()
.getOffsetWidth() * scale - 60;
if (app.getArticleElement().hasDataParamEnableGraphing()
&& !app.getArticleElement().getDataParamEnableGraphing(true)) {
return (int) Math.min(400, width);
}
return (int) Math.min(700, width);
}
// =================================================================
// Index Tree
// =================================================================
/**
* Update commands tree
*/
public void setCommands() {
indexTree.clear();
itmFunction = new MyTreeItem();
itmFunction.setWidget(new TreeItemButton(app.getLocalization().getMenu(
"MathematicalFunctions"), itmFunction, false));
indexTree.addItem(itmFunction);
MyTreeItem itmAllCommands = new MyTreeItem();
itmAllCommands.setWidget(new TreeItemButton(app.getLocalization()
.getMenu("AllCommands"), itmAllCommands, false));
addCmdNames(itmAllCommands, getAllCommandsTreeSet());
indexTree.addItem(itmAllCommands);
TreeMap<String, TreeSet<String>> cmdMap = getCommandTreeMap();
Iterator<Entry<String, TreeSet<String>>> i = cmdMap.entrySet()
.iterator();
while (i.hasNext()) {
Entry<String, TreeSet<String>> entry = i.next();
// add command set branch to tree
String cmdSetName = entry.getKey();
TreeItem itmCmdSet = new MyTreeItem();
itmCmdSet
.setWidget(new TreeItemButton(cmdSetName, itmCmdSet, false));
indexTree.addItem(itmCmdSet);
// add command names to this branch
TreeSet<String> cmdNames = entry.getValue();
addCmdNames(itmCmdSet, cmdNames);
}
}
private void addCmdNames(TreeItem item, TreeSet<String> names) {
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String cmdName = it.next();
if (cmdName != null && cmdName.length() > 0) {
MyTreeItem cmd = new MyTreeItem();
cmd.setWidget(new TreeItemButton(cmdName, cmd, true));
item.addItem(cmd);
}
}
}
private class TreeItemButton extends InlineLabel {
public TreeItemButton(String text, final TreeItem item,
final boolean isLeaf) {
super(text);
addStyleName("inputHelp-treeItem");
if (isLeaf) {
addStyleName("inputHelp-leaf");
}
this.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (!isLeaf) {
item.setState(!item.getState());
updateDetailPanel();
} else {
item.setState(true);
updateDetailPanel();
}
}
});
}
}
private static class MyTreeItem extends TreeItem {
protected MyTreeItem() {
// avoid synth access warning
}
@Override
public void setWidget(Widget newWidget) {
super.setWidget(newWidget);
this.addStyleName("inputHelp-treeItem");
}
}
// =================================================================
// Command Name Sorting
// =================================================================
/**
* Javascript comparator for different locales.
*
* TODO: handle accented characters
*/
@SuppressFBWarnings({ "SE_COMPARATOR_SHOULD_BE_SERIALIZABLE",
"not needed" })
private static class LocaleSensitiveComparator
implements Comparator<String> {
protected LocaleSensitiveComparator() {
// avoid synth access warning
}
@Override
public native int compare(String source, String target) /*-{
return source.localeCompare(target);
}-*/;
}
private TreeMap<String, TreeSet<String>> getCommandTreeMap() {
return InputBarHelpPanel.getCommandTreeMap(app, comparator);
}
private TreeSet<String> getAllCommandsTreeSet() {
return InputBarHelpPanel.getAllCommandsTreeSet(app, comparator);
}
// =================================================================
// Syntax Description
// =================================================================
/**
* Update syntax panel
*/
protected void updateDetailPanel() {
syntaxPanel.clear();
if (getSelectedCommand() == null) {
lblSyntax.setText("");
return;
}
lblSyntax.setText(getSelectedCommand());
ArrayList<Widget> rows;
if (getSelectedCommand().equals(
app.getLocalization().getMenu("MathematicalFunctions"))) {
rows = functionTableHTML();
syntaxPanel.removeStyleName("inputHelp-cmdSyntax");
syntaxPanel.addStyleName("inputHelp-functionTable");
} else {
rows = cmdSyntaxHTML();
syntaxPanel.removeStyleName("inputHelp-functionTable");
syntaxPanel.addStyleName("inputHelp-cmdSyntax");
}
for(int i = 0; i< rows.size(); i++){
syntaxPanel.add(rows.get(i));
}
}
private ArrayList<Widget> cmdSyntaxHTML() {
ArrayList<Widget> ret = new ArrayList<Widget>();
// internal name of selected command
String cmd = app.getReverseCommand(getSelectedCommand());
Localization loc = app.getLocalization();
String syntaxBasic = loc.getCommandSyntax(cmd);
if (loc.isCASCommand(cmd)) {
if (!syntaxBasic.equals(cmd + Localization.syntaxStr)) {
formattedHTMLString(ret,syntaxBasic, false);
}
// don't show cas specific syntax for exam graphing
boolean supportsCAS = app.getSettings().getCasSettings().isEnabled();
if (!app.getArticleElement().hasDataParamEnableGraphing()
|| (app.getArticleElement().hasDataParamEnableGraphing() && supportsCAS)) {
Label headCAS = new Label(loc.getMenu("Type.CAS") + ":");
headCAS.addStyleName("inputHelp-headerCAS");
ret.add(headCAS);
String syntaxCAS = loc.getCommandSyntaxCAS(cmd);
formattedHTMLString(ret, syntaxCAS, true);
}
} else {
formattedHTMLString(ret, syntaxBasic, false);
}
return ret;
}
/**
* Converts a java string to a SafeHTML string with newline characters
* replaced by paragraph tags. This tag is required for the hanging indent
* css style used to format syntax descriptions.
* @param b
* @param ret
*/
private void formattedHTMLString(ArrayList<Widget> ret, String s, boolean b) {
String[]lines = s.split("\n");
for(String line: lines){
Label syntax = syntaxLabel(line);
if(b){
syntax.addStyleName("inputHelp-CAScmdSyntax");
}
ret.add(syntax);
}
}
private Label syntaxLabel(String line) {
Label syntax = new Label(line);
final String fLine = line;
syntax.addMouseDownHandler(new MouseDownHandler(){
@Override
public void onMouseDown(MouseDownEvent event) {
event.preventDefault();
event.stopPropagation();
insertText(fLine);
}});
return syntax;
}
/**
* @param text
* to be inserted into input field
*/
void insertText(String text) {
if (this.inputField != null) {
this.inputField.autocomplete(text);
this.inputField.setFocus(true, true);
}
}
private ArrayList<Widget> functionTableHTML() {
String[][] f = TableSymbols.getTranslatedFunctionsGrouped(app);
ArrayList<Widget> ret = new ArrayList<Widget>();
// sb.append("<table>");
for (int i = 0; i < f.length; i++) {
HorizontalPanel widget = new HorizontalPanel();
for (int j = 0; j < f[i].length; j++) {
Label syntax = syntaxLabel(f[i][j]);
widget.add(syntax);
}
ret.add(widget);
}
return ret;
}
/**
* @param currentCommand
* command to be selected
*/
public void focusCommand(String currentCommand) {
if (indexTree == null || currentCommand == null) {
return;
}
for (int i = 2; i < indexTree.getItemCount(); i++) {
TreeItem group = indexTree.getItem(i);
if (group == null) {
continue;
}
for (int j = 0; j < group.getChildCount(); j++) {
if (group.getChild(j).getElement().getInnerText()
.equalsIgnoreCase(currentCommand)) {
group.setState(true);
indexTree.setSelectedItem(group.getChild(j), false);
updateDetailPanel();
return;
}
}
}
}
}