package org.geogebra.common.plugin; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.main.App; import org.geogebra.common.plugin.script.JsScript; import org.geogebra.common.util.debug.Log; public abstract class ScriptManager implements EventListener { protected App app; protected boolean listenersEnabled = true; // maps between GeoElement and JavaScript function names protected HashMap<GeoElement, JsScript> updateListenerMap; protected HashMap<GeoElement, JsScript> clickListenerMap; protected ArrayList<JsScript> addListeners = new ArrayList<JsScript>(); protected ArrayList<JsScript> storeUndoListeners = new ArrayList<JsScript>(); protected ArrayList<JsScript> removeListeners = new ArrayList<JsScript>(); protected ArrayList<JsScript> renameListeners = new ArrayList<JsScript>(); protected ArrayList<JsScript> updateListeners = new ArrayList<JsScript>(); protected ArrayList<JsScript> clickListeners = new ArrayList<JsScript>(); protected ArrayList<JsScript> clearListeners = new ArrayList<JsScript>(); protected ArrayList<JsScript> clientListeners = new ArrayList<JsScript>(); private ArrayList[] listenerLists() { return new ArrayList[] { addListeners, storeUndoListeners, removeListeners, renameListeners, updateListeners, clickListeners, clearListeners, clientListeners }; } public ScriptManager(App app) { this.app = app; app.getEventDispatcher().addEventListener(this); } @Override public void sendEvent(Event evt) { // TODO get rid of javaToJavaScriptView if (!listenersEnabled) { return; } switch (evt.type) { case CLICK: callListeners(clickListeners, evt); if (clickListenerMap != null) { callListener(clickListenerMap.get(evt.target), evt); } break; case UPDATE: callListeners(updateListeners, evt); if (updateListenerMap != null) { callListener(updateListenerMap.get(evt.target), evt); } break; case ADD: callListeners(addListeners, evt); break; case STOREUNDO: callListeners(storeUndoListeners, evt); break; case REMOVE: callListeners(removeListeners, evt); break; case RENAME: callListeners(renameListeners, evt); break; case CLEAR: callListeners(clearListeners, evt); break; case RELATION_TOOL: case RENAME_COMPLETE: case ADD_POLYGON: case ADD_POLYGON_COMPLETE: case MOVING_GEOS: case MOVED_GEOS: case PASTE_ELMS: case PASTE_ELMS_COMPLETE: case DELETE_GEOS: case LOGIN: case SET_MODE: case UPDATE_STYLE: case SHOW_NAVIGATION_BAR: case SHOW_STYLE_BAR: case PERSPECTIVE_CHANGE: case SELECT: case DESELECT: case UNDO: case REDO: case OPEN_MENU: case OPEN_DIALOG: case EXPORT: case ADD_MACRO: case REMOVE_MACRO: callClientListeners(clientListeners, evt); break; // TODO case CLEAR default: Log.debug("Unknown event type"); } } private void callListeners(List<JsScript> listeners, Event evt) { if (listeners.isEmpty()) { return; } for (JsScript listener : listeners) { callListener(listener, evt); } } private void callClientListeners(List<JsScript> listeners, Event evt) { if (listeners.isEmpty()) { return; } ArrayList<String> args = new ArrayList<String>(); args.add(evt.type.getName()); if (evt.targets != null) { for (GeoElement geo : evt.targets) { args.add(geo.getLabelSimple()); } } else if (evt.target != null) { args.add(evt.target.getLabelSimple()); } else { args.add(""); } if (evt.argument != null) { args.add(evt.argument); } for (JsScript listener : listeners) { callJavaScript(listener.getText(), args.toArray()); } } private void callListener(JsScript listener, Event evt) { if (listener != null) { String fn = listener.getText(); GeoElement geo = evt.target; if (geo == null) { callJavaScript(fn, null, null); return; } String label = geo.getLabel(StringTemplate.defaultTemplate); if (evt.type == EventType.RENAME) { callJavaScript(fn, geo.getOldLabel(), label); return; } else if (evt.argument == null) { callJavaScript(fn, label, null); return; } callJavaScript(fn, evt.argument, null); } } public void disableListeners() { listenersEnabled = false; } public void enableListeners() { listenersEnabled = true; } /* * needed for eg File -> New */ @Override public void reset() { if (updateListenerMap != null) { updateListenerMap = null; } if (clickListenerMap != null) { clickListenerMap = null; } // If undo clicked, mustn't clear the global listeners if (!listenersEnabled) { return; } if (addListeners != null) { addListeners.clear(); } for (ArrayList a : listenerLists()) { if (a != null && a != storeUndoListeners && a.size() > 0) { a.clear(); } } } /** * Registers a JavaScript function as an add listener for the applet's * construction. Whenever a new object is created in the GeoGebraApplet's * construction, the JavaScript function JSFunctionName is called using the * name of the newly created object as a single argument. */ public synchronized void registerAddListener(String JSFunctionName) { registerGlobalListener(addListeners, JSFunctionName); } /** * Registers a JavaScript function as an add listener for the applet's * construction. Whenever a new object is created in the GeoGebraApplet's * construction, the JavaScript function JSFunctionName is called using the * name of the newly created object as a single argument. */ public synchronized void registerStoreUndoListener(String JSFunctionName) { if (!app.isUndoActive()) { app.getKernel().setUndoActive(true); app.getKernel().initUndoInfo(); } registerGlobalListener(storeUndoListeners, JSFunctionName); } private void registerGlobalListener(ArrayList<JsScript> listenerList, String jSFunctionName) { if (jSFunctionName == null || jSFunctionName.length() == 0) { return; } initJavaScript(); // init list if (listenerList != null) { listenerList.add(JsScript.fromName(app, jSFunctionName)); } } /** * Removes a previously registered add listener * * @see #registerAddListener(String) */ public synchronized void unregisterAddListener(String JSFunctionName) { if (addListeners != null) { addListeners.remove(JsScript.fromName(app, JSFunctionName)); Log.debug("unregisterAddListener: " + JSFunctionName); } } /** * Registers a JavaScript function as a remove listener for the applet's * construction. Whenever an object is deleted in the GeoGebraApplet's * construction, the JavaScript function JSFunctionName is called using the * name of the deleted object as a single argument. */ public synchronized void registerRemoveListener(String JSFunctionName) { registerGlobalListener(removeListeners, JSFunctionName); } /** * Removes a previously registered remove listener * * @see #registerRemoveListener(String) */ public synchronized void unregisterRemoveListener(String JSFunctionName) { if (removeListeners != null) { removeListeners.remove(JsScript.fromName(app, JSFunctionName)); Log.debug("unregisterRemoveListener: " + JSFunctionName); } } /** * Registers a JavaScript function as a clear listener for the applet's * construction. Whenever the construction in the GeoGebraApplet's is * cleared (i.e. all objects are removed), the JavaScript function * JSFunctionName is called using no arguments. */ public synchronized void registerClearListener(String JSFunctionName) { registerGlobalListener(clearListeners, JSFunctionName); } /** * Removes a previously registered clear listener * * @see #registerClearListener(String) */ public synchronized void unregisterClearListener(String JSFunctionName) { if (clearListeners != null) { clearListeners.remove(JsScript.fromName(app, JSFunctionName)); Log.debug("unregisterClearListener: " + JSFunctionName); } } /** * Registers a JavaScript function as a rename listener for the applet's * construction. Whenever an object is renamed in the GeoGebraApplet's * construction, the JavaScript function JSFunctionName is called using the * name of the deleted object as a single argument. */ public synchronized void registerRenameListener(String JSFunctionName) { registerGlobalListener(renameListeners, JSFunctionName); } /** * Removes a previously registered rename listener. * * @see #registerRenameListener(String) */ public synchronized void unregisterRenameListener(String JSFunctionName) { if (renameListeners != null) { renameListeners.remove(JsScript.fromName(app, JSFunctionName)); Log.debug("unregisterRenameListener: " + JSFunctionName); } } /** * Registers a JavaScript function as an update listener for the applet's * construction. Whenever any object is updated in the GeoGebraApplet's * construction, the JavaScript function JSFunctionName is called using the * name of the updated object as a single argument. */ public synchronized void registerUpdateListener(String JSFunctionName) { registerGlobalListener(updateListeners, JSFunctionName); } /** * Removes a previously registered update listener. * * @see #registerRemoveListener(String) */ public synchronized void unregisterUpdateListener(String JSFunctionName) { if (updateListeners != null) { updateListeners.remove(JsScript.fromName(app, JSFunctionName)); } } /** * Registers a JavaScript function as a click listener for the applet's * construction. Whenever any object is click in the GeoGebraApplet's * construction, the JavaScript function JSFunctionName is called using the * name of the clicked object as a single argument. */ public synchronized void registerClickListener(String JSFunctionName) { registerGlobalListener(clickListeners, JSFunctionName); } /** * Removes a previously registered click listener. * * @see #registerRemoveListener(String) */ public synchronized void unregisterClickListener(String JSFunctionName) { if (clickListeners != null) { clickListeners.remove(JsScript.fromName(app, JSFunctionName)); } } /** * Registers a JS function to be notified of client events. */ public synchronized void registerClientListener(String JSFunctionName) { registerGlobalListener(clientListeners, JSFunctionName); } public synchronized void unregisterClientListener(String JSFunctionName) { if (clientListeners != null) { clientListeners.remove(JsScript.fromName(app, JSFunctionName)); } } /** * Registers a JavaScript listener for an object. Whenever the object with * the given name changes, a JavaScript function named JSFunctionName is * called using the name of the changed object as the single argument. If * objName previously had a mapping JavaScript function, the old value is * replaced. */ private synchronized HashMap<GeoElement, JsScript> registerObjectListener( HashMap<GeoElement, JsScript> map0, String objName, String JSFunctionName) { if (JSFunctionName == null || JSFunctionName.length() == 0) { return map0; } GeoElement geo = app.getKernel().lookupLabel(objName); if (geo == null) { return map0; } initJavaScript(); HashMap<GeoElement, JsScript> map = map0; if (map == null) { map = new HashMap<GeoElement, JsScript>(); } Log.debug(JSFunctionName); map.put(geo, JsScript.fromName(app, JSFunctionName)); return map; } /** * Removes a previously set object listener for the given object. */ private synchronized void unregisterObjectListener( HashMap<GeoElement, JsScript> map, String objName) { if (map != null) { GeoElement geo = app.getKernel().lookupLabel(objName); if (geo != null) { map.remove(geo); } } } /** * Register a JavaScript function that will run when an object is updated * * @param objName * the name of the target object * @param fName * the name of the JavaScript function */ public void registerObjectUpdateListener(String objName, String fName) { updateListenerMap = registerObjectListener(updateListenerMap, objName, fName); } /** * Unregister any JavaScript function that runs when an object is updated * * @param objName * the name of the target object */ public void unregisterObjectUpdateListener(String objName) { unregisterObjectListener(updateListenerMap, objName); } /** * Register a JavaScript function that will run when an object is clicked * * @param objName * the name of the target object * @param fName * the name of the JavaScript function */ public void registerObjectClickListener(String objName, String fName) { clickListenerMap = registerObjectListener(clickListenerMap, objName, fName); } /** * Unregister any JavaScript function that runs when an object is clicked * * @param objName * the name of the target object */ public void unregisterObjectClickListener(String objName) { unregisterObjectListener(clickListenerMap, objName); } public abstract void ggbOnInit(); public synchronized void initJavaScript() { // overridden in platforms } abstract public void callJavaScript(String jsFunction, Object[] args); public void callJavaScript(String jsFunction, Object arg0, Object arg1) { if (arg0 == null) { callJavaScript(jsFunction, new Object[0]); return; } if (arg1 == null) { callJavaScript(jsFunction, new Object[] { arg0 }); return; } callJavaScript(jsFunction, new Object[] { arg0, arg1 }); } // ------ getters for listeners ------------- public ArrayList<JsScript> getAddListeners() { if (addListeners == null) { addListeners = new ArrayList<JsScript>(); } return addListeners; } public ArrayList<JsScript> getStoreUndoListeners() { if (storeUndoListeners == null) { storeUndoListeners = new ArrayList<JsScript>(); } return storeUndoListeners; } public ArrayList<JsScript> getRemoveListeners() { if (removeListeners == null) { removeListeners = new ArrayList<JsScript>(); } return removeListeners; } public ArrayList<JsScript> getRenameListeners() { if (renameListeners == null) { renameListeners = new ArrayList<JsScript>(); } return renameListeners; } public ArrayList<JsScript> getupdateListeners() { if (updateListeners == null) { updateListeners = new ArrayList<JsScript>(); } return updateListeners; } public ArrayList<JsScript> getClearListeners() { if (clearListeners == null) { clearListeners = new ArrayList<JsScript>(); } return clearListeners; } public HashMap<GeoElement, JsScript> getUpdateListenerMap() { if (updateListenerMap == null) { updateListenerMap = new HashMap<GeoElement, JsScript>(); } return updateListenerMap; } public HashMap<GeoElement, JsScript> getClickListenerMap() { if (clickListenerMap == null) { clickListenerMap = new HashMap<GeoElement, JsScript>(); } return clickListenerMap; } public void setGlobalScript() { // to be overridden } public boolean hasListeners() { if (updateListenerMap != null && updateListenerMap.size() > 0) { return true; } if (clickListenerMap != null && clickListenerMap.size() > 0) { return true; } for (ArrayList a : listenerLists()) { if (a != null && a.size() > 0) { return true; } } return false; } }