package org.geogebra.web.html5.gui;
import java.util.ArrayList;
import java.util.HashMap;
import org.geogebra.common.GeoGebraConstants;
import org.geogebra.common.main.App;
import org.geogebra.common.util.debug.Log;
import org.geogebra.web.html5.gui.laf.GLookAndFeelI;
import org.geogebra.web.html5.js.ResourcesInjector;
import org.geogebra.web.html5.main.AppW;
import org.geogebra.web.html5.main.HasAppletProperties;
import org.geogebra.web.html5.util.ArticleElement;
import org.geogebra.web.html5.util.Dom;
import org.geogebra.web.html5.util.ViewW;
import org.geogebra.web.html5.util.debug.LoggerW;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.OutlineStyle;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.RootPanel;
/**
* The main frame containing every view / menu bar / .... This Panel (Frame is
* resize able)
*/
public abstract class GeoGebraFrameW extends FlowPanel implements
HasAppletProperties {
private static ArrayList<GeoGebraFrameW> instances = new ArrayList<GeoGebraFrameW>();
/** The application */
public AppW app;
/**
* Splash Dialog to get it work quickly
*/
public SplashDialog splash;
private static SpanElement firstDummy = null;
private static SpanElement lastDummy = null;
public static final int GRAPHICS_VIEW_TABINDEX = 10000;
private static HashMap<String, AppW> articleMap = new HashMap<String, AppW>();
/** Creates new GeoGebraFrame */
public GeoGebraFrameW(GLookAndFeelI laf) {
super();
this.laf = laf;
instances.add(this);
addStyleName("GeoGebraFrame");
DOM.sinkEvents(this.getElement(), Event.ONMOUSEDOWN | Event.ONMOUSEMOVE
| Event.ONMOUSEUP | Event.ONMOUSEOVER);
}
/**
* Add a dummy element to the parent
*
* @param parentElement
* parent
*/
protected static void tackleLastDummy(Element parentElement) {
lastDummy = DOM.createSpan().cast();
lastDummy.addClassName("geogebraweb-dummy-invisible");
lastDummy.setTabIndex(GeoGebraFrameW.GRAPHICS_VIEW_TABINDEX);
parentElement.appendChild(lastDummy);
}
/**
* @return map article id -> article
*/
public static HashMap<String, AppW> getArticleMap() {
return articleMap;
}
public static void reCheckForDummies(Element el) {
if ((firstDummy != null) && (lastDummy != null)) {
return;
}
NodeList<Element> nodes = Dom
.getElementsByClassName(GeoGebraConstants.GGM_CLASS_NAME);
if (nodes.getLength() == 0) {
// it would be better for the article tags to always have
// GeoGebraConstants.GGM_CLASS_NAME, but in case they do not,
// then they are probably child elements of class name
// "applet_container"
nodes = Dom.getElementsByClassName("applet_scaler");
Log.debug(nodes.getLength() + " scalers found");
// so "nodes" is meaning something else here actually
if (nodes.getLength() > 0) {
// no need to get the first node with articleElement
checkForDummiesInScaler(nodes, el);
}
}
}
private static void checkForDummiesInScaler(NodeList<Element> nodes,
Element el) {
// get the last node that really contains an articleElement
for (int i = nodes.getLength() - 1; i >= 0; i--) {
Element ell = nodes.getItem(i);
for (int j = 0; j < ell.getChildCount(); j++) {
Node elChild = ell.getChild(j);
if (elChild != null
&& Element.as(elChild).hasTagName("ARTICLE")) {
// found!!
if (elChild == el && lastDummy == null) {
tackleLastDummy(el);
}
return;
}
}
}
}
/**
* The application loading continues in the splashDialog onLoad handler
*
* @param articleElement
* ArticleElement
*/
public void createSplash(ArticleElement articleElement) {
int splashWidth = 427;
int splashHeight = 120;
// to not touch the DOM twice when computing widht and height
preProcessFitToSceen();
int width = computeWidth();
int height = computeHeight();
/*
* if (ae.getDataParamShowMenuBar()) { // The menubar has extra height:
* height += 31; } if (ae.getDataParamShowToolBar()) { // The toolbar
* has extra height: height += 57; }
*/
boolean showLogo = ((width >= splashWidth) && (height >= splashHeight));
splash = new SplashDialog(showLogo, articleElement.getId(), this);
if (splash.isPreviewExists()) {
splashWidth = width;
splashHeight = height;
}
int borderWidth = articleElement.getBorderThickness();
if (width > 0 && height > 0) {
setWidth((width - borderWidth) + "px"); // 2: border
setComputedWidth(width);
setComputedHeight(height);
setHeight((height - borderWidth) + "px"); // 2: border
// Styleshet not loaded yet, add CSS directly
splash.getElement().getStyle().setPosition(Position.RELATIVE);
splash.getElement().getStyle()
.setTop((height / 2) - (splashHeight / 2), Unit.PX);
if (!articleElement.isRTL()) {
splash.getElement().getStyle()
.setLeft((width / 2) - (splashWidth / 2), Unit.PX);
} else {
splash.getElement().getStyle()
.setRight((width / 2) - (splashWidth / 2), Unit.PX);
}
}
addStyleName("jsloaded");
add(splash);
}
private void preProcessFitToSceen() {
if (ae.getDataParamFitToScreen()) {
Document.get().getDocumentElement().getStyle()
.setHeight(100, Unit.PCT);
RootPanel.getBodyElement().getStyle().setHeight(100, Unit.PCT);
RootPanel.getBodyElement().getStyle().setOverflow(Overflow.HIDDEN);
ae.getStyle().setHeight(100, Unit.PCT);
}
}
private int computeHeight() {
// do we have data-param-height?
int height = ae.getDataParamHeight();
if (height > 0) {
return height;
}
// do we have fit to screen?
if (ae.getDataParamFitToScreen()) {
height = ae.getOffsetHeight();
}
return height;
}
private int computeWidth() {
// do we have data-param-width?
int width = ae.getDataParamWidth();
if (width > 0) {
return width;
}
// do we have fit to screen?
if (ae.getDataParamFitToScreen()) {
width = RootPanel.getBodyElement().getOffsetWidth();
}
return width;
}
/** Article element */
public ArticleElement ae;
private int computedWidth = 0;
private int computedHeight = 0;
private final GLookAndFeelI laf;
/**
* Callback from renderGGBElement to run, if everything is done
*/
private JavaScriptObject onLoadCallback = null;
@Override
public JavaScriptObject getOnLoadCallback() {
return onLoadCallback;
}
/**
* @param width
* width computed from article parameters
*/
public void setComputedWidth(int width) {
this.computedWidth = width;
if (this.app != null) {
this.app.setAppletWidth(width);
}
}
/**
* @param height
* height computed from article parameters
*/
public void setComputedHeight(int height) {
this.computedHeight = height;
if (this.app != null) {
this.app.setAppletHeight(height);
}
}
/**
* Needs running {@link #setComputedWidth(int)} first
*
* @return width computed from applet parameters
*/
public int getComputedWidth() {
return computedWidth;
}
/**
* Needs running {@link #setComputedHeight(int)} first
*
* @return height computed from applet parameters
*/
public int getComputedHeight() {
return computedHeight;
}
private static void setBorder(ArticleElement ae, GeoGebraFrameW gf,
String dpBorder, int px) {
setBorder(ae, gf.getStyleElement(), dpBorder, px);
}
private static void setBorder(ArticleElement ae, Element gfE,
String dpBorder, int px) {
ae.getStyle().setBorderWidth(0, Style.Unit.PX);
ae.getStyle().setBorderStyle(Style.BorderStyle.SOLID);
ae.getStyle().setBorderColor(dpBorder);
gfE.getStyle().setBorderWidth(px, Style.Unit.PX);
gfE.getStyle().setBorderStyle(Style.BorderStyle.SOLID);
gfE.getStyle().setBorderColor(dpBorder);
ae.getStyle().setOutlineStyle(OutlineStyle.NONE);
}
/**
* Sets the border around the canvas to the data-param-bordercolor property
* or leaves it invisible if "none" was set.
*
* @param ae
* article element
* @param gf
* frame
*/
public static void useDataParamBorder(ArticleElement ae, GeoGebraFrameW gf) {
// Log.debug("useDataParamBorder - " + ae.getClassName());
String dpBorder = ae.getDataParamBorder();
int thickness = ae.getBorderThickness() / 2;
if (dpBorder != null) {
if ("none".equals(dpBorder)) {
setBorder(ae, gf, "transparent", thickness);
} else {
setBorder(ae, gf, dpBorder, thickness);
}
}
gf.getElement().removeClassName(
GeoGebraConstants.APPLET_FOCUSED_CLASSNAME);
gf.getElement().addClassName(
GeoGebraConstants.APPLET_UNFOCUSED_CLASSNAME);
ae.getStyle().setOutlineStyle(OutlineStyle.NONE);
}
/**
* @param ae
* article element
* @param gfE
* app frame element
*/
public static void useDataParamBorder(ArticleElement ae, Element gfE) {
// Log.debug("useDataParamBorder - " + ae.getClassName());
String dpBorder = ae.getDataParamBorder();
int thickness = ae.getBorderThickness() / 2;
if (dpBorder != null) {
if ("none".equals(dpBorder)) {
setBorder(ae, gfE, "transparent", thickness);
} else {
setBorder(ae, gfE, dpBorder, thickness);
}
}
gfE.removeClassName(GeoGebraConstants.APPLET_FOCUSED_CLASSNAME);
gfE.addClassName(GeoGebraConstants.APPLET_UNFOCUSED_CLASSNAME);
}
/**
* Sets the border around the canvas to be highlighted. At the moment we use
* "#9999ff" for this purpose.
*
* @param ae
* article element
* @param gf
* frame
*/
public static void useFocusedBorder(ArticleElement ae, GeoGebraFrameW gf) {
// Log.debug("useFocusedBorder - " + ae.getClassName());
String dpBorder = ae.getDataParamBorder();
gf.getElement().removeClassName(
GeoGebraConstants.APPLET_UNFOCUSED_CLASSNAME);
gf.getElement()
.addClassName(GeoGebraConstants.APPLET_FOCUSED_CLASSNAME);
int thickness = ae.getBorderThickness() / 2;
if (dpBorder != null && "none".equals(dpBorder)) {
setBorder(ae, gf, "transparent", thickness);
return;
}
}
/**
* @param ae
* article element
* @param gfE
* app frame element
*/
public static void useFocusedBorder(ArticleElement ae, Element gfE) {
// Log.debug("useFocusedBorder - " + ae.getClassName());
String dpBorder = ae.getDataParamBorder();
gfE.removeClassName(GeoGebraConstants.APPLET_UNFOCUSED_CLASSNAME);
gfE.addClassName(GeoGebraConstants.APPLET_FOCUSED_CLASSNAME);
int thickness = ae.getBorderThickness() / 2;
if (dpBorder != null && "none".equals(dpBorder)) {
setBorder(ae, gfE, "transparent", thickness);
return;
}
}
public void runAsyncAfterSplash() {
final GeoGebraFrameW inst = this;
final ArticleElement articleElement = this.ae;
// GWT.runAsync(new RunAsyncCallback() {
// public void onSuccess() {
ResourcesInjector
.injectResources();
// More testing is needed how can we use
// createApplicationSimple effectively
// if (ae.getDataParamGuiOff())
// inst.app = inst.createApplicationSimple(articleElement, inst);
// else
inst.app = inst.createApplication(articleElement, this.laf);
inst.app.setCustomToolBar();
// useDataParamBorder(articleElement, inst);
// inst.add(inst.app.buildApplicationPanel());
boolean showAppPicker = app.isPerspectivesPopupVisible();
inst.app.buildApplicationPanel();
if (showAppPicker) {
app.showPerspectivesPopup();
}
// need to call setLabels here
// to print DockPanels' titles
inst.app.setLabels();
// }
// public void onFailure(Throwable reason) {
// App.debug("Async load failed");
// }
// });
}
public static void handleLoadFile(ArticleElement articleElement, AppW app) {
ViewW view = new ViewW(articleElement, app);
ViewW.fileLoader.setView(view);
ViewW.fileLoader.onPageLoad();
}
/**
* @return the application
*/
public App getApplication() {
return app;
}
/**
* Sets the Application of the GeoGebraFrame
*
* @param app
* the application
*/
public void setApplication(AppW app) {
this.app = app;
}
/**
* @param article
* article element
* @param lookAndFeel
* look and feel
* @return the newly created instance of Application
*/
protected abstract AppW createApplication(ArticleElement article,
GLookAndFeelI lookAndFeel);
/**
* @return list of instances of GeogebraFrame
*/
public static ArrayList<GeoGebraFrameW> getInstances() {
return instances;
}
@Override
public void onBrowserEvent(Event event) {
// do nothing
}
public static int getInstanceCount() {
return instances.size();
}
/**
* @param width
*
* sets the geogebra-web applet widht
*/
@Override
public void setWidth(int width) {
if (app.getGuiManager() != null) {
app.getGuiManager().resize(width, getOffsetHeight());
setWidth(width - app.getArticleElement().getBorderThickness()
+ "px");
app.persistWidthAndHeight();
} else {
setWidth(width - app.getArticleElement().getBorderThickness()
+ "px");
app.getEuclidianViewpanel().setPixelSize(width, getOffsetHeight());
// maybe onResize is OK too
app.getEuclidianViewpanel().deferredOnResize();
}
}
/**
* @param height
*
* sets the geogebra-web applet height
*/
@Override
public void setHeight(int height) {
if (app.getGuiManager() != null) {
app.getGuiManager().resize(getOffsetWidth(), height);
setHeight(height - app.getArticleElement().getBorderThickness()
+ "px");
app.persistWidthAndHeight();
} else {
setHeight(height - app.getArticleElement().getBorderThickness()
+ "px");
app.getEuclidianViewpanel().setPixelSize(getOffsetWidth(), height);
// maybe onResize is OK too
app.getEuclidianViewpanel().deferredOnResize();
}
}
/**
* sets the geogebra-web applet size (width, height)
*
* @param width
* width in pixels
* @param height
* height in pixels
*
*
*/
@Override
public void setSize(int width, int height) {
// setPixelSize(width, height);
if (app.getGuiManager() != null) {
app.getGuiManager().resize(width, height);
setWidth(width - app.getArticleElement().getBorderThickness()
+ "px");
setHeight(height - app.getArticleElement().getBorderThickness()
+ "px");
app.persistWidthAndHeight();
}
}
/**
* After loading a new GGB file, the size should be set to "auto"
*/
@Override
public void resetAutoSize() {
setWidth("auto");
setHeight("auto");
}
/**
* @param show
*
* wheter show the toolbar in geogebra-web applets or not
*/
@Override
public void showToolBar(boolean show) {
if (app.getGuiManager() != null) {
app.getGuiManager().showToolBar(show);
}
}
/**
* @param show
*
* wheter show the menubar in geogebra-web applets or not
*/
@Override
public void showMenuBar(boolean show) {
if (app.getGuiManager() != null) {
app.getGuiManager().showMenuBar(show);
}
}
/**
* @param show
*
* wheter show the algebrainput in geogebra-web applets or not
*/
@Override
public void showAlgebraInput(boolean show) {
if (app.getGuiManager() != null) {
app.getGuiManager().showAlgebraInput(show);
}
}
/**
* @param show
*
* wheter show the reseticon in geogebra-web applets or not
*/
@Override
public void showResetIcon(boolean show) {
app.setShowResetIcon(show);
app.refreshViews();
}
/**
* @param element
* Html Element
* @param frame
* GeoGebraFrame subclasses
* @param onLoadCallback
* load callback
*
*/
public static void renderArticleElementWithFrame(final Element element,
GeoGebraFrameW frame, JavaScriptObject onLoadCallback) {
final ArticleElement article = ArticleElement.as(element);
if (Log.getLogger() == null) {
LoggerW.startLogger(article);
}
article.clear();
article.initID(0);
final GeoGebraFrameW inst = frame;
inst.ae = article;
inst.onLoadCallback = onLoadCallback;
inst.createSplash(article);
RootPanel root = RootPanel.get(article.getId());
if (root != null) {
RootPanel.get(article.getId()).add(inst);
} else {
Log.error("Cannot find article with ID " + article.getId());
}
}
/**
* callback when renderGGBElement is ready
*/
public static native void renderGGBElementReady() /*-{
if (typeof $wnd.renderGGBElementReady === "function") {
$wnd.renderGGBElementReady();
}
}-*/;
/**
* removes applet from the page
*/
@Override
public void remove() {
this.removeFromParent();
// this does not do anything!
GeoGebraFrameW.getInstances()
.remove(
GeoGebraFrameW.getInstances().indexOf(this));
this.ae.removeFromParent();
this.ae = null;
this.app = null;
ViewW.fileLoader.setView(null);
if (GeoGebraFrameW.getInstanceCount() == 0) {
ResourcesInjector.removeResources();
}
}
}