package org.geogebra.keyboard.web;
import java.util.ArrayList;
import java.util.List;
import org.geogebra.common.euclidian.event.PointerEventType;
import org.geogebra.common.util.lang.Language;
import org.geogebra.common.util.lang.Unicode;
import org.geogebra.keyboard.base.Accents;
import org.geogebra.keyboard.base.Action;
import org.geogebra.keyboard.base.Keyboard;
import org.geogebra.keyboard.base.KeyboardFactory;
import org.geogebra.keyboard.base.Resource;
import org.geogebra.keyboard.base.listener.KeyboardObserver;
import org.geogebra.keyboard.base.model.Row;
import org.geogebra.keyboard.base.model.WeightedButton;
import org.geogebra.web.html5.gui.util.ClickStartHandler;
import org.geogebra.web.html5.gui.util.KeyboardLocale;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CustomButton;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.ToggleButton;
import com.google.gwt.user.client.ui.Widget;
public class TabbedKeyboard extends FlowPanel {
private static final int TAB_NUMBERS = 0;
private static final int TAB_FX = 1;
private static final int TAB_ABC = 2;
private static final int TAB_ALPHA = 3;
private static final int TAB_SPECIAL = 4;
/**
* minimum width of the whole application to use normal font (small font
* otherwise)
*/
protected static final int MIN_WIDTH_FONT = 485;
private class KeyboardSwitcher extends FlowPanel {
private static final int SWITCHER_HEIGHT = 40;
private FlowPanel contents;
private List<Button> switches;
private CustomButton closeButton;
private ToggleButton moreButton;
private Integer width = null;
private int selectedIdx;
private Button selectedButton;
boolean isSpecialActive = false;
public KeyboardSwitcher() {
addStyleName("KeyboardSwitcher");
add(makeCloseButton());
contents = new FlowPanel();
contents.addStyleName("switcherContents");
add(contents);
switches = new ArrayList<>();
}
public void addMoreButton() {
contents.add(makeMoreButton());
}
public void addSwitch(final KeyPanelBase keyboard,
String string) {
Button btn = makeSwitcherButton(keyboard, string);
switches.add(btn);
contents.add(btn);
}
private Button makeSwitcherButton(final KeyPanelBase keyboard,
String string) {
final Button ret = new Button(string);
ClickStartHandler.init(ret, new ClickStartHandler(true, true) {
@Override
public void onClickStart(int x, int y, PointerEventType type) {
for (int i = 0; i <= switches.size(); i++) {
((FlowPanel) keyboard.getParent()).getWidget(i)
.setVisible(false);
setSelected(i, false);
}
currentKeyboard = keyboard;
keyboard.setVisible(true);
setSelected(ret, true);
}
});
return ret;
}
private void setSelected(Button btn, boolean value) {
if (value) {
btn.addStyleName("selected");
selectedButton = btn;
} else {
btn.removeStyleName("selected");
}
}
private void setSelected(int idx, boolean value) {
if (idx == TAB_SPECIAL) {
if (value) {
setSelected(switches.get(TAB_ABC), value);
isSpecialActive = true;
}
return;
} else if (idx == TAB_ABC && isSpecialActive) {
setSelected(switches.get(TAB_ABC), value);
isSpecialActive = false;
} else {
setSelected(switches.get(idx), value);
}
if (value) {
selectedIdx = idx;
}
}
private void unselectAll() {
for (Widget btn: switches) {
btn.removeStyleName("selected");
}
}
private Widget makeCloseButton() {
Image img = new Image(KeyboardResources.INSTANCE
.keyboard_close_black().getSafeUri().asString());
Image hoverImg = new Image(KeyboardResources.INSTANCE
.keyboard_close_purple().getSafeUri().asString());
closeButton = new CustomButton(){};
closeButton.getUpFace().setImage(img);
closeButton.getUpHoveringFace().setImage(hoverImg);
closeButton.addStyleName("closeTabbedKeyboardButton");
ClickStartHandler.init(closeButton, new ClickStartHandler() {
@Override
public void onClickStart(int x, int y, PointerEventType type) {
updateKeyBoardListener.keyBoardNeeded(false, null);
keyboardWanted = false;
}
});
return closeButton;
}
private Widget makeMoreButton() {
Image img = new Image(KeyboardResources.INSTANCE
.keyboard_more().getSafeUri().asString());
Image hoverImg = new Image(KeyboardResources.INSTANCE
.keyboard_more_purple().getSafeUri().asString());
moreButton = new ToggleButton(img, hoverImg);
moreButton.getUpHoveringFace().setImage(hoverImg);
moreButton.addStyleName("moreKeyboardButton");
ClickStartHandler.init(moreButton, new ClickStartHandler() {
@Override
public void onClickStart(int x, int y, PointerEventType type) {
// unselectAll();
showHelp(moreButton.getAbsoluteLeft() + moreButton.getOffsetWidth(),
moreButton.getAbsoluteTop());
}
});
// moreButton.addMouseOverHandler(new MouseOverHandler() {
//
// @Override
// public void onMouseOver(MouseOverEvent event) {
// unselectAll();
// }
// });
//
// moreButton.addMouseOutHandler(new MouseOutHandler() {
//
// @Override
// public void onMouseOut(MouseOutEvent event) {
// setSelected(selectedButton, true);
// }
// });
return moreButton;
}
public void setWidth(int width) {
// if (this.width != null) {
// return;
// }
// this.width = width;
// // contents.getElement().getStyle().setProperty("height", getOffsetHeight(), Unit.PX);
// contents.getElement().getStyle().setProperty("width", width, Unit.PX);
//
}
public void reset() {
if (moreButton != null) {
moreButton.setValue(false);
}
}
public void select(int idx) {
for (int i = 0; i < tabs.getWidgetCount();i++) {
tabs.getWidget(i).setVisible(i == idx);
setSelected(i, i == idx);
}
}
}
private static final int BASE_WIDTH = 70;
private KeyboardLocale locale;
private boolean isSmallKeyboard;
protected HasKeyboard app;
private ArrayList<Keyboard> layouts = new ArrayList<Keyboard>(4);
private Object keyboardLocale;
private ButtonHandler bh;
private UpdateKeyBoardListener updateKeyBoardListener;
private FlowPanel tabs;
private KeyboardSwitcher switcher;
protected KeyPanelBase currentKeyboard=null;
protected boolean keyboardWanted = false;
public TabbedKeyboard() {
}
public UpdateKeyBoardListener getUpdateKeyBoardListener() {
return updateKeyBoardListener;
}
public void setListener(UpdateKeyBoardListener listener) {
this.updateKeyBoardListener = listener;
}
public void buildGUI(ButtonHandler bh, HasKeyboard app) {
KeyboardFactory kbf = new KeyboardFactory();
this.tabs = new FlowPanel();
switcher = new KeyboardSwitcher();
this.app = app;
this.bh = bh;
this.locale = app.getLocalization();
this.keyboardLocale = locale.getLocaleStr();
KeyPanelBase keyboard = buildPanel(kbf.createMathKeyboard(), bh);
tabs.add(keyboard);
switcher.addSwitch(keyboard, "123");
keyboard = buildPanel(kbf.createFunctionsKeyboard(), bh);
tabs.add(keyboard);
keyboard.setVisible(false);
switcher.addSwitch(keyboard, "fx");
keyboard = buildPanel(
kbf.createLettersKeyboard(filter(locale.getKeyboardRow(1).replace("'", "")),
filter(locale.getKeyboardRow(2)),
filter(locale.getKeyboardRow(3))),
bh);
tabs.add(keyboard);
keyboard.setVisible(false);
switcher.addSwitch(keyboard, "ABC");
keyboard = buildPanel(kbf.createGreekKeyboard(), bh);
tabs.add(keyboard);
keyboard.setVisible(false);
switcher.addMoreButton();
switcher.addSwitch(keyboard, Unicode.alphaBetaGamma);
switcher.setSelected(0, true);
// add special char tab
keyboard = buildPanel(kbf.createSpecialSymbolsKeyboard(), bh);
keyboard.setVisible(false);
tabs.add(keyboard);
add(switcher);
add(tabs);
addStyleName("KeyBoard");
addStyleName("TabbedKeyBoard");
addStyleName("gwt-PopupPanel");
}
private String filter(String keys) {
StringBuilder sb = new StringBuilder(11);
for (int i = 0; i < keys.length(); i += 2) {
sb.append(keys.charAt(i));
}
// TODO remove the replace once ggbtrans is fixed
return sb.toString().replace("'", "");
}
private KeyPanelBase buildPanel(Keyboard layout, final ButtonHandler bh) {
final KeyPanelBase keyboard = new KeyPanelBase(layout);
layouts.add(layout);
keyboard.addStyleName("KeyPanel");
keyboard.addStyleName("normal");
updatePanel(keyboard, layout, bh, getBaseSize());
layout.registerKeyboardObserver(new KeyboardObserver() {
public void keyboardModelChanged(Keyboard l2) {
updatePanel(keyboard, l2, bh, getBaseSize());
}
});
return keyboard;
}
/**
*
* @return button base size
*/
int getBaseSize() {
final int n = 11;
return (int) ((app.getInnerWidth() - 50) > BASE_WIDTH * n ? BASE_WIDTH
: (app.getInnerWidth() - 50) / n);
}
void updatePanel(KeyPanelBase keyboard, Keyboard layout,
ButtonHandler bh, int baseSize) {
keyboard.reset(layout);
int index = 0;
for (Row row : layout.getModel().getRows()) {
for (WeightedButton wb : row.getButtons()) {
if (!Action.NONE.name().equals(wb.getActionName())) {
KeyBoardButtonBase button = makeButton(wb, bh);
keyboard.addToRow(index, button);
}
}
index++;
}
updatePanelSize(keyboard, baseSize);
}
/**
* This is much faster than updatePanel as it doesn't clear the model. It
* assumes the model and button layout are in sync.
*/
private void updatePanelSize(KeyPanelBase keyboard, int baseSize) {
int buttonIndex = 0;
int margins = 4;
if (keyboard.getLayout() == null) {
return;
}
KeyBoardButtonBase button = null;
for (Row row : keyboard.getLayout().getModel().getRows()) {
double offset = 0;
for (WeightedButton wb : row.getButtons()) {
if (Action.NONE.name().equals(wb.getActionName())) {
offset = wb.getWeight();
} else {
button = keyboard.getButtons()
.get(buttonIndex);
if (offset > 0) {
button.getElement().getStyle()
.setMarginLeft(offset * baseSize + margins / 2, Unit.PX);
}
button.getElement().getStyle()
.setWidth(wb.getWeight() * baseSize - margins, Unit.PX);
offset = 0;
buttonIndex++;
}
}
if (Action.NONE.name().equals(row.getButtons()
.get(row.getButtons().size() - 1).getActionName())) {
button.getElement().getStyle()
.setMarginRight(offset * baseSize + margins / 2,
Unit.PX);
}
}
if (app.getInnerWidth() < getMinWidthWithoutScaling()) {
addStyleName("scale");
removeStyleName("normal");
removeStyleName("smallerFont");
if (app.getInnerWidth() < MIN_WIDTH_FONT) {
addStyleName("smallerFont");
}
} else {
addStyleName("normal");
removeStyleName("scale");
removeStyleName("smallerFont");
}
}
private KeyBoardButtonBase makeButton(WeightedButton wb, ButtonHandler b) {
switch (wb.getResourceType()) {
case TRANSLATION_MENU_KEY:
if (wb.getResourceName().equals("Translate.currency")) {
return new KeyBoardButtonBase(Language.getCurrency(keyboardLocale.toString()),
Language.getCurrency(keyboardLocale.toString()), b);
}
return new KeyBoardButtonBase(locale.getMenu(wb.getActionName()),
wb.getActionName().replace("Function.", ""), b);
case TRANSLATION_COMMAND_KEY:
return new KeyBoardButtonBase(locale.getCommand(wb.getActionName()),
wb.getActionName(), b);
case DEFINED_CONSTANT:
return functionButton(wb, b);
case TEXT:
default:
if (wb.getActionName().equals(Action.TOGGLE_ACCENT_ACUTE.name())) {
return accentButton(Accents.ACCENT_ACUTE, b);
}
if (wb.getActionName().equals(Action.TOGGLE_ACCENT_CARON.name())) {
return accentButton(Accents.ACCENT_CARON, b);
}
if (wb.getActionName()
.equals(Action.TOGGLE_ACCENT_CIRCUMFLEX.name())) {
return accentButton(Accents.ACCENT_CIRCUMFLEX, b);
}
if (wb.getActionName().equals(Action.TOGGLE_ACCENT_GRAVE.name())) {
return accentButton(Accents.ACCENT_GRAVE, b);
}
if (wb.getActionName().equals("*")) {
return new KeyBoardButtonBase(Unicode.MULTIPLY + "", "*", b);
}
if (wb.getActionName().equals("/")) {
return new KeyBoardButtonBase(Unicode.DIVIDE, Unicode.DIVIDE, b);
}
if (wb.getActionName().equals("|")) {
return new KeyBoardButtonBase("|a|", "|", b);
}
if (wb.getActionName().equals("-")) {
return new KeyBoardButtonBase(Unicode.MINUS + "", "-", b);
}
if (wb.getActionName().equals(Unicode.EULER_STRING)) {
return new KeyBoardButtonBase("e", Unicode.EULER_STRING, b);
}
if (wb.getActionName().equals(Action.SWITCH_TO_SPECIAL_SYMBOLS.name())) {
return functionButton(wb, bh);
}
if (wb.getActionName().equals(Action.SWITCH_TO_ABC.name())) {
return functionButton(wb, bh);
}
if (wb.getActionName().equals("" + Unicode.LFLOOR)) {
return new KeyBoardButtonBase(KeyboardConstants.FLOOR,wb.getActionName(),bh);
}
if (wb.getActionName().equals("" + Unicode.LCEIL)) {
return new KeyBoardButtonBase(KeyboardConstants.CEIL, wb.getActionName(), bh);
}
return new KeyBoardButtonBase(wb.getActionName(),
wb.getActionName(), b);
}
}
protected boolean isAccent(String txt) {
return Accents.ACCENT_GRAVE.equals(txt)
|| Accents.ACCENT_ACUTE.equals(txt)
|| Accents.ACCENT_CIRCUMFLEX.equals(txt)
|| Accents.ACCENT_CARON.equals(txt);
}
private KeyBoardButtonBase accentButton(String accent, ButtonHandler b) {
return new KeyBoardButtonBase(accent, accent, b);
}
protected void processShift() {
for (Keyboard layout : layouts) {
layout.toggleCapsLock();
}
}
protected void disableCapsLock() {
for (Keyboard layout : layouts) {
layout.disableCapsLock();
}
}
protected void processAccent(String text) {
for (Keyboard layout : layouts) {
layout.toggleAccent(text);
}
}
private KeyBoardButtonBase functionButton(WeightedButton button,
ButtonHandler bh) {
String resourceName = button.getResourceName();
if (resourceName.equals(Resource.RETURN_ENTER.name())) {
return new KeyBoardButtonFunctionalBase(
KeyboardResources.INSTANCE.keyboard_enter_black(), bh,
Action.RETURN_ENTER);
} else if (resourceName.equals(Resource.BACKSPACE_DELETE.name())) {
return new KeyBoardButtonFunctionalBase(
KeyboardResources.INSTANCE.keyboard_backspace(), bh,
Action.BACKSPACE_DELETE);
} else if (resourceName.equals(Resource.LEFT_ARROW.name())) {
return new KeyBoardButtonFunctionalBase(
KeyboardResources.INSTANCE.keyboard_arrowLeft_black(), bh,
Action.LEFT_CURSOR);
} else if (resourceName.equals(Resource.RIGHT_ARROW.name())) {
return new KeyBoardButtonFunctionalBase(
KeyboardResources.INSTANCE.keyboard_arrowRight_black(), bh,
Action.RIGHT_CURSOR);
} else if (resourceName.equals(Resource.POWA2.name())) {
return new KeyBoardButtonBase("a^2", "^2", bh);
} else if (resourceName.equals(Resource.POWAB.name())) {
return new KeyBoardButtonBase("a^b", "a^x", bh);
}
else if (resourceName.equals(Resource.CAPS_LOCK.name())) {
return new KeyBoardButtonFunctionalBase(
KeyboardResources.INSTANCE.keyboard_shift(), bh,
Action.CAPS_LOCK);
} else if (resourceName.equals(Resource.CAPS_LOCK_ENABLED.name())) {
return new KeyBoardButtonFunctionalBase(
KeyboardResources.INSTANCE.keyboard_shiftDown(), bh,
Action.CAPS_LOCK);
} else if (resourceName.equals(Resource.POW10_X.name())) {
return new KeyBoardButtonBase("10^x", "10^", bh);
} else if (resourceName.equals(Resource.POWE_X.name())) {
return new KeyBoardButtonBase("e^x",
Unicode.EULER_STRING + "^", bh);
}
else if (resourceName.equals(Resource.LOG_10.name())) {
return new KeyBoardButtonBase("log_10", "log10", bh);
}
else if (resourceName.equals(Resource.LOG_B.name())) {
return new KeyBoardButtonBase("log_b", "log_", bh);
}
else if (resourceName.equals(Resource.A_N.name())) {
return new KeyBoardButtonBase("a_n", "_", bh);
}
else if (resourceName.equals(Resource.N_ROOT.name())) {
return new KeyBoardButtonFunctionalBase(
KeyboardResources.INSTANCE.nroot(), button.getActionName(),
bh);
}
else if (resourceName.equals(Resource.INTEGRAL.name())) {
return new KeyBoardButtonFunctionalBase(
KeyboardResources.INSTANCE.integral(),
button.getActionName(), bh);
} else if (resourceName.equals(Resource.DERIVATIVE.name())) {
return new KeyBoardButtonFunctionalBase(
KeyboardResources.INSTANCE.derivative(),
button.getActionName(), bh);
}
if (resourceName.equals(Resource.ROOT.name())) {
return new KeyBoardButtonFunctionalBase(
KeyboardResources.INSTANCE.sqrt(),
button.getActionName(), bh);
}
if (resourceName.equals(KeyboardConstants.SWITCH_TO_SPECIAL_SYMBOLS)) {
return new KeyBoardButtonFunctionalBase(KeyboardConstants.SWITCH_TO_SPECIAL_SYMBOLS, bh, Action.SWITCH_TO_SPECIAL_SYMBOLS);
}
if (resourceName.equals("ABC")) {
return new KeyBoardButtonFunctionalBase("ABC", bh, Action.SWITCH_TO_ABC);
}
return new KeyBoardButtonBase(button.getActionName(),
button.getActionName(), bh);
}
/**
*
*/
public void updateSize() {
if (app.getInnerWidth() < 0) {
return;
}
// -2 for applet border
this.setWidth(app.getInnerWidth() + "px");
boolean shouldBeSmall = app.needsSmallKeyboard();
if (shouldBeSmall && !isSmallKeyboard) {
this.addStyleName("lowerHeight");
this.isSmallKeyboard = true;
} else if (!shouldBeSmall && isSmallKeyboard) {
this.removeStyleName("lowerHeight");
this.isSmallKeyboard = false;
}
updateHeight();
for (int i = 0; tabs != null && i < tabs.getWidgetCount(); i++) {
Widget wdgt = tabs.getWidget(i);
if (wdgt instanceof KeyPanelBase) {
updatePanelSize((KeyPanelBase) wdgt, getBaseSize());
}
}
}
private void updateHeight() {
if (app != null) {
app.updateKeyboardHeight();
}
}
/**
* loads the translation-files for the active language if it is different
* from the last loaded language and sets the {@link #keyboardLocale} to the
* new language
*/
public void checkLanguage() {
switcher.reset();
if (bh == null) {
return;
}
// TODO validate?
String newKeyboardLocale = app.getLocalization().getLocaleStr();
if (newKeyboardLocale != null
&& keyboardLocale.equals(newKeyboardLocale)) {
return;
}
if (newKeyboardLocale != null) {
this.keyboardLocale = newKeyboardLocale;
} else {
this.keyboardLocale = Language.English_US.localeGWT;
}
clear();
buildGUI(bh, app);
}
@Override
public void setVisible(boolean b) {
switcher.reset();
super.setVisible(b);
}
protected void showHelp(int x, int y) {
}
private void selectTab(int idx) {
switcher.select(idx);
}
public void selectNumbers() {
selectTab(TAB_NUMBERS);
}
public void selectFunctions() {
selectTab(TAB_FX);
}
public void selectAbc() {
selectTab(TAB_ABC);
}
public void selectGreek() {
selectTab(TAB_ALPHA);
}
public void selectSpecial() {
selectTab(TAB_SPECIAL);
}
/**
* check the minimum width. Either width of ABC panel or 123 panel. 70 =
* width of button; 82 = padding
*
* @return
*/
private int getMinWidthWithoutScaling() {
int abc = 10 * 70 + 82;
int numbers = 850;
return Math.max(abc, numbers);
}
public final boolean shouldBeShown() {
return this.keyboardWanted;
}
public final void showOnFocus() {
this.keyboardWanted = true;
}
}