com.google.appinventor.client.DesignToolbar.java Source code

Java tutorial

Introduction

Here is the source code for com.google.appinventor.client.DesignToolbar.java

Source

// -*- 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;
    }
}