com.programmablefun.ide.App.java Source code

Java tutorial

Introduction

Here is the source code for com.programmablefun.ide.App.java

Source

/*
 * Copyright (c) 2017 Andreas Signer <asigner@gmail.com>
 *
 * This file is part of programmablefun.
 *
 * programmablefun is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * programmablefun is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with programmablefun.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.programmablefun.ide;

import com.programmablefun.compiler.CodeGenerator;
import com.programmablefun.compiler.Error;
import com.programmablefun.compiler.Parser;
import com.programmablefun.compiler.ast.Node;
import com.programmablefun.compiler.ast.Stmt;
import com.programmablefun.ide.console.ConsoleComposite;
import com.programmablefun.ide.editor.StylesheetRepository;
import com.programmablefun.ide.platform.CocoaUiEnhancer;
import com.programmablefun.ide.preferences.ColorSchemePrefPage;
import com.programmablefun.ide.preferences.RepositoryPrefPage;
import com.programmablefun.ide.sync.LocalPersistenceStrategy;
import com.programmablefun.ide.sync.PersistenceStrategy;
import com.programmablefun.ide.sync.SyncService;
import com.programmablefun.ide.turtle.Canvas;
import com.programmablefun.ide.util.AnsiEscapeCodes;
import com.programmablefun.ide.util.OS;
import com.programmablefun.ide.util.SWTResources;
import com.programmablefun.runtime.FuncValue;
import com.programmablefun.runtime.Instruction;
import com.programmablefun.runtime.VirtualMachine;
import com.programmablefun.runtime.nativecode.CanvasWrapper;
import com.programmablefun.runtime.nativecode.Export;
import com.programmablefun.runtime.nativecode.MathWrapper;
import com.programmablefun.runtime.nativecode.TurtleWrapper;
import com.programmablefun.runtime.nativecode.UtilsWrapper;
import com.programmablefun.util.Messages;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.eclipse.jface.action.ControlContribution;
import org.eclipse.jface.action.CoolBarManager;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.preference.PreferenceManager;
import org.eclipse.jface.preference.PreferenceNode;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;

import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;

import static com.programmablefun.util.Messages.Key.Action_About;
import static com.programmablefun.util.Messages.Key.Action_Help;
import static com.programmablefun.util.Messages.Key.Action_Pause;
import static com.programmablefun.util.Messages.Key.Action_Preferences;
import static com.programmablefun.util.Messages.Key.Action_Resume;
import static com.programmablefun.util.Messages.Key.Action_Run;
import static com.programmablefun.util.Messages.Key.Action_Step_Into;
import static com.programmablefun.util.Messages.Key.Action_Step_Over;
import static com.programmablefun.util.Messages.Key.Action_Stop;
import static com.programmablefun.util.Messages.Key.MenuItem_Exit;
import static com.programmablefun.util.Messages.Key.Menu_File;
import static com.programmablefun.util.Messages.Key.Toolbar_FollowCodeExecution;
import static com.programmablefun.util.Messages.Key.VM_Error_While_Compiling;

public class App {

    private static final String APP_NAME = "Programmable Fun";

    protected Shell shell;

    private StylesheetRepository stylesheetRepository;
    private CodeRepository codeRepository;
    private Set<String> wellKnownWords;

    private Canvas canvas;
    private SourceCodeComposite sourceCodeComposite;
    private CallStackComposite callStackComposite;
    private ConsoleComposite consoleComposite;

    private PrintWriter consoleOut;
    private VirtualMachine virtualMachine;

    private BaseAction vmStartAction;
    private BaseAction vmPauseAction;
    private BaseAction vmResumeAction;
    private BaseAction vmStopAction;
    private BaseAction vmStepIntoAction;
    private BaseAction vmStepOverAction;
    private BaseAction helpAction;
    private IAction preferencesAction;
    private IAction aboutAction;

    private CoolBarManager coolBarManager;

    private Settings settings;

    private boolean highlightLines = false;

    /**
     * Launch the application.
     * @param args
     */
    public static void main(String[] args) {
        try {
            App window = new App();
            window.open();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public App() {
        settings = Settings.getInstance();
        stylesheetRepository = new StylesheetRepository();
        stylesheetRepository.loadDefaults();
        PersistenceStrategy persistenceStrategy = new LocalPersistenceStrategy();
        for (SyncService syncService : SyncService.ALL) {
            if (syncService.isConnected()) {
                persistenceStrategy = syncService.getPersistenceStrategy();
                break;
            }
        }
        codeRepository = new CodeRepository(persistenceStrategy);
        codeRepository.load();
    }

    /**
     * Open the window.
     */
    public void open() {
        Display.setAppName(APP_NAME);
        Display display = Display.getDefault();
        createContents();
        shell.open();
        shell.layout();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }

    /**
     * Create contents of the window.
     */
    protected void createContents() {
        Display display = Display.getDefault();
        shell = new Shell(display, SWT.SHELL_TRIM | SWT.CENTER);
        shell.setLayout(new GridLayout(1, false));
        Image icon = new Image(display, App.class.getResourceAsStream("icons/icon.png"));
        shell.setImage(icon);

        createActions();
        createMenu(display);
        createToolbar();

        SashForm sashForm = new SashForm(shell, SWT.VERTICAL);
        sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));

        // Upper part of toplevel sash
        SashForm sashForm2 = new SashForm(sashForm, SWT.HORIZONTAL);
        sashForm2.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));

        SashForm sashForm3 = new SashForm(sashForm2, SWT.VERTICAL);
        sashForm3.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        sourceCodeComposite = new SourceCodeComposite(sashForm3, SWT.NONE, codeRepository);
        sourceCodeComposite.setStylesheet(stylesheetRepository.get(settings.getSelectedStylesheet()));
        callStackComposite = new CallStackComposite(sashForm3, SWT.NONE);
        sashForm3.setWeights(new int[] { 18, 2 });

        canvas = new Canvas(sashForm2, SWT.NONE);
        sashForm2.setWeights(new int[] { 10, 10 });

        // Lower part of toplevel sash
        consoleComposite = new ConsoleComposite(sashForm, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);

        List<Object> nativeCodeWrappers = Lists.newArrayList(new CanvasWrapper(canvas), new TurtleWrapper(canvas),
                new MathWrapper(), new UtilsWrapper(consoleComposite));
        wellKnownWords = Sets.newHashSet();
        for (Object wrapper : nativeCodeWrappers) {
            Export export = wrapper.getClass().getAnnotation(Export.class);
            if (export != null) {
                String name = export.name();
                if (Strings.isNullOrEmpty(name)) {
                    name = wrapper.getClass().getName();
                }
                wellKnownWords.add(name);
            }
            for (Method m : wrapper.getClass().getMethods()) {
                export = m.getAnnotation(Export.class);
                if (export != null) {
                    String name = export.name();
                    if (Strings.isNullOrEmpty(name)) {
                        name = m.getName();
                    }
                    wellKnownWords.add(name);
                }
            }
        }
        sourceCodeComposite.setWellKnownWords(wellKnownWords);

        sashForm.setWeights(new int[] { 3, 1 });

        shell.setText(APP_NAME);
        shell.setMaximized(true);
        shell.layout();
        shell.open();

        consoleOut = new PrintWriter(consoleComposite.getOutputStream(), true);
        virtualMachine = new VirtualMachine(consoleComposite.getOutputStream(), consoleComposite.getInputStream(),
                nativeCodeWrappers);

        callStackComposite.setVirtualMachine(virtualMachine);

        updateVmButtons();

        virtualMachine.addListener(new VirtualMachine.EventListener() {
            @Override
            public void vmStateChanged() {
                showVmStateMessage();
                updateVmButtons();
                updateSourceCodeSelectionButtons();
                switch (virtualMachine.getState()) {
                case STOPPED:
                    highlightLine(-1);
                    break;
                case RUNNING:
                    if (!highlightLines) {
                        highlightLine(-1);
                    }
                }
            }

            @Override
            public void newStatementReached(Node stmt) {
                if (highlightLines) {
                    highlightLine(stmt.getPos().getLine());
                }
            }

            @Override
            public void programSet() {
                updateVmButtons();
                highlightLine(-1);
            }

            @Override
            public void reset() {
                updateVmButtons();
                highlightLine(-1);
            }

            @Override
            public void enteringFunction(FuncValue func) {
            }

            @Override
            public void leavingFunction() {
            }
        });
    }

    private void updateSourceCodeSelectionButtons() {
        boolean enabled = virtualMachine.getState() == VirtualMachine.State.STOPPED;
        shell.getDisplay().asyncExec(() -> sourceCodeComposite.setSourceCodeSelectionEnabled(enabled));
    }

    private void updateVmButtons() {
        shell.getDisplay().asyncExec(() -> {
            VirtualMachine.State state = virtualMachine.getState();
            switch (state) {
            case RUNNING:
                vmStartAction.setEnabled(false);
                vmPauseAction.setEnabled(true);
                vmResumeAction.setEnabled(false);
                vmStopAction.setEnabled(true);
                vmStepIntoAction.setEnabled(false);
                vmStepOverAction.setEnabled(false);
                break;
            case STOPPED:
                vmStartAction.setEnabled(true);
                vmPauseAction.setEnabled(false);
                vmResumeAction.setEnabled(false);
                vmStopAction.setEnabled(false);
                vmStepIntoAction.setEnabled(true);
                vmStepOverAction.setEnabled(true);
                break;
            case PAUSED:
                vmStartAction.setEnabled(false);
                vmPauseAction.setEnabled(false);
                vmResumeAction.setEnabled(true);
                vmStopAction.setEnabled(true);
                vmStepIntoAction.setEnabled(true);
                vmStepOverAction.setEnabled(true);
                break;
            }
        });
    }

    private void showVmStateMessage() {
        VirtualMachine.State state = virtualMachine.getState();
        switch (state) {
        case RUNNING:
            status(Messages.get(Messages.Key.VM_Execution_Started));
            break;
        case STOPPED:
            status(Messages.get(Messages.Key.VM_Execution_Stopped));
            break;
        case PAUSED:
            status(Messages.get(Messages.Key.VM_Execution_Paused));
            break;
        }
    }

    private void status(String s) {
        consoleOut.println(AnsiEscapeCodes.FG_BLUE + s + AnsiEscapeCodes.FG_BLACK);
    }

    private void createActions() {
        vmStartAction = new BaseAction(Messages.get(Action_Run),
                SWTResources.getImage("/com/programmablefun/ide/toolbar/nav_go@2x.png"), this::runCode);
        vmPauseAction = new BaseAction(Messages.get(Action_Pause),
                SWTResources.getImage("/com/programmablefun/ide/toolbar/suspend_co@2x.png"),
                () -> virtualMachine.pause());
        vmResumeAction = new BaseAction(Messages.get(Action_Resume),
                SWTResources.getImage("/com/programmablefun/ide/toolbar/resume_co@2x.png"),
                () -> virtualMachine.start());
        vmStopAction = new BaseAction(Messages.get(Action_Stop),
                SWTResources.getImage("/com/programmablefun/ide/toolbar/stop@2x.png"), () -> virtualMachine.stop());
        vmStepIntoAction = new BaseAction(Messages.get(Action_Step_Into),
                SWTResources.getImage("/com/programmablefun/ide/toolbar/stepinto_co@2x.png"), this::stepInto);
        vmStepOverAction = new BaseAction(Messages.get(Action_Step_Over),
                SWTResources.getImage("/com/programmablefun/ide/toolbar/stepover_co@2x.png"), this::stepOver);
        aboutAction = new BaseAction(Messages.get(Action_About), this::showAbout);
        preferencesAction = new BaseAction(Messages.get(Action_Preferences), this::showPreferences);
        helpAction = new BaseAction(Messages.get(Action_Help),
                SWTResources.getImage("/com/programmablefun/ide/toolbar/help@2x.png"), this::showHelp);
    }

    private void createMenu(Display display) {
        Listener quitListener = event -> {
            display.dispose();
            System.exit(0);
        };

        boolean isMac = System.getProperty("os.name").equals("Mac OS X");
        if (OS.isMac()) {
            CocoaUiEnhancer enhancer = new CocoaUiEnhancer(APP_NAME);
            enhancer.hookApplicationMenu(display, quitListener, aboutAction, preferencesAction);
        }

        Menu menuBar = new Menu(shell, SWT.BAR);
        MenuItem cascadeFileMenu = new MenuItem(menuBar, SWT.CASCADE);
        cascadeFileMenu.setText(Messages.get(Menu_File));

        Menu fileMenu = new Menu(shell, SWT.DROP_DOWN);
        cascadeFileMenu.setMenu(fileMenu);

        if (!isMac) {
            MenuItem exitItem = new MenuItem(fileMenu, SWT.PUSH);
            exitItem.setText(Messages.get(MenuItem_Exit));
            shell.setMenuBar(menuBar);
            exitItem.addListener(SWT.Selection, quitListener);

            MenuItem preferencesItem = new MenuItem(fileMenu, SWT.PUSH);
            preferencesItem.setText(preferencesAction.getText());
            preferencesItem.addListener(SWT.Selection, event -> preferencesAction.run());
        }
    }

    private void createToolbar() {
        coolBarManager = new CoolBarManager(SWT.FLAT);
        final ToolBarManager vmToolBarManager = new ToolBarManager(SWT.FLAT | SWT.NO_FOCUS);
        coolBarManager.add(vmToolBarManager);
        vmToolBarManager.add(vmStartAction);
        vmToolBarManager.add(vmPauseAction);
        vmToolBarManager.add(vmResumeAction);
        vmToolBarManager.add(vmStopAction);
        vmToolBarManager.add(vmStepIntoAction);
        vmToolBarManager.add(vmStepOverAction);

        final ToolBarManager helpToolBarManager = new ToolBarManager(SWT.FLAT | SWT.NO_FOCUS);
        coolBarManager.add(helpToolBarManager);
        helpToolBarManager.add(new ControlContribution("trace-code") {
            @Override
            protected Control createControl(Composite parent) {
                Button checkbox = new Button(parent, SWT.CHECK);
                checkbox.setText(Messages.get(Toolbar_FollowCodeExecution));
                checkbox.setSelection(highlightLines);
                checkbox.addSelectionListener(new SelectionAdapter() {
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        highlightLines = checkbox.getSelection();
                    }
                });
                return checkbox;
            }
        });
        helpToolBarManager.add(helpAction);

        coolBarManager.createControl(shell);
    }

    private void highlightCurrentInstructionLine() {
        Instruction instr = virtualMachine.getCurrentInstruction();
        int line = instr != null ? instr.getSourceNode().getPos().getLine() : -1;
        highlightLine(line);
    }

    private void highlightLine(int line) {
        sourceCodeComposite.setActiveLine(line);
    }

    private boolean loadCode() {
        sourceCodeComposite.clearErrors();
        Parser p = new Parser(sourceCodeComposite.getText());
        Stmt stmt = p.parse();
        if (stmt == null) {
            consoleOut.print(AnsiEscapeCodes.BOLD);
            consoleOut.print(AnsiEscapeCodes.FG_YELLOW);
            consoleOut.print(AnsiEscapeCodes.BG_RED);
            consoleOut.print(Messages.get(VM_Error_While_Compiling));
            consoleOut.println(AnsiEscapeCodes.RESET);
            for (Error e : p.getErrors()) {
                consoleOut.println(e);
            }
            sourceCodeComposite.setErrors(p.getErrors());
            return false;
        }

        CodeGenerator codeGen = new CodeGenerator(stmt);
        List<Instruction> program = codeGen.generate();
        //        int i = 0;
        //        for (Instruction instr : program) {
        //            System.out.println(String.format("%04d: %s", i++, instr));
        //        }
        //        System.out.flush();

        virtualMachine.setProgram(program);
        return true;
    }

    private void runCode() {
        if (loadCode()) {
            virtualMachine.start();
        }
    }

    private void stepInto() {
        if (virtualMachine.getState() != VirtualMachine.State.PAUSED) {
            if (!loadCode()) {
                return;
            }
        }

        int thisLine = virtualMachine.getCurrentInstruction().getSourceNode().getPos().getLine();

        virtualMachine.addListener(new VirtualMachine.EventListener() {
            @Override
            public void vmStateChanged() {
                if (virtualMachine.getState() == VirtualMachine.State.STOPPED) {
                    virtualMachine.removeListener(this);
                }
            }

            @Override
            public void newStatementReached(Node stmt) {
                int line = stmt.getPos().getLine();
                if (line != thisLine) {
                    virtualMachine.removeListener(this);
                    virtualMachine.pause();
                    highlightCurrentInstructionLine();
                }
            }

            @Override
            public void programSet() {
                virtualMachine.removeListener(this);
            }

            @Override
            public void reset() {
                virtualMachine.removeListener(this);
            }

            @Override
            public void enteringFunction(FuncValue func) {
            }

            @Override
            public void leavingFunction() {
            }
        });
        virtualMachine.start();
    }

    private void stepOver() {
        if (virtualMachine.getState() != VirtualMachine.State.PAUSED) {
            if (!loadCode()) {
                return;
            }
        }

        int thisLine = virtualMachine.getCurrentInstruction().getSourceNode().getPos().getLine();
        virtualMachine.addListener(new VirtualMachine.EventListener() {

            int callDepth = 0;

            @Override
            public void vmStateChanged() {
                if (virtualMachine.getState() == VirtualMachine.State.STOPPED) {
                    virtualMachine.removeListener(this);
                }
            }

            @Override
            public void newStatementReached(Node stmt) {
                int line = stmt.getPos().getLine();
                if (line != thisLine && callDepth <= 0) { // We might be in a function and "step over" leaves the function
                    virtualMachine.removeListener(this);
                    virtualMachine.pause();
                    highlightCurrentInstructionLine();
                }
            }

            @Override
            public void programSet() {
                virtualMachine.removeListener(this);
            }

            @Override
            public void reset() {
                virtualMachine.removeListener(this);
            }

            @Override
            public void enteringFunction(FuncValue func) {
                callDepth++;
            }

            @Override
            public void leavingFunction() {
                callDepth--;
            }
        });
        virtualMachine.start();
    }

    private void showAbout() {
        System.err.println("show about dialog");
    }

    private void showHelp() {
        System.err.println("Launch browser with help page");
    }

    private void showPreferences() {
        PreferencePage pages[] = new PreferencePage[] { new RepositoryPrefPage(codeRepository),
                new ColorSchemePrefPage(stylesheetRepository, wellKnownWords), };
        PreferenceManager mgr = new PreferenceManager();
        for (PreferencePage p : pages) {
            mgr.addToRoot(new PreferenceNode(p.getTitle(), p));
        }
        PreferenceDialog dlg = new PreferenceDialog(Display.getCurrent().getActiveShell(), mgr);
        if (dlg.open() == Window.OK) {
            sourceCodeComposite.setStylesheet(stylesheetRepository.get(settings.getSelectedStylesheet()));
            settings.save();
        }
    }
}