package org.geogebra.web.web.main;
import javax.swing.SwingConstants;
import org.geogebra.common.GeoGebraConstants;
import org.geogebra.common.euclidian.event.PointerEventType;
import org.geogebra.common.gui.layout.DockPanel;
import org.geogebra.common.gui.toolbar.ToolBar;
import org.geogebra.common.io.layout.DockPanelData;
import org.geogebra.common.io.layout.Perspective;
import org.geogebra.common.io.layout.PerspectiveDecoder;
import org.geogebra.common.kernel.View;
import org.geogebra.common.main.App;
import org.geogebra.common.main.Feature;
import org.geogebra.common.util.debug.GeoGebraProfiler;
import org.geogebra.common.util.debug.Log;
import org.geogebra.keyboard.web.TabbedKeyboard;
import org.geogebra.web.html5.Browser;
import org.geogebra.web.html5.awt.GDimensionW;
import org.geogebra.web.html5.gui.GeoGebraFrameW;
import org.geogebra.web.html5.gui.HasKeyboardPopup;
import org.geogebra.web.html5.gui.util.CancelEventTimer;
import org.geogebra.web.html5.gui.util.ClickStartHandler;
import org.geogebra.web.html5.main.GeoGebraTubeAPIWSimple;
import org.geogebra.web.html5.util.ArticleElement;
import org.geogebra.web.web.gui.GuiManagerW;
import org.geogebra.web.web.gui.app.GGWCommandLine;
import org.geogebra.web.web.gui.app.GGWToolBar;
import org.geogebra.web.web.gui.applet.GeoGebraFrameBoth;
import org.geogebra.web.web.gui.dialog.DialogBoxW;
import org.geogebra.web.web.gui.inputbar.AlgebraInputW;
import org.geogebra.web.web.gui.laf.GLookAndFeel;
import org.geogebra.web.web.gui.layout.DockGlassPaneW;
import org.geogebra.web.web.gui.layout.DockManagerW;
import org.geogebra.web.web.gui.layout.DockPanelW;
import org.geogebra.web.web.gui.layout.DockSplitPaneW;
import org.geogebra.web.web.gui.layout.ZoomSplitLayoutPanel;
import org.geogebra.web.web.gui.layout.panels.EuclidianDockPanelW;
import org.geogebra.web.web.gui.view.algebra.AlgebraViewW;
import org.geogebra.web.web.move.ggtapi.operations.LoginOperationW;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* Full-featured GeoGebra instance that needs part of the screen only.
*/
public class AppWapplet extends AppWFull {
// Event flow operations - are these needed in AppWapplet?
// private LogInOperation loginOperation;
private int spWidth;
private int spHeight;
private boolean menuShowing = false;
private boolean menuInited = false;
private GeoGebraFrameBoth frame;
/******************************************************
* Constructs AppW for applets with undo enabled
*
* @param ae
* article element
* @param gf
* frame
* @param dimension
* 3 for 3d, 2 otherwise
* @param laf
* look and feel
*/
public AppWapplet(ArticleElement ae, GeoGebraFrameBoth gf, int dimension,
GLookAndFeel laf) {
this(ae, gf, dimension, laf, null);
}
/******************************************************
* Constructs AppW for applets
*
* @param ae
* article element
* @param gf
* frame
* @param dimension
* 3 for 3d, 2 otherwise
* @param laf
* look and feel
* @param device
* browser or tablet
*/
public AppWapplet(ArticleElement ae, GeoGebraFrameBoth gf,
int dimension, GLookAndFeel laf,
GDevice device) {
super(ae, dimension, laf, device);
this.frame = gf;
setAppletHeight(frame.getComputedHeight());
setAppletWidth(frame.getComputedWidth());
this.useFullGui = !isApplet() || ae.getDataParamShowAlgebraInput(false)
|| ae.getDataParamShowToolBar(false)
|| ae.getDataParamShowMenuBar(false)
|| ae.getDataParamEnableRightClick();
Log.info("GeoGebra " + GeoGebraConstants.VERSION_STRING + " "
+ GeoGebraConstants.BUILD_DATE + " "
+ Window.Navigator.getUserAgent());
initCommonObjects();
initing = true;
this.euclidianViewPanel = new EuclidianDockPanelW(this, allowStylebar());
// (EuclidianDockPanelW)getGuiManager().getLayout().getDockManager().getPanel(App.VIEW_EUCLIDIAN);
this.canvas = this.euclidianViewPanel.getCanvas();
canvas.setWidth("1px");
canvas.setHeight("1px");
canvas.setCoordinateSpaceHeight(1);
canvas.setCoordinateSpaceWidth(1);
initCoreObjects(this);
afterCoreObjectsInited();
resetFonts();
Browser.removeDefaultContextMenu(this.getArticleElement());
if (ae.getDataParamApp() && !this.getLAF().isSmart()) {
RootPanel.getBodyElement().addClassName("application");
}
if (this.showMenuBar()) {
// opening file -> this was inited before
if (getLoginOperation() == null) {
initSignInEventFlow(new LoginOperationW(this),
ArticleElement.isEnableUsageStats());
}
} else {
if (Browser.runningLocal() && ArticleElement.isEnableUsageStats()) {
new GeoGebraTubeAPIWSimple(has(Feature.TUBE_BETA))
.checkAvailable(null);
}
}
}
@Override
public GeoGebraFrameBoth getAppletFrame() {
return frame;
}
@Override
protected void afterCoreObjectsInited() {
// Code to run before buildApplicationPanel
initGuiManager();
if (this.showConsProtNavigation(App.VIEW_EUCLIDIAN)) {
((EuclidianDockPanelW) euclidianViewPanel).addNavigationBar();
}
// following lines were swapped before but for async file loading it
// does not matter
// and for sync file loading this makes sure perspective setting is not
// blocked by initing flag
initing = false;
GeoGebraFrameW.handleLoadFile(articleElement, this);
}
private void buildSingleApplicationPanel() {
if (frame != null) {
frame.clear();
frame.add((Widget) getEuclidianViewpanel());
// we need to make sure trace works after this, see #4373 or #4236
this.getEuclidianView1().createImage();
((DockPanelW) getEuclidianViewpanel()).setVisible(true);
((DockPanelW) getEuclidianViewpanel())
.setEmbeddedSize(getSettings().getEuclidian(1)
.getPreferredSize().getWidth());
((DockPanelW) getEuclidianViewpanel()).updatePanel(false);
getEuclidianViewpanel()
.setPixelSize(
getSettings().getEuclidian(1).getPreferredSize()
.getWidth(),
getSettings().getEuclidian(1).getPreferredSize()
.getHeight());
// FIXME: temporary hack until it is found what causes
// the 1px difference
// getEuclidianViewpanel().getAbsolutePanel().getElement().getStyle().setLeft(1,
// Style.Unit.PX);
// getEuclidianViewpanel().getAbsolutePanel().getElement().getStyle().setTop(1,
// Style.Unit.PX);
getEuclidianViewpanel().getAbsolutePanel().getElement().getStyle()
.setBottom(-1, Style.Unit.PX);
getEuclidianViewpanel().getAbsolutePanel().getElement().getStyle()
.setRight(-1, Style.Unit.PX);
oldSplitLayoutPanel = null;
}
}
private DockSplitPaneW oldSplitLayoutPanel = null; // just a technical
// helper
// variable
private HorizontalPanel splitPanelWrapper = null;
@Override
public void buildApplicationPanel() {
if (!isUsingFullGui()) {
if (showConsProtNavigation() || !isJustEuclidianVisible()) {
useFullGui = true;
}
}
if (!isUsingFullGui()) {
buildSingleApplicationPanel();
return;
}
for (int i = frame.getWidgetCount() - 1; i >= 0; i--) {
if (!(frame.getWidget(i) instanceof HasKeyboardPopup
|| frame.getWidget(i) instanceof TabbedKeyboard
|| frame.getWidget(i) instanceof DialogBoxW)) {
frame.remove(i);
}
}
// showMenuBar should come from data-param,
// this is just a 'second line of defense'
// otherwise it can be used for taking ggb settings into account too
if (articleElement.getDataParamShowMenuBar(showMenuBar)) {
frame.attachMenubar(this);
}
// showToolBar should come from data-param,
// this is just a 'second line of defense'
// otherwise it can be used for taking ggb settings into account too
if (articleElement.getDataParamShowToolBar(showToolBar)
&& this.getToolbarPosition() != SwingConstants.SOUTH) {
frame.attachToolbar(this);
}
if (this.getInputPosition() == InputPosition.top
&& articleElement
.getDataParamShowAlgebraInput(showAlgebraInput)) {
attachAlgebraInput();
}
attachSplitLayoutPanel();
// showAlgebraInput should come from data-param,
// this is just a 'second line of defense'
// otherwise it can be used for taking ggb settings into account too
if (this.getInputPosition() == InputPosition.bottom
&& articleElement
.getDataParamShowAlgebraInput(showAlgebraInput)) {
attachAlgebraInput();
}
if (articleElement.getDataParamShowToolBar(showToolBar)
&& this.getToolbarPosition() == SwingConstants.SOUTH) {
frame.attachToolbar(this);
}
if (has(Feature.SHOW_ONE_KEYBOARD_BUTTON_IN_FRAME)
// we do not need keyboard in whiteboard
&& !isWhiteboardActive()) {
frame.attachKeyboardButton();
}
frame.attachGlass();
}
private void refreshSplitLayoutPanel() {
if (frame != null && frame.getWidgetCount() != 0
&& frame.getWidgetIndex(getSplitLayoutPanel()) == -1
&& frame.getWidgetIndex(oldSplitLayoutPanel) != -1) {
int wi = frame.getWidgetIndex(oldSplitLayoutPanel);
frame.remove(oldSplitLayoutPanel);
frame.insert(getSplitLayoutPanel(), wi);
oldSplitLayoutPanel = getSplitLayoutPanel();
Browser.removeDefaultContextMenu(
getSplitLayoutPanel().getElement());
}
}
/**
* Attach algebra input
*/
public void attachAlgebraInput() {
// inputbar's width varies,
// so it's probably good to regenerate every time
GGWCommandLine inputbar = new GGWCommandLine();
inputbar.attachApp(this);
frame.add(inputbar);
if (has(Feature.KEYBOARD_MESSED_WITH_OLD_INPUTBAR)) {
updateSplitPanelHeight();
}
this.getGuiManager().getAlgebraInput()
.setInputFieldWidth(this.appletWidth);
}
@Override
protected final void updateTreeUI() {
if (getSplitLayoutPanel() != null) {
((ZoomSplitLayoutPanel) getSplitLayoutPanel()).forceLayout();
}
// updateComponentTreeUI();
}
/**
* @return main panel
*/
public DockSplitPaneW getSplitLayoutPanel() {
if (getGuiManager() == null) {
return null;
}
if (getGuiManager().getLayout() == null) {
return null;
}
return ((GuiManagerW) getGuiManager()).getRootComponent();
}
private void attachSplitLayoutPanel() {
boolean oldSLPanelChanged = oldSplitLayoutPanel == getSplitLayoutPanel() ? false
: true;
oldSplitLayoutPanel = getSplitLayoutPanel();
if (oldSplitLayoutPanel != null) {
if (getArticleElement().getDataParamShowMenuBar(false)) {
this.splitPanelWrapper = new HorizontalPanel();
// TODO
splitPanelWrapper.add(oldSplitLayoutPanel);
if (this.menuShowing) {
splitPanelWrapper.add(frame.getMenuBar(this));
}
frame.add(splitPanelWrapper);
} else {
frame.add(oldSplitLayoutPanel);
}
Browser.removeDefaultContextMenu(
getSplitLayoutPanel().getElement());
if (has(Feature.FIX_KEYBOARD_POSITION) && !oldSLPanelChanged) {
return;
}
ClickStartHandler.init(oldSplitLayoutPanel,
new ClickStartHandler() {
@Override
public void onClickStart(int x, int y,
final PointerEventType type) {
onUnhandledClick();
}
});
}
}
@Override
public void onUnhandledClick() {
updateAVStylebar();
if (!isWhiteboardActive() && !CancelEventTimer.cancelKeyboardHide()) {
Timer timer = new Timer() {
@Override
public void run() {
getAppletFrame().keyBoardNeeded(false, null);
}
};
timer.schedule(0);
}
}
@Override
public void afterLoadFileAppOrNot() {
closePerspectivesPopup();
if (!getLAF().isSmart()) {
removeSplash();
}
String perspective = getArticleElement().getDataParamPerspective();
if (!isUsingFullGui()) {
if (showConsProtNavigation() || !isJustEuclidianVisible()
|| perspective.length() > 0) {
useFullGui = true;
}
}
frame.setApplication(this);
if (!isUsingFullGui()) {
buildSingleApplicationPanel();
} else {
((DockManagerW) getGuiManager().getLayout().getDockManager())
.init(frame);
Perspective p = null;
if (perspective != null) {
p = PerspectiveDecoder.decode(perspective, this.getKernel()
.getParser(), ToolBar.getAllToolsNoMacros(true, false, this));
}
getGuiManager().updateFrameSize();
if (articleElement.getDataParamShowAlgebraInput(false) && !isWhiteboardActive()) {
Perspective p2 = getTmpPerspective(p);
if (!algebraVisible(p2)
&& getInputPosition() == InputPosition.algebraView) {
setInputPosition(InputPosition.bottom, false);
p2.setInputPosition(InputPosition.bottom);
}
}
getGuiManager().getLayout()
.setPerspectives(getTmpPerspectives(), p);
}
getScriptManager().ggbOnInit(); // put this here from Application
// constructor because we have to delay
// scripts until the EuclidianView is
// shown
initUndoInfoSilent();
getEuclidianView1().synCanvasSize();
getAppletFrame().resetAutoSize();
getEuclidianView1().doRepaint2();
stopCollectingRepaints();
frame.splash.canNowHide();
if (!articleElement.preventFocus()) {
requestFocusInWindow();
}
if (isUsingFullGui()) {
if (needsSpreadsheetTableModel()) {
getSpreadsheetTableModel();
}
refreshSplitLayoutPanel();
// probably this method can be changed by more,
// to be more like AppWapplication's method with the same name,
// but preferring to change what is needed only to avoid new unknown
// bugs
if (getGuiManager().hasSpreadsheetView()) {
DockPanel sp = getGuiManager().getLayout().getDockManager()
.getPanel(App.VIEW_SPREADSHEET);
if (sp != null) {
sp.deferredOnResize();
}
}
}
if (isUsingFullGui()) {
updateNavigationBars();
}
this.setPreferredSize(new GDimensionW((int) this.getWidth(), (int) this
.getHeight()));
setDefaultCursor();
GeoGebraFrameW.useDataParamBorder(getArticleElement(), frame);
GeoGebraProfiler.getInstance().profileEnd();
onOpenFile();
showStartTooltip(0);
setAltText();
adjustViews(false);
kernel.notifyScreenChanged();
resetPenTool();
}
private static boolean algebraVisible(Perspective p2) {
if (p2 == null || p2.getDockPanelData() == null) {
return false;
}
for (DockPanelData dp : p2.getDockPanelData()) {
if (dp.getViewId() == App.VIEW_ALGEBRA) {
return dp.isVisible() && !dp.isOpenInFrame();
}
}
return false;
}
private View focusedView;
@Override
public void focusLost(View v, Element el) {
super.focusLost(v, el);
if (v != focusedView) {
return;
}
focusedView = null;
GeoGebraFrameW.useDataParamBorder(getArticleElement(), frame);
// if it is there in focusGained, why not put it here?
this.getGlobalKeyDispatcher().setFocused(false);
}
@Override
public void focusGained(View v, Element el) {
super.focusGained(v, el);
focusedView = v;
GeoGebraFrameW.useFocusedBorder(getArticleElement(), frame);
// we really need to set it to true
switch (v.getViewID()) {
case App.VIEW_ALGEBRA:
case App.VIEW_EUCLIDIAN:
case App.VIEW_EUCLIDIAN2:
this.getGlobalKeyDispatcher().setFocusedIfNotTab();
break;
default:
if (App.isView3D(v.getViewID())
|| ((v.getViewID() >= App.VIEW_EUCLIDIAN_FOR_PLANE_START) && (v
.getViewID() <= App.VIEW_EUCLIDIAN_FOR_PLANE_END))) {
this.getGlobalKeyDispatcher().setFocusedIfNotTab();
}
}
}
@Override
public boolean hasFocus() {
return focusedView != null;
}
@Override
public void setCustomToolBar() {
String customToolbar = articleElement.getDataParamCustomToolBar();
if ((customToolbar != null) && (customToolbar.length() > 0)
&& (articleElement.getDataParamShowToolBar(false))
&& (getGuiManager() != null)) {
getGuiManager().setGeneralToolBarDefinition(customToolbar);
}
}
/**
* Check if just the euclidian view is visible in the document just loaded.
*
* @return
*/
private boolean isJustEuclidianVisible() {
if (tmpPerspectives == null) {
return true;
}
Perspective docPerspective = getTmpPerspective(null);
if (docPerspective == null) {
return true;
}
boolean justEuclidianVisible = false;
for (DockPanelData panel : docPerspective.getDockPanelData()) {
if ((panel.getViewId() == App.VIEW_EUCLIDIAN) && panel.isVisible()) {
justEuclidianVisible = true;
} else if (panel.isVisible()) {
justEuclidianVisible = false;
break;
}
}
return justEuclidianVisible;
}
private Perspective getTmpPerspective(Perspective fallback) {
for (Perspective perspective : tmpPerspectives) {
if (perspective.getId().equals("tmp")) {
return perspective;
}
}
return fallback;
}
@Override
public void updateCenterPanel() {
buildApplicationPanel();
this.oldSplitLayoutPanel.setPixelSize(spWidth, spHeight);
// we need relative position to make sure the menubar / toolbar are not
// hidden
this.oldSplitLayoutPanel.getElement().getStyle()
.setPosition(Position.RELATIVE);
if (!has(Feature.NEW_TOOLBAR) && getGuiManager().hasAlgebraView() && showView(App.VIEW_ALGEBRA)) {
((AlgebraViewW) getAlgebraView())
.setShowAlgebraInput(showAlgebraInput()
&& getInputPosition() == InputPosition.algebraView);
}
}
@Override
public double getWidth() {
if (spWidth > 0) {
return menuShowing ? spWidth + GLookAndFeel.MENUBAR_WIDTH : spWidth;
}
return super.getWidth();
}
@Override
public void updateContentPane() {
super.updateContentPane();
frame.setApplication(this);
frame.refreshKeyboard();
}
@Override
public void persistWidthAndHeight() {
if (this.oldSplitLayoutPanel != null) {
spWidth = this.oldSplitLayoutPanel.getEstimateWidth();
spHeight = this.oldSplitLayoutPanel.getEstimateHeight();
}
}
@Override
public int getWidthForSplitPanel(int fallback) {
if (spWidth > 0) {
return spWidth;
}
return super.getWidthForSplitPanel(fallback);
}
@Override
public int getHeightForSplitPanel(int fallback) {
if (spHeight > 0) {
return spHeight;
}
return super.getHeightForSplitPanel(fallback);
}
@Override
public void toggleMenu() {
if (!this.menuShowing) {
this.menuShowing = true;
boolean needsUpdate = menuInited;
if (!menuInited) {
frame.getMenuBar(this).init(this);
this.menuInited = true;
}
if (isWhiteboardActive()) {
this.splitPanelWrapper.insert(frame.getMenuBar(this), 0);
} else {
this.splitPanelWrapper.add(frame.getMenuBar(this));
}
this.oldSplitLayoutPanel.setPixelSize(
this.oldSplitLayoutPanel.getOffsetWidth()
- GLookAndFeel.MENUBAR_WIDTH,
this.oldSplitLayoutPanel.getOffsetHeight());
updateMenuHeight();
if (needsUpdate) {
frame.getMenuBar(this).getMenubar().updateMenubar();
}
this.getGuiManager().refreshDraggingViews();
oldSplitLayoutPanel.getElement().getStyle()
.setOverflow(Overflow.HIDDEN);
getGuiManager().updateStyleBarPositions(true);
frame.getMenuBar(this).getMenubar().dispatchOpenEvent();
} else {
hideMenu();
}
}
@Override
public void updateMenuHeight() {
if (menuShowing) {
frame.getMenuBar(this).setPixelSize(GLookAndFeel.MENUBAR_WIDTH,
this.oldSplitLayoutPanel.getOffsetHeight());
}
}
@Override
public void hideMenu() {
if (!menuInited || !menuShowing) {
if (this.getGuiManager() != null) {
this.getGuiManager().updateStyleBarPositions(false);
}
return;
}
this.menuShowing = false;
this.oldSplitLayoutPanel.setPixelSize(
this.oldSplitLayoutPanel.getOffsetWidth()
+ GLookAndFeel.MENUBAR_WIDTH,
this.oldSplitLayoutPanel.getOffsetHeight());
this.splitPanelWrapper.remove(frame.getMenuBar(this));
oldSplitLayoutPanel.getElement().getStyle()
.setOverflow(Overflow.VISIBLE);
if (this.getGuiManager() != null
&& this.getGuiManager().getLayout() != null) {
this.getGuiManager().getLayout().getDockManager().resizePanels();
}
if (this.getGuiManager() != null) {
this.getGuiManager().setDraggingViews(false, true);
this.getGuiManager().updateStyleBarPositions(false);
}
}
@Override
public boolean isMenuShowing() {
return this.menuShowing;
}
@Override
public DockGlassPaneW getGlassPane() {
return frame.getGlassPane();
}
@Override
public void addToHeight(int i) {
this.spHeight += i;
}
/**
* Updates height of split panel accordingly if there is algebra input
* and/or toolbar or not. Implemented for the case if there is no keyboard
* open.
*/
@Override
public void updateSplitPanelHeight() {
int newHeight = appletHeight;
if (this.showAlgebraInput()
&& getInputPosition() != InputPosition.algebraView
&& getGuiManager().getAlgebraInput() != null) {
newHeight -= ((AlgebraInputW) getGuiManager().getAlgebraInput())
.getOffsetHeight();
}
if (getToolbar()!=null && getToolbar().isVisible()){
newHeight -= ((GGWToolBar) getToolbar()).getOffsetHeight();
}
if (frame.isKeyboardShowing()) {
newHeight -= frame.getKeyboardHeight();
}
this.spHeight = newHeight;
oldSplitLayoutPanel.setHeight(spHeight + "px");
}
@Override
public Panel getPanel() {
return frame;
}
public boolean hasKeyboardInProbCalculator() {
return has(Feature.ONSCREEN_KEYBOARD_AT_PROBCALC);
}
public double getInnerWidth() {
return getWidth() - getArticleElement().getBorderThickness();
}
}