Java tutorial
// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.client; import com.google.appinventor.client.editor.FileEditor; import com.google.appinventor.client.editor.ProjectEditor; import com.google.appinventor.client.editor.youngandroid.BlocklyPanel; import com.google.appinventor.client.explorer.commands.AddFormCommand; import com.google.appinventor.client.explorer.commands.ChainableCommand; import com.google.appinventor.client.explorer.commands.DeleteFileCommand; import com.google.appinventor.client.output.OdeLog; import com.google.appinventor.client.tracking.Tracking; import com.google.appinventor.client.widgets.DropDownButton.DropDownItem; import com.google.appinventor.client.widgets.Toolbar; import com.google.appinventor.common.version.AppInventorFeatures; import com.google.appinventor.shared.rpc.project.ProjectRootNode; import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidSourceNode; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.gwt.core.client.Scheduler; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import java.util.LinkedList; import java.util.List; import java.util.Map; import static com.google.appinventor.client.Ode.MESSAGES; /** * The design toolbar houses command buttons in the Young Android Design * tab (for the UI designer (a.k.a, Form Editor) and Blocks Editor). * */ public class DesignToolbar extends Toolbar { private boolean isReadOnly; // If the UI is in read only mode /* * A Screen groups together the form editor and blocks editor for an * application screen. Name is the name of the screen (form) displayed * in the screens pull-down. */ public static class Screen { public final String screenName; public final FileEditor formEditor; public final FileEditor blocksEditor; public Screen(String name, FileEditor formEditor, FileEditor blocksEditor) { this.screenName = name; this.formEditor = formEditor; this.blocksEditor = blocksEditor; } } /* * A project as represented in the DesignToolbar. Each project has a name * (as displayed in the DesignToolbar on the left), a set of named screens, * and an indication of which screen is currently being edited. */ public static class DesignProject { public final String name; public final Map<String, Screen> screens; // screen name -> Screen public String currentScreen; // name of currently displayed screen public DesignProject(String name, long projectId) { this.name = name; screens = Maps.newHashMap(); // Screen1 is initial screen by default currentScreen = YoungAndroidSourceNode.SCREEN1_FORM_NAME; // Let BlocklyPanel know which screen to send Yail for BlocklyPanel.setCurrentForm(projectId + "_" + currentScreen); } // Returns true if we added the screen (it didn't previously exist), false otherwise. public boolean addScreen(String name, FileEditor formEditor, FileEditor blocksEditor) { if (!screens.containsKey(name)) { screens.put(name, new Screen(name, formEditor, blocksEditor)); return true; } else { return false; } } public void removeScreen(String name) { screens.remove(name); } public void setCurrentScreen(String name) { currentScreen = name; } } private static final String WIDGET_NAME_ADDFORM = "AddForm"; private static final String WIDGET_NAME_REMOVEFORM = "RemoveForm"; private static final String WIDGET_NAME_SCREENS_DROPDOWN = "ScreensDropdown"; private static final String WIDGET_NAME_SWITCH_TO_BLOCKS_EDITOR = "SwitchToBlocksEditor"; private static final String WIDGET_NAME_SWITCH_TO_FORM_EDITOR = "SwitchToFormEditor"; // Switch language private static final String WIDGET_NAME_SWITCH_LANGUAGE = "Language"; private static final String WIDGET_NAME_SWITCH_LANGUAGE_ENGLISH = "English"; private static final String WIDGET_NAME_SWITCH_LANGUAGE_CHINESE_CN = "Simplified Chinese"; private static final String WIDGET_NAME_SWITCH_LANGUAGE_SPANISH_ES = "Spanish-Spain"; //private static final String WIDGET_NAME_SWITCH_LANGUAGE_GERMAN = "German"; //private static final String WIDGET_NAME_SWITCH_LANGUAGE_VIETNAMESE = "Vietnamese"; // Enum for type of view showing in the design tab public enum View { FORM, // Form editor view BLOCKS // Blocks editor view } public View currentView = View.FORM; public Label projectNameLabel; // Project currently displayed in designer private DesignProject currentProject; // Map of project id to project info for all projects we've ever shown // in the Designer in this session. public Map<Long, DesignProject> projectMap = Maps.newHashMap(); // Stack of screens switched to from the Companion // We implement screen switching in the Companion by having it tell us // to switch screens. We then load into the companion the new Screen // We save where we were because the companion can have us return from // a screen. If we switch projects in the browser UI, we clear this // list of screens as we are effectively running a different application // on the device. public static LinkedList<String> pushedScreens = Lists.newLinkedList(); /** * Initializes and assembles all commands into buttons in the toolbar. */ public DesignToolbar() { super(); isReadOnly = Ode.getInstance().isReadOnly(); projectNameLabel = new Label(); projectNameLabel.setStyleName("ya-ProjectName"); HorizontalPanel toolbar = (HorizontalPanel) getWidget(); toolbar.insert(projectNameLabel, 0); // width of palette minus cellspacing/border of buttons toolbar.setCellWidth(projectNameLabel, "222px"); List<DropDownItem> screenItems = Lists.newArrayList(); addDropDownButton(WIDGET_NAME_SCREENS_DROPDOWN, MESSAGES.screensButton(), screenItems); if (AppInventorFeatures.allowMultiScreenApplications() && !isReadOnly) { addButton(new ToolbarItem(WIDGET_NAME_ADDFORM, MESSAGES.addFormButton(), new AddFormAction())); addButton(new ToolbarItem(WIDGET_NAME_REMOVEFORM, MESSAGES.removeFormButton(), new RemoveFormAction())); } addButton(new ToolbarItem(WIDGET_NAME_SWITCH_TO_FORM_EDITOR, MESSAGES.switchToFormEditorButton(), new SwitchToFormEditorAction()), true); addButton(new ToolbarItem(WIDGET_NAME_SWITCH_TO_BLOCKS_EDITOR, MESSAGES.switchToBlocksEditorButton(), new SwitchToBlocksEditorAction()), true); // Gray out the Designer button and enable the blocks button toggleEditor(false); Ode.getInstance().getTopToolbar().updateFileMenuButtons(0); } private class AddFormAction implements Command { @Override public void execute() { Ode ode = Ode.getInstance(); if (ode.screensLocked()) { return; // Don't permit this if we are locked out (saving files) } ProjectRootNode projectRootNode = ode.getCurrentYoungAndroidProjectRootNode(); if (projectRootNode != null) { ChainableCommand cmd = new AddFormCommand(); cmd.startExecuteChain(Tracking.PROJECT_ACTION_ADDFORM_YA, projectRootNode); } } } private class RemoveFormAction implements Command { @Override public void execute() { Ode ode = Ode.getInstance(); if (ode.screensLocked()) { return; // Don't permit this if we are locked out (saving files) } YoungAndroidSourceNode sourceNode = ode.getCurrentYoungAndroidSourceNode(); if (sourceNode != null && !sourceNode.isScreen1()) { // DeleteFileCommand handles the whole operation, including displaying the confirmation // message dialog, closing the form editor and the blocks editor, // deleting the files in the server's storage, and deleting the // corresponding client-side nodes (which will ultimately trigger the // screen deletion in the DesignToolbar). final String deleteConfirmationMessage = MESSAGES.reallyDeleteForm(sourceNode.getFormName()); ChainableCommand cmd = new DeleteFileCommand() { @Override protected boolean deleteConfirmation() { return Window.confirm(deleteConfirmationMessage); } }; cmd.startExecuteChain(Tracking.PROJECT_ACTION_REMOVEFORM_YA, sourceNode); } } } private class SwitchScreenAction implements Command { private final long projectId; private final String name; // screen name public SwitchScreenAction(long projectId, String screenName) { this.projectId = projectId; this.name = screenName; } @Override public void execute() { doSwitchScreen(projectId, name, currentView); } } private void doSwitchScreen(final long projectId, final String screenName, final View view) { Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { @Override public void execute() { if (Ode.getInstance().screensLocked()) { // Wait until I/O complete Scheduler.get().scheduleDeferred(this); } else { doSwitchScreen1(projectId, screenName, view); } } }); } private void doSwitchScreen1(long projectId, String screenName, View view) { if (!projectMap.containsKey(projectId)) { OdeLog.wlog( "DesignToolbar: no project with id " + projectId + ". Ignoring SwitchScreenAction.execute()."); return; } DesignProject project = projectMap.get(projectId); if (currentProject != project) { // need to switch projects first. this will not switch screens. if (!switchToProject(projectId, project.name)) { return; } } String newScreenName = screenName; if (!currentProject.screens.containsKey(newScreenName)) { // Can't find the requested screen in this project. This shouldn't happen, but if it does // for some reason, try switching to Screen1 instead. OdeLog.wlog("Trying to switch to non-existent screen " + newScreenName + " in project " + currentProject.name + ". Trying Screen1 instead."); if (currentProject.screens.containsKey(YoungAndroidSourceNode.SCREEN1_FORM_NAME)) { newScreenName = YoungAndroidSourceNode.SCREEN1_FORM_NAME; } else { // something went seriously wrong! ErrorReporter .reportError("Something is wrong. Can't find Screen1 for project " + currentProject.name); return; } } currentView = view; Screen screen = currentProject.screens.get(newScreenName); ProjectEditor projectEditor = screen.formEditor.getProjectEditor(); currentProject.setCurrentScreen(newScreenName); setDropDownButtonCaption(WIDGET_NAME_SCREENS_DROPDOWN, newScreenName); OdeLog.log("Setting currentScreen to " + newScreenName); if (currentView == View.FORM) { projectEditor.selectFileEditor(screen.formEditor); toggleEditor(false); Ode.getInstance().getTopToolbar().updateFileMenuButtons(1); } else { // must be View.BLOCKS projectEditor.selectFileEditor(screen.blocksEditor); toggleEditor(true); Ode.getInstance().getTopToolbar().updateFileMenuButtons(1); } // Inform the Blockly Panel which project/screen (aka form) we are working on BlocklyPanel.setCurrentForm(projectId + "_" + newScreenName); } private class SwitchToBlocksEditorAction implements Command { @Override public void execute() { if (currentProject == null) { OdeLog.wlog("DesignToolbar.currentProject is null. " + "Ignoring SwitchToBlocksEditorAction.execute()."); return; } if (currentView != View.BLOCKS) { long projectId = Ode.getInstance().getCurrentYoungAndroidProjectRootNode().getProjectId(); switchToScreen(projectId, currentProject.currentScreen, View.BLOCKS); toggleEditor(true); // Gray out the blocks button and enable the designer button Ode.getInstance().getTopToolbar().updateFileMenuButtons(1); } } } private class SwitchToFormEditorAction implements Command { @Override public void execute() { if (currentProject == null) { OdeLog.wlog( "DesignToolbar.currentProject is null. " + "Ignoring SwitchToFormEditorAction.execute()."); return; } if (currentView != View.FORM) { long projectId = Ode.getInstance().getCurrentYoungAndroidProjectRootNode().getProjectId(); switchToScreen(projectId, currentProject.currentScreen, View.FORM); toggleEditor(false); // Gray out the Designer button and enable the blocks button Ode.getInstance().getTopToolbar().updateFileMenuButtons(1); } } } public void addProject(long projectId, String projectName) { if (!projectMap.containsKey(projectId)) { projectMap.put(projectId, new DesignProject(projectName, projectId)); OdeLog.log("DesignToolbar added project " + projectName + " with id " + projectId); } else { OdeLog.wlog("DesignToolbar ignoring addProject for existing project " + projectName + " with id " + projectId); } } // Switch to an existing project. Note that this does not switch screens. // TODO(sharon): it might be better to throw an exception if the // project doesn't exist. private boolean switchToProject(long projectId, String projectName) { if (projectMap.containsKey(projectId)) { DesignProject project = projectMap.get(projectId); if (project == currentProject) { OdeLog.wlog("DesignToolbar: ignoring call to switchToProject for current project"); return true; } pushedScreens.clear(); // Effectively switching applications clear stack of screens clearDropDownMenu(WIDGET_NAME_SCREENS_DROPDOWN); OdeLog.log("DesignToolbar: switching to existing project " + projectName + " with id " + projectId); currentProject = projectMap.get(projectId); // TODO(sharon): add screens to drop-down menu in the right order for (Screen screen : currentProject.screens.values()) { addDropDownButtonItem(WIDGET_NAME_SCREENS_DROPDOWN, new DropDownItem(screen.screenName, screen.screenName, new SwitchScreenAction(projectId, screen.screenName))); } projectNameLabel.setText(projectName); } else { ErrorReporter.reportError( "Design toolbar doesn't know about project " + projectName + " with id " + projectId); OdeLog.wlog("Design toolbar doesn't know about project " + projectName + " with id " + projectId); return false; } return true; } /* * Add a screen name to the drop-down for the project with id projectId. * name is the form name, formEditor is the file editor for the form UI, * and blocksEditor is the file editor for the form's blocks. */ public void addScreen(long projectId, String name, FileEditor formEditor, FileEditor blocksEditor) { if (!projectMap.containsKey(projectId)) { OdeLog.wlog("DesignToolbar can't find project " + name + " with id " + projectId + ". Ignoring addScreen()."); return; } DesignProject project = projectMap.get(projectId); if (project.addScreen(name, formEditor, blocksEditor)) { if (currentProject == project) { addDropDownButtonItem(WIDGET_NAME_SCREENS_DROPDOWN, new DropDownItem(name, name, new SwitchScreenAction(projectId, name))); } } } /* * PushScreen -- Static method called by Blockly when the Companion requests * That we switch to a new screen. We keep track of the Screen we were on * and push that onto a stack of Screens which we pop when requested by the * Companion. */ public static boolean pushScreen(String screenName) { DesignToolbar designToolbar = Ode.getInstance().getDesignToolbar(); long projectId = Ode.getInstance().getCurrentYoungAndroidProjectId(); String currentScreen = designToolbar.currentProject.currentScreen; if (!designToolbar.currentProject.screens.containsKey(screenName)) // No such screen -- can happen return false; // because screen is user entered here. pushedScreens.addFirst(currentScreen); designToolbar.doSwitchScreen(projectId, screenName, View.BLOCKS); return true; } public static void popScreen() { DesignToolbar designToolbar = Ode.getInstance().getDesignToolbar(); long projectId = Ode.getInstance().getCurrentYoungAndroidProjectId(); String newScreen; if (pushedScreens.isEmpty()) { return; // Nothing to do really } newScreen = pushedScreens.removeFirst(); designToolbar.doSwitchScreen(projectId, newScreen, View.BLOCKS); } // Called from Javascript when Companion is disconnected public static void clearScreens() { pushedScreens.clear(); } /* * Switch to screen name in project projectId. Also switches projects if * necessary. */ public void switchToScreen(long projectId, String screenName, View view) { doSwitchScreen(projectId, screenName, view); } /* * Remove screen name (if it exists) from project projectId */ public void removeScreen(long projectId, String name) { if (!projectMap.containsKey(projectId)) { OdeLog.wlog("DesignToolbar can't find project " + name + " with id " + projectId + " Ignoring removeScreen()."); return; } OdeLog.log("DesignToolbar: got removeScreen for project " + projectId + ", screen " + name); DesignProject project = projectMap.get(projectId); if (!project.screens.containsKey(name)) { // already removed this screen return; } if (currentProject == project) { // if removing current screen, choose a new screen to show if (currentProject.currentScreen.equals(name)) { // TODO(sharon): maybe make a better choice than screen1, but for now // switch to screen1 because we know it is always there switchToScreen(projectId, YoungAndroidSourceNode.SCREEN1_FORM_NAME, View.FORM); } removeDropDownButtonItem(WIDGET_NAME_SCREENS_DROPDOWN, name); } project.removeScreen(name); } private void toggleEditor(boolean blocks) { setButtonEnabled(WIDGET_NAME_SWITCH_TO_BLOCKS_EDITOR, !blocks); setButtonEnabled(WIDGET_NAME_SWITCH_TO_FORM_EDITOR, blocks); if (AppInventorFeatures.allowMultiScreenApplications() && !isReadOnly) { if (getCurrentProject() == null || getCurrentProject().currentScreen == "Screen1") { setButtonEnabled(WIDGET_NAME_REMOVEFORM, false); } else { setButtonEnabled(WIDGET_NAME_REMOVEFORM, true); } } } public DesignProject getCurrentProject() { return currentProject; } }