package org.geogebra.web.web.main; import java.util.ArrayList; import java.util.List; import org.geogebra.common.GeoGebraConstants; import org.geogebra.common.euclidian.EuclidianConstants; import org.geogebra.common.gui.layout.DockPanel; import org.geogebra.common.gui.menubar.MenuInterface; import org.geogebra.common.gui.view.probcalculator.ProbabilityCalculatorView; import org.geogebra.common.gui.view.spreadsheet.CopyPasteCut; import org.geogebra.common.gui.view.spreadsheet.DataImport; import org.geogebra.common.io.OFFHandler; import org.geogebra.common.io.layout.Perspective; import org.geogebra.common.javax.swing.GOptionPane; import org.geogebra.common.kernel.ModeSetter; import org.geogebra.common.kernel.View; import org.geogebra.common.main.App; import org.geogebra.common.main.DialogManager; import org.geogebra.common.main.Feature; import org.geogebra.common.main.MaterialsManagerI; import org.geogebra.common.move.events.BaseEvent; import org.geogebra.common.move.events.StayLoggedOutEvent; import org.geogebra.common.move.ggtapi.TubeAvailabilityCheckEvent; import org.geogebra.common.move.ggtapi.events.LoginEvent; import org.geogebra.common.move.ggtapi.models.Chapter; import org.geogebra.common.move.ggtapi.models.Material; import org.geogebra.common.move.views.EventRenderable; import org.geogebra.common.util.debug.Log; import org.geogebra.common.util.opencsv.CSVException; import org.geogebra.keyboard.web.HasKeyboard; import org.geogebra.web.html5.gui.GuiManagerInterfaceW; import org.geogebra.web.html5.gui.HasKeyboardPopup; import org.geogebra.web.html5.gui.ToolBarInterface; import org.geogebra.web.html5.gui.laf.GLookAndFeelI; import org.geogebra.web.html5.gui.tooltip.ToolTipManagerW; import org.geogebra.web.html5.gui.tooltip.ToolTipManagerW.ToolTipLinkType; import org.geogebra.web.html5.gui.util.CancelEventTimer; import org.geogebra.web.html5.gui.util.MathKeyboardListener; import org.geogebra.web.html5.javax.swing.GImageIconW; import org.geogebra.web.html5.main.AppW; import org.geogebra.web.html5.main.StringHandler; import org.geogebra.web.html5.util.ArticleElement; import org.geogebra.web.html5.util.ViewW; import org.geogebra.web.web.css.GuiResources; import org.geogebra.web.web.euclidian.EuclidianStyleBarW; import org.geogebra.web.web.gui.CustomizeToolbarGUI; import org.geogebra.web.web.gui.GuiManagerW; import org.geogebra.web.web.gui.HeaderPanelDeck; import org.geogebra.web.web.gui.LanguageGUI; import org.geogebra.web.web.gui.MyHeaderPanel; import org.geogebra.web.web.gui.app.GGWToolBar; import org.geogebra.web.web.gui.dialog.DialogBoxW; import org.geogebra.web.web.gui.dialog.DialogManagerW; import org.geogebra.web.web.gui.exam.ExamDialog; import org.geogebra.web.web.gui.images.AppResources; 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.LayoutW; import org.geogebra.web.web.gui.layout.panels.AlgebraStyleBarW; import org.geogebra.web.web.gui.menubar.PerspectivesPopup; import org.geogebra.web.web.gui.util.PopupBlockAvoider; import org.geogebra.web.web.gui.view.algebra.AlgebraViewW; import org.geogebra.web.web.gui.view.dataCollection.DataCollection; import org.geogebra.web.web.gui.view.spreadsheet.MyTableW; import org.geogebra.web.web.move.ggtapi.models.GeoGebraTubeAPIW; import org.geogebra.web.web.move.ggtapi.models.MaterialCallback; import org.geogebra.web.web.move.ggtapi.operations.LoginOperationW; import org.geogebra.web.web.move.googledrive.operations.GoogleDriveOperationW; import com.google.gwt.dom.client.Element; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window.Location; import com.google.gwt.user.client.ui.HeaderPanel; import com.google.gwt.user.client.ui.MenuBar; import com.google.gwt.user.client.ui.Widget; /** * App with all the GUI * */ public abstract class AppWFull extends AppW implements HasKeyboard { private final static int AUTO_SAVE_PERIOD = 2000; private DataCollection dataCollection; private GuiManagerInterfaceW guiManager = null; private LanguageGUI lg; private CustomizeToolbarGUI ct; /** flag to prevent infinite recursion in focusGained */ boolean focusGainedRunning = false; private ArrayList<Runnable> waitingForLocalization; private boolean localizationLoaded; /** browser / tablet / win store device */ protected GDevice device; /** * * @param ae * article element * @param dimension * 2 or 3 (for 2D or 3D app) * @param laf * look and feel * @param device * browser / tablet / win store device */ protected AppWFull(ArticleElement ae, int dimension, GLookAndFeelI laf, GDevice device) { super(ae, dimension, laf); this.device = device; if (this.isExam()) { afterLocalizationLoaded(new Runnable() { @Override public void run() { examWelcome(); } }); } if (this.getArticleElement().getDataParamShowMenuBar(false)) { maybeStartAutosave(); } } /** * shows the on-screen keyboard (or e.g. a show-keyboard-button) * * @param textField * keyboard listener */ public void showKeyboard(MathKeyboardListener textField) { showKeyboard(textField, false); } /** * @return data collection view */ public DataCollection getDataCollection() { if (this.dataCollection == null) { this.dataCollection = new DataCollection(this); } return this.dataCollection; } @Override public void showKeyboard(MathKeyboardListener textField, boolean forceShow) { getAppletFrame().showKeyBoard(true, textField, forceShow); if (textField != null) { CancelEventTimer.keyboardSetVisible(); } } @Override public void updateKeyboardHeight() { getAppletFrame().updateKeyboardHeight(); } @Override public void updateKeyBoardField(MathKeyboardListener field) { getGuiManager().setOnScreenKeyboardTextField(field); } @Override public final GuiManagerInterfaceW getGuiManager() { return guiManager; } @Override public final void initGuiManager() { // this should not be called from AppWsimple! setWaitCursor(); guiManager = newGuiManager(); getGuiManager().setLayout(new LayoutW(this)); getGuiManager().initialize(); setDefaultCursor(); } /** * @return a GuiManager for GeoGebraWeb */ protected GuiManagerW newGuiManager() { return new GuiManagerW(AppWFull.this, getDevice()); } @Override public void hideKeyboard() { getAppletFrame().showKeyBoard(false, null, false); } @Override public final boolean letShowPropertiesDialog() { return rightClickEnabled || getArticleElement().getDataParamShowMenuBar(false) || getArticleElement().getDataParamApp(); } @Override public void updateKeyboard() { getGuiManager().focusScheduled(false, false, false); invokeLater(new Runnable() { @Override public void run() { DockPanelW dp = ((DockManagerW) getGuiManager().getLayout().getDockManager()).getPanelForKeyboard(); if (dp != null && dp.getKeyboardListener() != null) { // dp.getKeyboardListener().setFocus(true); dp.getKeyboardListener().ensureEditing(); dp.getKeyboardListener().setFocus(true, true); if (isKeyboardNeeded() && (getExam() == null || getExam().getStart() > 0)) { getAppletFrame().showKeyBoard(true, dp.getKeyboardListener(), true); } } if (!isKeyboardNeeded()) { getAppletFrame().showKeyBoard(false, null, true); } } }); } @Override public void notifyLocalizationLoaded() { if(waitingForLocalization == null){ return; } localizationLoaded = true; for (Runnable run : waitingForLocalization) { run.run(); } waitingForLocalization.clear(); } /** * @param run * localization callback */ public final void afterLocalizationLoaded(Runnable run) { if (localizationLoaded) { run.run(); } else { if (waitingForLocalization == null) { waitingForLocalization = new ArrayList<Runnable>(); } waitingForLocalization.add(run); } } @Override public void showStartTooltip(final int perspID) { afterLocalizationLoaded(new Runnable() { @Override public void run() { doShowStartTooltip(perspID); } }); } /** * @param perspID * perspective ID */ void doShowStartTooltip(int perspID) { String[] tutorials = new String[] { "graphing", "graphing", "geometry", "spreadsheet", "cas", "3d", "probability" }; if (articleElement.getDataParamShowStartTooltip(perspID > 0)) { ToolTipManagerW.sharedInstance().setBlockToolTip(false); String tooltipText = getLocalization().getMenu("NewToGeoGebra") + "<br/>" + getLocalization() .getPlain( "CheckOutTutorial", getLocalization().getMenu(Perspective .getPerspectiveName( perspID))); String tooltipURL = GeoGebraConstants.QUICKSTART_URL + tutorials[perspID] + "/" + getLocalization().getLocaleStr() + "/"; DockPanelW focused = ((DockManagerW) getGuiManager().getLayout() .getDockManager()).getPanelForKeyboard(); ToolTipManagerW.sharedInstance().showBottomInfoToolTip( tooltipText, tooltipURL, ToolTipLinkType.Help, this, focused != null && focused.isVisible()); ToolTipManagerW.sharedInstance().setBlockToolTip(true); } } @Override public void checkSaved(Runnable runnable) { ((DialogManagerW) getDialogManager()).getSaveDialog().showIfNeeded( runnable); } @Override public void openCSV(String csv) { String[][] data = DataImport.parseExternalData(this, csv, true); CopyPasteCut cpc = ((MyTableW) getGuiManager().getSpreadsheetView() .getSpreadsheetTable()).getCopyPasteCut(); cpc.pasteExternal( data, 0, 0, data.length > 0 ? data[0].length - 1 : 0, data.length); onOpenFile(); } @Override public void focusGained(View v, Element el) { super.focusGained(v, el); if (getGuiManager() != null) { // somehow the panel was not activated in case focus gain // so it is good to do here, unless it makes an // infinite loop... my code inspection did not find // infinite loop, but it is good to try to exclude that // anyway, e.g. for future changes in the code if (!focusGainedRunning) { focusGainedRunning = true; getGuiManager().setActiveView(v.getViewID()); focusGainedRunning = false; } } } @Override public final void uploadToGeoGebraTube() { final PopupBlockAvoider popupBlockAvoider = new PopupBlockAvoider(); final GeoGebraTubeExportW ggbtube = new GeoGebraTubeExportW(this); getGgbApi().getBase64(true, new StringHandler() { @Override public void handle(String s) { ggbtube.uploadWorksheetSimple(s, popupBlockAvoider); } }); } @Override public void fileNew() { super.fileNew(); resetEVs(); // make sure file->new->probability does not clear the prob. calc if (this.getGuiManager() != null && this.getGuiManager().hasProbabilityCalculator()) { ((ProbabilityCalculatorView) this.getGuiManager() .getProbabilityCalculator()).updateAll(); } // remove all Macros before loading preferences kernel.removeAllMacros(); // reload the saved/(default) preferences Perspective p = null; if (getGuiManager() != null) { p = getGuiManager().getLayout().createPerspective("tmp"); } GeoGebraPreferencesW.getPref().loadForApp(this, p); resetAllToolbars(); resetPenTool(); } private void resetAllToolbars() { GuiManagerW gm = (GuiManagerW) getGuiManager(); DockPanelW[] panels = gm.getLayout().getDockManager().getPanels(); for (DockPanelW panel : panels) { if (panel.canCustomizeToolbar()) { panel.setToolbarString(panel.getDefaultToolbarString()); } } gm.setToolBarDefinition(gm.getDefaultToolbarString()); } /** * Selects Pen tool in whiteboard */ protected void resetPenTool() { if (!isWhiteboardActive()) { return; } setMode(EuclidianConstants.MODE_PEN, ModeSetter.TOOLBAR); getEuclidianController().getPen().DEFAULT_PEN_LINE .setLineThickness(EuclidianConstants.DEFAULT_PEN_SIZE); } @Override public void openOFF(String content){ OFFHandler h = new OFFHandler(getKernel().getConstruction()); h.reset(); String[] lines = content.split("\n"); try { for (String line : lines) { h.addLine(line); } } catch (CSVException e) { // TODO Auto-generated catch block e.printStackTrace(); } h.updateAfterParsing(); afterLoadFileAppOrNot(); } @Override public void showConfirmDialog(String title, String mess) { getOptionPane().showInputDialog(this, "", title, mess, GOptionPane.OK_CANCEL_OPTION, GOptionPane.PLAIN_MESSAGE, null, null, null); } /** * Updates the stylebar in Algebra View */ public void updateAVStylebar() { if (getGuiManager() != null && getGuiManager().hasAlgebraView()) { AlgebraStyleBarW styleBar = ((AlgebraViewW) getView(App.VIEW_ALGEBRA)) .getStyleBar(false); if (styleBar != null) { styleBar.update(null); } } } /** * Popup exam welcome message */ @Override public void examWelcome(){ if (isExam() && getExam().getStart() < 0) { this.closePerspectivesPopup(); resetViewsEnabled(); new ExamDialog(this).show(); if (Location.getHost() != null) { return; } } } @Override public abstract HeaderPanelDeck getAppletFrame(); @Override public ToolBarInterface getToolbar() { return getAppletFrame().getToolbar(); } @Override protected CustomizeToolbarGUI getCustomizeToolbarGUI() { if (this.ct == null) { this.ct = new CustomizeToolbarGUI(this); } int toolbarId = getGuiManager().getActiveToolbarId(); Log.debug("[CT] toolbarId: " + toolbarId); ct.setToolbarId(toolbarId); return this.ct; } @Override public final LanguageGUI getLanguageGUI() { if (this.lg == null) { this.lg = new LanguageGUI(this); } return this.lg; } @Override public final void set1rstMode() { GGWToolBar.set1rstMode(this); } @Override public final void setLabels() { super.setLabels(); if (this.lg != null) { lg.setLabels(); } if (this.ct != null) { ct.setLabels(); } } @Override public final native void copyBase64ToClipboardChromeWebAppCase(String str) /*-{ // solution copied from geogebra.web.gui.view.spreadsheet.CopyPasteCutW.copyToSystemClipboardChromeWebapp // although it's strange that .contentEditable is not set to true var copyFrom = @org.geogebra.web.html5.main.AppW::getHiddenTextArea()(); copyFrom.value = str; copyFrom.select(); $doc.execCommand('copy'); }-*/; @Override public final boolean isSelectionRectangleAllowed() { return getToolbar() != null; } @Override public void toggleShowConstructionProtocolNavigation(int id) { super.toggleShowConstructionProtocolNavigation(id); if (getGuiManager() != null) { getGuiManager().updateMenubar(); } } @Override public final MaterialsManagerI getFileManager() { if (this.fm == null) { this.fm = getDevice().createFileManager(this); } return this.fm; } @Override public DialogManager getDialogManager() { if (dialogManager == null) { dialogManager = new DialogManagerW(this); if (getGoogleDriveOperation() != null) { ((GoogleDriveOperationW) getGoogleDriveOperation()).getView() .add((DialogManagerW) dialogManager); } } return dialogManager; } @Override public final void showBrowser(HeaderPanel bg) { getAppletFrame().showBrowser(bg); } @Override public void openSearch(String query) { showBrowser((MyHeaderPanel) getGuiManager().getBrowseView(query)); } @Override protected final void initGoogleDriveEventFlow() { googleDriveOperation = new GoogleDriveOperationW(this); String state = Location.getParameter("state"); if (getNetworkOperation().isOnline() && state != null && !"".equals(state)) { googleDriveOperation.initGoogleDriveApi(); } } @Override public final Element getFrameElement() { return getAppletFrame().getElement(); } /** material ID waiting for login */ String toOpen = ""; private PerspectivesPopup perspectivesPopup; private int activePerspective; @Override public final void openMaterial(final String id, final Runnable onError) { if (getLoginOperation() != null && ((GeoGebraTubeAPIW) getLoginOperation().getGeoGebraTubeAPI()) .isCheckDone()) { doOpenMaterial(id, onError); } else { if (getLoginOperation() == null) { this.initSignInEventFlow(new LoginOperationW(this), ArticleElement.isEnableUsageStats()); } toOpen = id; getLoginOperation().getView().add(new EventRenderable() { @Override public void renderEvent(BaseEvent event) { Log.debug("received:" + event); if (event instanceof LoginEvent || event instanceof StayLoggedOutEvent || event instanceof TubeAvailabilityCheckEvent) { if (toOpen != null && toOpen.length() > 0) { doOpenMaterial(toOpen, onError); toOpen = ""; } } } }); Log.debug("listening"); } } /** * @param id * material ID * @param onError * error callback */ public void doOpenMaterial(String id, final Runnable onError) { ((GeoGebraTubeAPIW) getLoginOperation().getGeoGebraTubeAPI()).getItem( id, new MaterialCallback() { @Override public void onLoaded(final List<Material> parseResponse, ArrayList<Chapter> meta) { if (parseResponse.size() == 1) { Material material = parseResponse.get(0); material.setSyncStamp(parseResponse.get(0) .getModified()); AppWFull.this.setSyncStamp( parseResponse.get(0).getModified()); getGgbApi().setBase64(material.getBase64()); setActiveMaterial(material); AppWFull.this.updateMaterialURL(material.getId(), material.getSharingKey()); } else { onError.run(); } } @Override public void onError(Throwable error) { onError.run(); } }); } @Override public final boolean isOffline() { return getDevice().isOffline(this); } /** * @return glass pane */ public DockGlassPaneW getGlassPane() { // TODO Auto-generated method stub return null; } private void addMenuItem(MenuBar parentMenu, String key, MenuInterface subMenu) { if (subMenu instanceof MenuBar) { ((MenuBar) subMenu).addStyleName("GeoGebraMenuBar"); } ImageResource imgRes = AppResources.INSTANCE.empty(); if ("Labeling".equals(key)) { imgRes = AppResources.INSTANCE.mode_showhidelabel_16(); } if ("FontSize".equals(key)) { imgRes = GuiResources.INSTANCE.menu_icon_options_font_size(); } parentMenu.addItem( getGuiManager().getMenuBarHtml(imgRes, getLocalization().getMenu(key), true), true, (MenuBar) subMenu); } @Override public void showPerspectivesPopup() { afterLocalizationLoaded(new Runnable() { @Override public void run() { getPerspectivesPopup().showPerspectivesPopup(); } }); } /** * Removed element called ggbsplash */ protected static void removeSplash() { Element el = DOM.getElementById("ggbsplash"); if (el != null) { el.removeFromParent(); } } @Override public void appSplashCanNowHide() { String cmd = Location.getParameter("command"); if (cmd != null) { Log.debug("exectuing commands: " + cmd); String[] cmds = cmd.split(";"); for (int i = 0; i < cmds.length; i++) { getKernel().getAlgebraProcessor() .processAlgebraCommandNoExceptionsOrErrors(cmds[i], false); } } removeSplash(); } @Override public void closePerspectivesPopup() { if (this.perspectivesPopup != null) { getPerspectivesPopup().closePerspectivesPopup(); } } @Override public void setActivePerspective(int index) { activePerspective = index; } @Override public int getActivePerspective() { return activePerspective; } /** * @return perspectives popup */ final PerspectivesPopup getPerspectivesPopup() { if (this.perspectivesPopup == null) { this.perspectivesPopup = new PerspectivesPopup(this); } return perspectivesPopup; } @Override public boolean isPerspectivesPopupVisible() { return perspectivesPopup != null && perspectivesPopup.isShowing(); } @Override public void updateViewSizes() { getEuclidianViewpanel().deferredOnResize(); if (hasEuclidianView2(1)) { ((GuiManagerW) getGuiManager()).getEuclidianView2DockPanel(1) .deferredOnResize(); } if (getGuiManager().hasSpreadsheetView()) { DockPanel sp = getGuiManager().getLayout().getDockManager() .getPanel(App.VIEW_SPREADSHEET); if (sp != null) { sp.deferredOnResize(); } } if (getGuiManager().hasCasView()) { DockPanel sp = getGuiManager().getLayout().getDockManager() .getPanel(App.VIEW_CAS); if (sp != null) { ((DockPanelW) sp).onResize(); } } getAppletFrame().setMenuHeight( getInputPosition() == InputPosition.bottom); } @Override public GImageIconW wrapGetModeIcon(int mode) { return new GImageIconW(GGWToolBar.getImageURL(mode, this)); } @Override public void centerAndResizePopups() { if(!has(Feature.DIALOGS_OVERLAP_KEYBOARD)){ return; } for (int i = 0; i < popups.size(); i++) { Widget w = popups.get(i); if (w instanceof HasKeyboardPopup) { if (w instanceof DialogBoxW) { ((DialogBoxW) w).setOverlapFeature(true); ((DialogBoxW) w) .centerAndResize(this.getAppletFrame().getKeyboardHeight()); } } } } @Override public void closePopups() { super.closePopups(); EuclidianStyleBarW.setCurrentPopup(null); if (this.has(Feature.TOOLBAR_ON_SMALL_SCREENS)) { if (this.getToolbar() != null && this.getToolbar().isMobileToolbar()) { ((GGWToolBar) this.getToolbar()).getToolBar().closeAllSubmenu(); } } } @Override public void setToolbarPosition(int position, boolean update) { Log.debug("TOOLBAR POSITION " + position); toolbarPosition = position; if (update) { updateApplicationLayout(); // updateMenubar(); TODO check if needed } } /** * @return device (tablet / Win store device / browser) */ public final GDevice getDevice() { if (device == null) { return new BrowserDevice(); } return device; } /** * @param ggburl * ggb file URL */ public final static void loadURL_GGB(String ggburl) { ViewW.fileLoader.getView().processFileName(ggburl); } @Override public final void setSaved() { super.setSaved(); if (hasAutosave()) { getFileManager().deleteAutoSavedFile(); getLAF().removeWindowClosingHandler(); } } private boolean hasAutosave() { return articleElement.getDataParamApp(); } @Override public final void setUnsaved() { super.setUnsaved(); if (hasAutosave() && kernel != null && kernel.getConstruction() != null && kernel.getConstruction().isStarted()) { getLAF().addWindowClosingHandler(this); } } private void maybeStartAutosave() { if (hasMacroToRestore() || !this.getLAF().autosaveSupported()) { return; } final String materialJSON = getFileManager().getAutosaveJSON(); if (materialJSON != null && !this.isStartedWithFile() && this.getExam() == null) { afterLocalizationLoaded(new Runnable() { @Override public void run() { ((DialogManagerW) getDialogManager()) .showRecoverAutoSavedDialog(AppWFull.this, materialJSON); } }); } else { this.startAutoSave(); } } /** * if there are unsaved changes, the file is saved to the localStorage. */ public void startAutoSave() { Timer timer = new Timer() { private int counter = 0; @Override public void run() { counter++; if (!isSaved()) { getFileManager().autoSave(counter); } getFileManager().refreshAutosaveTimestamp(); } }; timer.scheduleRepeating(AUTO_SAVE_PERIOD); } @Override public void loadPreferences(Perspective p) { GeoGebraPreferencesW.getPref().loadForApp(this, p); } /** * Update the menu height */ public abstract void updateMenuHeight(); @Override public boolean isWhiteboardActive() { return activePerspective == 6 || "7".equals(getArticleElement().getDataParamPerspective()); } }