Java tutorial
// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2017 Massachusetts Institute of Technology, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.client.editor.blocks; import com.google.appinventor.client.ComponentsTranslation; import com.google.appinventor.client.DesignToolbar; import com.google.appinventor.client.ErrorReporter; import com.google.appinventor.client.Ode; import com.google.appinventor.client.TopToolbar; import com.google.appinventor.client.output.OdeLog; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.appinventor.client.settings.user.BlocksSettings; import com.google.appinventor.components.common.YaVersion; import com.google.appinventor.shared.settings.SettingsConstants; import com.google.common.collect.Sets; import com.google.gwt.core.client.Callback; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptException; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.query.client.builders.JsniBundle; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DialogBox; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.VerticalPanel; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import static com.google.appinventor.client.Ode.MESSAGES; /** * Blocks editor panel. * The contents of the blocks editor panel is in an iframe identified by * the formName passed to the constructor. That identifier is also the hashtag * on the URL that is the source of the iframe. This class provides methods for * calling the Javascript Blockly code from the rest of the Designer. * * @author sharon@google.com (Sharon Perl) * @author ewpatton@mit.edu (Evan W. Patton) refactor for Blockly update */ public class BlocklyPanel extends HTMLPanel { public interface BlocklySource extends JsniBundle { @LibrarySource(value = "blockly.js", prepend = "(function(window, document, console){\nthis.goog = goog = top.goog;\n", postpend = "\n}.apply(window, [$wnd, $doc, $wnd.console]));\n" + "for(var ns in window.goog.implicitNamespaces_) {\n" + " if(ns.indexOf('.') !== false) ns = ns.split('.')[0];\n" + " top[ns] = window.goog.global[ns];\n" + "}\nwindow['Blockly'] = top['Blockly'];\nwindow['AI'] = top['AI'];") void initBlockly(); } private static final String EDITOR_HTML = "<div id=\"FORM_NAME\" class=\"svg\" tabindex=\"-1\"></div>"; private static final NativeTranslationMap SIMPLE_COMPONENT_TRANSLATIONS; static { ((BlocklySource) GWT.create(BlocklySource.class)).initBlockly(); exportMethodsToJavascript(); // Tell the blockly world about companion versions. setLanguageVersion(YaVersion.YOUNG_ANDROID_VERSION, YaVersion.BLOCKS_LANGUAGE_VERSION); setPreferredCompanion(YaVersion.PREFERRED_COMPANION, YaVersion.COMPANION_UPDATE_URL); for (int i = 0; i < YaVersion.ACCEPTABLE_COMPANIONS.length; i++) { addAcceptableCompanion(YaVersion.ACCEPTABLE_COMPANIONS[i]); } addAcceptableCompanionPackage(YaVersion.ACCEPTABLE_COMPANION_PACKAGE); SIMPLE_COMPONENT_TRANSLATIONS = NativeTranslationMap.transform(ComponentsTranslation.myMap); } /** * BlocklyWorkspaceChangeListener allows other parts of the App Inventor system to subscribe to * events in the Blockly panel. * * @see com.google.appinventor.client.editor.youngandroid.events.EventHelper * * @author ewpatton * */ public interface BlocklyWorkspaceChangeListener { /** * Event callback when a workspace change occurs. * * @param panel Source BlocklyPanel where the event occurred. * @param event Native object representing the event. */ void onWorkspaceChange(BlocklyPanel panel, JavaScriptObject event); } // The currently displayed entity (project/screen) private static String currentForm; // My entity name private final String formName; /** * Objects registered to listen for workspace changes. */ private final Set<BlocklyWorkspaceChangeListener> listeners = Sets.newHashSet(); /** * Reference to the native Blockly.WorkspaceSvg. */ private JavaScriptObject workspace; /** * If true, the loading of the blocks editor has not completed. */ private boolean loadComplete = false; /** * If true, the loading of the blocks editor resulted in an error. */ private boolean loadError = false; private final BlocksCodeGenerationTarget targetPlatform; public BlocklyPanel(String formName, BlocksCodeGenerationTarget targetPlatform) { this(formName, targetPlatform, false); } public BlocklyPanel(String formName, BlocksCodeGenerationTarget targetPlatform, boolean readOnly) { super(""); getElement().addClassName("svg"); getElement().setId(formName); this.formName = formName; this.targetPlatform = targetPlatform; String projectId = formName.split("_")[0]; /* Blockly initialization now occurs in three stages. This is due to the fact that certain * Blockly objects rely on SVG methods such as getScreenCTM(), which are not properly * initialized and/or null prior to the svg element being attached to the DOM. The first * stage of initialization happens here. * * Stages 2 and 3 can occur in different orders depending on network latency. On a fast * connection, the second stage will be loading of the .bky content into the workspace. * The third stage will then be rendering of the workspace when the user switches to the * Blocks editor. On slow connections, the workspace may render blank until the blocks file * has been downloaded from the server. */ initWorkspace(projectId, readOnly, LocaleInfo.getCurrentLocale().isRTL(), targetPlatform.getTarget()); OdeLog.log("Created BlocklyPanel for " + formName); } /** * Register an object to listen for changes in the Blockly workspace. * * @param listener */ public void addChangeListener(BlocklyWorkspaceChangeListener listener) { listeners.add(listener); } /** * Unregister an object from listening to changes in the Blockly workspace. * * @param listener */ public void removeChangeListener(BlocklyWorkspaceChangeListener listener) { listeners.remove(listener); } /** * Notify listeners that an event has occurred in the Blockly workspace. * * @param event Native JavaScript event object with additional details. */ private void workspaceChanged(JavaScriptObject event) { // ignore workspaceChanged events until after the load finishes if (!loadComplete) { return; } if (loadError) { ErrorReporter.reportError(MESSAGES.blocksNotSaved(formName)); } else { for (BlocklyWorkspaceChangeListener listener : listeners) { listener.onWorkspaceChange(this, event); } } } /** * Remember any component instances for this form in case * the workspace gets reinitialized later (we get detached from * our parent object and then our blocks editor gets loaded * again later). Also, remember the current state of the blocks * area in case we get reloaded. * * This method originally stashed a bunch of iframe related state * that is no longer necessary due to the removal of blocklyframe.html. * To maintain the correct logic with the ReplMgr, it remains for now. */ public void saveComponentsAndBlocks() { // Call doResetYail which will stop the timer that is polling the phone. It is important // that it be stopped to avoid a race condition where the last timer on this form fires // while the new form is loading. doResetYail(); } /** * Load the blocks described by blocksContent into the blocks workspace. * * @param formJson JSON description of Form's structure for upgrading * @param blocksContent XML description of a blocks workspace in format expected by Blockly * @throws LoadBlocksException if Blockly throws an uncaught exception */ // [lyn, 2014/10/27] added formJson for upgrading public void loadBlocksContent(String formJson, String blocksContent) throws LoadBlocksException { try { doLoadBlocksContent(formJson, blocksContent); } catch (JavaScriptException e) { loadError = true; ErrorReporter.reportError(MESSAGES.blocksLoadFailure(formName)); OdeLog.elog("Error loading blocks for screen " + formName + ": " + e.getDescription()); throw new LoadBlocksException(e, formName); } finally { loadComplete = true; } } /** * Get code for current blocks workspace * * @return the code as a String * @throws BlocksCodeGenerationException if there was a problem generating code for the target * platform */ public String getCode(String formJson, String packageName) throws BlocksCodeGenerationException { try { return doGetYail(formJson, packageName); } catch (JavaScriptException e) { throw new BlocksCodeGenerationException(e.getDescription(), formName); } } /** * Send component data (json and form name) to Blockly for building code for the target REPL. * * @throws BlocksCodeGenerationException if there was a problem generating the code for the * target platform */ public void sendComponentData(String formJson, String packageName) throws BlocksCodeGenerationException { if (!currentForm.equals(formName)) { // Not working on the current form... OdeLog.log("Not working on " + currentForm + " (while sending for " + formName + ")"); return; } try { doSendJson(formJson, packageName); } catch (JavaScriptException e) { throw new BlocksCodeGenerationException(e.getDescription(), formName); } } public void startRepl(boolean alreadyRunning, boolean forEmulator, boolean forUsb) { // Start the Repl makeActive(); doStartRepl(alreadyRunning, forEmulator, forUsb); } public void hardReset() { doHardReset(); } public void verifyAllBlocks() { doVerifyAllBlocks(); } public static boolean checkIsAdmin() { return Ode.getInstance().getUser().getIsAdmin(); } // Set currentScreen // We use this to determine if we should send Yail to a // a connected device. public static void setCurrentForm(String formName) { currentForm = formName; } public static void indicateDisconnect() { TopToolbar.indicateDisconnect(); DesignToolbar.clearScreens(); } public static boolean pushScreen(String newScreen) { return DesignToolbar.pushScreen(newScreen); } public static void popScreen() { DesignToolbar.popScreen(); } public void getBlocksImage(Callback<String, String> callback) { doFetchBlocksImage(callback); } // The code below (4 methods worth) is for creating a GWT dialog box // from the blockly code. See the comment in replmgr.js for more // information. /** * Create a Dialog Box. We call this from Javascript (blockly) to * display a dialog box. We do this here because we can get calls * from the blocklyframe when it is not visible. Because we are in * the parent window, we can display dialogs that will be visible * even when the blocklyframe is not visible. * * @param title Title for the Dialog Box * @param mess The message to display * @param buttonName The string to display in the "OK" button. * @param size 0 or 1. 0 makes a smaller box 1 makes a larger box. * @param callback an opague JavaScriptObject that contains the * callback function provided by the Javascript code. * @return The created dialog box. */ public static DialogBox createDialog(String title, String mess, final String buttonName, final String cancelButtonName, int size, final JavaScriptObject callback) { final DialogBox dialogBox = new DialogBox(); dialogBox.setStylePrimaryName("ode-DialogBox"); dialogBox.setText(title); if (size == 0) { dialogBox.setHeight("150px"); } else { dialogBox.setHeight("400px"); } dialogBox.setWidth("400px"); dialogBox.setGlassEnabled(true); dialogBox.setAnimationEnabled(true); dialogBox.center(); VerticalPanel DialogBoxContents = new VerticalPanel(); HTML message = new HTML(mess); HorizontalPanel holder = new HorizontalPanel(); if (buttonName != null) { // If buttonName and cancelButtonName are null Button ok = new Button(buttonName); // We won't have any buttons and other ok.addClickHandler(new ClickHandler() { // code is needed to dismiss us @Override public void onClick(ClickEvent event) { doCallBack(callback, buttonName); } }); holder.add(ok); } if (cancelButtonName != null) { Button cancel = new Button(cancelButtonName); cancel.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { doCallBack(callback, cancelButtonName); } }); holder.add(cancel); } DialogBoxContents.add(message); DialogBoxContents.add(holder); dialogBox.setWidget(DialogBoxContents); terminateDrag(); // cancel a drag before showing the modal dialog dialogBox.show(); return dialogBox; } /** * Hide a dialog box. This function is here so it can be called from * the blockly code. We cannot call "hide" directly from the blockly * code because when this code is compiled, the "hide" method disappears! * * @param dialog The dialogbox to hide. */ public static void HideDialog(DialogBox dialog) { dialog.hide(); } public static void SetDialogContent(DialogBox dialog, String mess) { HTML html = (HTML) ((VerticalPanel) dialog.getWidget()).getWidget(0); html.setHTML(mess); } public static String getQRCode(String inString) { return doQRCode(inString); } /** * Trigger and Update of the Companion if the Companion is connected * and an update is available. Note: We do not compare the currently * running Companion's version against the version we are going to load * we just do it. If YaVersion.COMPANION_UPDATE_URL is "", then no * Update is available. */ public void updateCompanion() { updateCompanion(formName); } public static void updateCompanion(String formName) { doUpdateCompanion(formName); } /** * Access UI translations for generating a deletion warning dialog. * @param message Identifier of message * @return Translated message * @throws IllegalArgumentException if the identifier is not understood */ public static String getOdeMessage(String message) { // TODO(ewpatton): Investigate using a generator to work around // lack of reflection if ("deleteButton".equals(message)) { return Ode.getMessages().deleteButton(); } else if ("cancelButton".equals(message)) { return Ode.getMessages().cancelButton(); } else { throw new IllegalArgumentException("Unexpected argument in getOdeMessage: " + message); } } /** * Update the user's grid setting. * This method is called via JSNI. * @param enable true if the grid should be enabled, otherwise false. */ private static void setGridEnabled(boolean enable) { BlocksSettings settings = (BlocksSettings) Ode.getUserSettings() .getSettings(SettingsConstants.BLOCKS_SETTINGS); settings.changePropertyValue(SettingsConstants.GRID_ENABLED, Boolean.toString(enable)); } /** * Update the user's snap-to-grid setting. * This method is called via JSNI. * @param enable true if snapping should be enabled, otherwise false. */ private static void setSnapEnabled(boolean enable) { BlocksSettings settings = (BlocksSettings) Ode.getUserSettings() .getSettings(SettingsConstants.BLOCKS_SETTINGS); settings.changePropertyValue(SettingsConstants.SNAP_ENABLED, Boolean.toString(enable)); } /** * Get the current state of the user's grid setting. * This method is called via JSNI. * @return true if the setting is present and set to true, otherwise false. */ private static boolean getGridEnabled() { BlocksSettings settings = (BlocksSettings) Ode.getUserSettings() .getSettings(SettingsConstants.BLOCKS_SETTINGS); String snap = settings.getPropertyValue(SettingsConstants.GRID_ENABLED); return Boolean.parseBoolean(snap); } /** * Get the current state of the user's snap setting. * This method is called via JSNI. * @return true if the setting is present and set to true, otherwise false. */ private static boolean getSnapEnabled() { BlocksSettings settings = (BlocksSettings) Ode.getUserSettings() .getSettings(SettingsConstants.BLOCKS_SETTINGS); String snap = settings.getPropertyValue(SettingsConstants.SNAP_ENABLED); return Boolean.parseBoolean(snap); } /** * Trigger a save of the user's settings. This is used to prevent two updates being sent to the * server in the event a Blockly operation sets both grid and snap back-to-back. * This method is called via JSNI. */ private static void saveUserSettings() { Ode.getUserSettings().saveSettings(null); } // ------------ Native methods ------------ /** * Take a Javascript function, embedded in an opaque JavaScriptObject, * and call it. * * @param callback the Javascript callback. */ private static native void doCallBack(JavaScriptObject callback, String buttonName) /*-{ callback.call(null, buttonName); }-*/; private static native void exportMethodsToJavascript() /*-{ $wnd.BlocklyPanel_checkIsAdmin = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::checkIsAdmin()); $wnd.BlocklyPanel_indicateDisconnect = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::indicateDisconnect()); // Note: above lines are longer than 100 chars but I'm not sure whether they can be split $wnd.BlocklyPanel_pushScreen = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::pushScreen(Ljava/lang/String;)); $wnd.BlocklyPanel_popScreen = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::popScreen()); $wnd.BlocklyPanel_createDialog = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::createDialog(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILcom/google/gwt/core/client/JavaScriptObject;)); $wnd.BlocklyPanel_hideDialog = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::HideDialog(Lcom/google/gwt/user/client/ui/DialogBox;)); $wnd.BlocklyPanel_setDialogContent = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::SetDialogContent(Lcom/google/gwt/user/client/ui/DialogBox;Ljava/lang/String;)); $wnd.BlocklyPanel_storeBackpack = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::storeBackpack(Ljava/lang/String;)); $wnd.BlocklyPanel_getOdeMessage = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::getOdeMessage(Ljava/lang/String;)); $wnd.BlocklyPanel_setGridEnabled = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::setGridEnabled(Z)); $wnd.BlocklyPanel_setSnapEnabled = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::setSnapEnabled(Z)); $wnd.BlocklyPanel_getGridEnabled = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::getGridEnabled()); $wnd.BlocklyPanel_getSnapEnabled = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::getSnapEnabled()); $wnd.BlocklyPanel_saveUserSettings = $entry(@com.google.appinventor.client.editor.blocks.BlocklyPanel::saveUserSettings()); }-*/; private native void initWorkspace(String projectId, boolean readOnly, boolean rtl, String targetLang)/*-{ var el = this.@com.google.gwt.user.client.ui.UIObject::getElement()(); var workspace = Blockly.BlocklyEditor.create(el, this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::formName, readOnly, rtl, targetLang); workspace.projectId = projectId; var cb = $entry(this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspaceChanged(Lcom/google/gwt/core/client/JavaScriptObject;)); cb = cb.bind(this); workspace.addChangeListener(function(e) { var block = this.getBlockById(e.blockId); if ( block && e.name == Blockly.ComponentBlock.COMPONENT_SELECTOR ) { block.rename(e.oldValue, e.newValue); } cb(e); // [lyn 12/31/2013] Check for duplicate component event handlers before // running any error handlers to avoid quadratic time behavior. var handler = this.getWarningHandler(); if (handler) { handler.determineDuplicateComponentEventHandlers(); } }.bind(workspace)); this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace = workspace; }-*/; /** * Inject the workspace into the <div> element. */ native void injectWorkspace()/*-{ var el = this.@com.google.gwt.user.client.ui.UIObject::getElement()(); Blockly.ai_inject(el, this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace); }-*/; /** * Make the workspace associated with the BlocklyPanel the main workspace. */ native void makeActive()/*-{ Blockly.mainWorkspace = this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace; // Trigger a screen switch to send new YAIL. var parts = Blockly.mainWorkspace.formName.split(/_/); Blockly.mainWorkspace.fireChangeListener(new AI.Events.ScreenSwitch(parts[0], parts[1])); }-*/; // [lyn, 2014/10/27] added formJson for upgrading public native void doLoadBlocksContent(String formJson, String blocksContent) /*-{ var workspace = this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace; var previousMainWorkspace = Blockly.mainWorkspace; try { Blockly.mainWorkspace = workspace; workspace.loadBlocksFile(formJson, blocksContent).verifyAllBlocks(); } catch(e) { workspace.loadError = true; throw e; } finally { workspace.loadComplete = true; Blockly.mainWorkspace = previousMainWorkspace; } }-*/; /** * Return the XML string describing the current state of the blocks workspace */ public native String getBlocksContent() /*-{ return this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .saveBlocksFile(); }-*/; /** * Add a component to the blocks workspace * * @param uid the unique id of the component instance * @param instanceName the name of the component instance * @param typeName the type of the component instance */ public native void addComponent(String uid, String instanceName, String typeName)/*-{ this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .addComponent(uid, instanceName, typeName); }-*/; /** * Remove the component instance instanceName, with the given typeName * and uid from the workspace. * * @param uid unique id */ public native void removeComponent(String uid)/*-{ this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .removeComponent(uid); }-*/; /** * Rename the component whose old name is oldName (and whose * unique id is uid and type name is typeName) to newName. * * @param uid unique id * @param oldName old instance name * @param newName new instance name */ public native void renameComponent(String uid, String oldName, String newName)/*-{ this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .renameComponent(uid, oldName, newName); }-*/; /** * Show the drawer for component with the specified instance name * * @param name */ public native void showComponentBlocks(String name)/*-{ this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .hideDrawer() .showComponent(name); }-*/; /** * Show the built-in blocks drawer with the specified name * * @param drawerName */ public native void showBuiltinBlocks(String drawerName)/*-{ this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .hideDrawer() .showBuiltin(drawerName); }-*/; /** * Show the generic blocks drawer with the specified name * * @param drawerName */ public native void showGenericBlocks(String drawerName)/*-{ this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .hideDrawer() .showGeneric(drawerName); }-*/; /** * Hide the blocks drawer */ public native void hideDrawer()/*-{ this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .hideDrawer(); }-*/; /** * @returns true if the blocks drawer is showing, false otherwise. */ public native boolean drawerShowing()/*-{ return this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .isDrawerShowing(); }-*/; public native void render()/*-{ this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .resize() .render(); }-*/; public native void hideChaff()/*-{ Blockly.hideChaff(); }-*/; public native void toggleWarning()/*-{ var handler = this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .getWarningHandler(); if (handler) { // handler won't exist if the workspace hasn't rendered yet. handler.toggleWarning(); } }-*/; public native String doGetYail(String formJson, String packageName) /*-{ return this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .getFormYail(formJson, packageName); }-*/; public native void doSendJson(String formJson, String packageName) /*-{ Blockly.ReplMgr.sendFormData(formJson, packageName, this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace); }-*/; public native void doResetYail() /*-{ Blockly.ReplMgr.resetYail(); }-*/; public native void doPollYail() /*-{ try { Blockly.ReplMgr.pollYail(); } catch (e) { $wnd.console.log("doPollYail() Failed"); $wnd.console.log(e); } }-*/; public native void doStartRepl(boolean alreadyRunning, boolean forEmulator, boolean forUsb) /*-{ Blockly.ReplMgr.startRepl(alreadyRunning, forEmulator, forUsb); }-*/; public native void doHardReset() /*-{ Blockly.ReplMgr.ehardreset( this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::formName ); }-*/; public native void doCheckWarnings() /*-{ this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .checkAllBlocksForWarningsAndErrors(); }-*/; static native void setLanguageVersion(int yaVersion, int blocksVersion)/*-{ $wnd.YA_VERSION = yaVersion; $wnd.BLOCKS_VERSION = blocksVersion; }-*/; public static native String getCompVersion() /*-{ return $wnd.PREFERRED_COMPANION; }-*/; static native void setPreferredCompanion(String comp, String url) /*-{ $wnd.PREFERRED_COMPANION = comp; $wnd.COMPANION_UPDATE_URL = url; }-*/; static native void addAcceptableCompanionPackage(String comp) /*-{ $wnd.ACCEPTABLE_COMPANION_PACKAGE = comp; }-*/; static native void addAcceptableCompanion(String comp) /*-{ if ($wnd.ACCEPTABLE_COMPANIONS === null || $wnd.ACCEPTABLE_COMPANIONS === undefined) { $wnd.ACCEPTABLE_COMPANIONS = []; } $wnd.ACCEPTABLE_COMPANIONS.push(comp); }-*/; static native String doQRCode(String inString) /*-{ return Blockly.ReplMgr.makeqrcode(inString); }-*/; public static native void doUpdateCompanion(String formName) /*-{ Blockly.ReplMgr.triggerUpdate(); }-*/; /** * Update Component Types in Blockly ComponentTypes */ public native void populateComponentTypes(String jsonComponentsStr) /*-{ this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .populateComponentTypes(jsonComponentsStr, @com.google.appinventor.client.editor.blocks.BlocklyPanel::SIMPLE_COMPONENT_TRANSLATIONS); }-*/; /** * Update Component Types in Blockly ComponentTypes */ public native void doVerifyAllBlocks() /*-{ this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .verifyAllBlocks(); }-*/; public native void doFetchBlocksImage(Callback<String, String> callback) /*-{ var callb = $entry(function(result, error) { if (error) { callback.@com.google.gwt.core.client.Callback::onFailure(Ljava/lang/Object;)(error); } else { callback.@com.google.gwt.core.client.Callback::onSuccess(Ljava/lang/Object;)(result); } }); this.@com.google.appinventor.client.editor.blocks.BlocklyPanel::workspace .exportBlocksImageToUri(callb); }-*/; /** * Set the initial backpack contents from the server. * * This is an optimization that reduces the number of serializations to and from JSON. The * backpack in earlier versions needed to be marshalled between iframes, but now lives in * the same environment so can remain as JavaScript content. * * @param backpack JSON-serialized backpack contents. */ public static native void setInitialBackpack(String backpack)/*-{ Blockly.Backpack.shared_contents = JSON.parse(backpack); }-*/; /** * Cancel an ongoing drag operation. */ public static native void terminateDrag()/*-{ if (Blockly) Blockly.terminateDrag_(); }-*/; /** * Store the backpack's contents to the App Inventor service. * * @param backpack JSON-serialized backpack contents. */ private static void storeBackpack(String backpack) { Ode.getInstance().getUserInfoService().storeUserBackpack(backpack, new AsyncCallback<Void>() { @Override public void onSuccess(Void nothing) { // Nothing to do... } @Override public void onFailure(Throwable caught) { OdeLog.elog("Failed setting the backpack"); } }); } /** * NativeTranslationMap is a plain JavaScriptObject that provides key-value mappings for * user interface translations in Blockly. This reduces the overhead of crossing GWT's * JSNI barrier by replacing a more expensive function call with a dictionary lookup. * * @author ewpatton * */ private static class NativeTranslationMap extends JavaScriptObject { // GWT requires JSO constructors to be non-visible. protected NativeTranslationMap() { } /** * Instantiate a new NativeTranslationMap. * @return An empty NativeTranslationMap */ private static native NativeTranslationMap make()/*-{ return {}; }-*/; /** * Add a key-value pair to the translation map. * @param key Untranslated term * @param value Translated term for the user's current locale */ private native void put(String key, String value)/*-{ this[key] = value; }-*/; /** * Transforms a Java Collections Map into a NativeTranslationMap. * @param map The source mapping of key-value pairs * @return A new NativeTranslationMap with the same contents as <i>map</i> but as a * JavaScript Object usable in native code. */ public static NativeTranslationMap transform(Map<String, String> map) { NativeTranslationMap result = make(); for (Entry<String, String> entry : map.entrySet()) { result.put(entry.getKey(), entry.getValue()); } return result; } } }