papertoolkit.PaperToolkit.java Source code

Java tutorial

Introduction

Here is the source code for papertoolkit.PaperToolkit.java

Source

package papertoolkit;

import java.awt.AWTException;
import java.awt.Image;
import java.awt.Menu;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.InvalidPropertiesFormatException;
import java.util.List;
import java.util.Properties;

import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import papertoolkit.actions.remote.ActionReceiverTrayApp;
import papertoolkit.application.Application;
import papertoolkit.application.config.Configuration;
import papertoolkit.application.config.StartupOptions;
import papertoolkit.events.EventDispatcher;
import papertoolkit.events.PenEvent;
import papertoolkit.paper.Region;
import papertoolkit.paper.Sheet;
import papertoolkit.pattern.coordinates.PatternToSheetMapping;
import papertoolkit.pattern.coordinates.RegionID;
import papertoolkit.pattern.coordinates.conversion.TiledPatternCoordinateConverter;
import papertoolkit.pen.InputDevice;
import papertoolkit.pen.Pen;
import papertoolkit.pen.PenSample;
import papertoolkit.pen.handwriting.HandwritingRecognitionService;
import papertoolkit.pen.replay.SaveAndReplay;
import papertoolkit.pen.streaming.PenServerTrayApp;
import papertoolkit.pen.synch.BatchedDataDispatcher;
import papertoolkit.tools.ToolExplorer;
import papertoolkit.tools.design.acrobat.PaperUIDesigner;
import papertoolkit.tools.design.acrobat.RegionConfiguration;
import papertoolkit.tools.monitor.ToolkitMonitoringService;
import papertoolkit.units.Centimeters;
import papertoolkit.units.Inches;
import papertoolkit.units.Pixels;
import papertoolkit.units.Points;
import papertoolkit.util.DebugUtils;
import papertoolkit.util.WindowUtils;
import papertoolkit.util.graphics.ImageCache;
import papertoolkit.util.graphics.ImageUtils;

import com.jgoodies.looks.plastic.PlasticLookAndFeel;
import com.jgoodies.looks.plastic.PlasticXPLookAndFeel;
import com.jgoodies.looks.plastic.theme.DarkStar;
import com.thoughtworks.xstream.XStream;

/**
 * <p>
 * Every PaperToolit has one EventEngine that handles input from users, and schedules output for the system. A
 * PaperToolkit can run one or more Applications at the same time. You can also deactivate applications (to
 * pause them). Or, you can remove them altogether. (These features are not yet fully implemented.)
 * </p>
 * <p>
 * <span class="BSDLicense"> This software is distributed under the <a
 * href="http://hci.stanford.edu/research/copyright.txt">BSD License</a>. </span>
 * </p>
 * <p>
 * TODOS:
 * </p>
 * 
 * @author <a href="http://graphics.stanford.edu/~ronyeh">Ron B Yeh</a> (ronyeh(AT)cs.stanford.edu)
 */
public class PaperToolkit {

    /**
     * Ensure the toolkit initialization happens once and only once.
     */
    private static boolean alreadyInitialized = false;

    /**
     * 
     */
    public static final String CONFIG_FILE_KEY = "papertoolkit.startupinformation";

    /**
     * 
     */
    public static final String CONFIG_FILE_VALUE = "data/config/PaperToolkit.xml";

    /**
     * 
     */
    public static final String CONFIG_PATTERN_PATH_KEY = "tiledpatterngenerator.patternpath";

    /**
     * Where our pattern files are located...
     */
    public static final String CONFIG_PATTERN_PATH_VALUE = "data/pattern/";

    /**
     * We should move this out to a config file, I know... :(
     */
    private static final String DEFAULT_TOOLKIT_RESOURCE_DIR = "C:/Documents and Settings/Ron Yeh/My Documents/Projects/PaperToolkit/bin";

    /**
     * Property Keys.
     */
    private static final String HW_REC_KEY = "handwritingRecognition";

    /**
     * 
     */
    private static boolean isfirstAppPopulatingSystemTray = true;

    /**
     * Whether we have called initializeLookAndFeel() yet...
     */
    private static boolean lookAndFeelInitialized = false;

    /**
     * Where to find the directories that store our pattern definition files.
     */
    public static final File PATTERN_PATH = getPatternPath();

    /**
     * A key for the configuration file.
     */
    private static final String REMOTE_PENS_KEY = "remotePens";

    private static String toolkitIconTitle;

    /**
     * The instance that is created when you use the convenience function.
     */
    private static PaperToolkit toolkitInstance;

    /**
     * Where PaperToolkit is installed.
     */
    private static File toolkitRootPath;

    /**
     * In the Windows System Tray...
     */
    private static TrayIcon trayIcon;

    /**
     * The System Tray right click menu.
     */
    private static PopupMenu trayMenu;

    /**
     * The version of PaperToolkit.
     * 
     * 0.7 added gesture recognition<br>
     * 0.8 should add SideCar testing tools<br>
     * 0.9 should do major cleaning and bug fixing....<br>
     * 1.0 should re-include the printing API.<br>
     */
    private static String versionString = "0.7";

    /**
     * Serializes/Unserializes toolkit objects to/from XML strings.
     */
    private static XStream xmlEngine;

    /**
     * Print an Intro Message.
     */
    static {
        init();
    }

    /**
     * @return a new application with a file name that is based on the calling class... That is, if you use
     *         this convenience function, we will assume that the class you called it from is the main app.
     */
    public static Application createApplication() {
        return new Application(
                DebugUtils.getClassNameFromStackTraceElement(Thread.currentThread().getStackTrace()[2]));
    }

    /**
     * For debugging running applications. =)
     */
    private static void createSystemTrayIcon() {
        if (!SystemTray.isSupported()) {
            return;
        }
        if (trayIcon == null) {
            // this is the icon that sits in our tray...
            toolkitIconTitle = Configuration.getPropertyValue(Configuration.Keys.TOOLKIT_ICON_TITLE.toString());
            trayIcon = new TrayIcon(
                    ImageCache.loadBufferedImage(PaperToolkit.getDataFile(
                            Configuration.getPropertyValue(Configuration.Keys.TOOLKIT_ICON_PATH.toString()))),
                    toolkitIconTitle, getTrayMenu());
            trayIcon.setImageAutoSize(true);
            trayIcon.addMouseListener(new MouseAdapter() {
                public void mouseClicked(MouseEvent e) {
                    if (!SwingUtilities.isRightMouseButton(e)) {
                        exitPaperToolkit();
                    }
                }
            });
            try {
                SystemTray.getSystemTray().add(trayIcon);
            } catch (AWTException e) {
                e.printStackTrace();
            }
            // Don't add a Shutdown Hook, as that is buggy, and can stop shutdown
        }
    }

    /**
     * Remove any tray icons, and exit!
     * 
     * @return
     */
    private static void exitPaperToolkit() {
        System.out.println("Exiting PaperToolkit...");
        if (trayIcon != null) {
            TrayIcon iconToRemove = trayIcon;
            trayIcon = null;
            SystemTray.getSystemTray().remove(iconToRemove);
        }
        System.exit(0);
    }

    /**
     * Loads an object from an XML File.
     * 
     * @param xmlFile
     * @return
     */
    public static Object fromXML(File xmlFile) {
        Object o = null;
        try {
            o = getXMLEngine().fromXML(new FileInputStream(xmlFile));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return o;
    }

    public static Object fromXML(String xmlString) {
        return getXMLEngine().fromXML(xmlString);
    }

    /**
     * @param relativePath
     * @return a file under the /data/ directory...
     */
    public static File getDataFile(String relativePath) {
        return new File(new File(getToolkitRootPath(), "/data/"), relativePath);
    }

    /**
     * @return
     */
    public static synchronized PaperToolkit getInstance() {
        if (toolkitInstance == null) {
            toolkitInstance = new PaperToolkit();
        }
        return toolkitInstance;
    }

    /**
     * @return
     */
    public static Image getPaperToolkitIcon() {
        return ImageUtils.readImage(getDataFile("icons/paper.png"));
    }

    /**
     * @return the location of pattern data, from the configuration files.
     */
    public static File getPatternPath() {
        return Configuration.getConfigFile(CONFIG_PATTERN_PATH_KEY);
    }

    /**
     * @return the place where new XML files show up, when we synch our pen.
     */
    public static File getPenSynchDataPath() {
        return new File(getToolkitRootPath(), "penSynch/data/XML/");
    }

    /**
     * Can only point to files that are in the "classpath"...
     * 
     * @param resourcePath
     * @return
     * @deprecated until we figure out what we want to do with the JAR situation. No plans to deploy
     *             PaperToolkit as a single JAR.
     */
    private static File getResourceFile(String resourcePath) {
        try {
            // we need a way to anchor the PaperToolkit Path...
            // maybe through a config file? but where might we access this config file...
            // in the user's home directory?
            // instead, if it's a resource we can't understand, we just default to the toolkitRootPath field
            URI resourceURI = PaperToolkit.class.getResource(resourcePath).toURI();
            if (resourceURI.getScheme().equals("bundleresource")) {
                return new File(DEFAULT_TOOLKIT_RESOURCE_DIR, resourcePath);
            } else {
                return new File(resourceURI);
            }
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param relativePath
     * @return a file or directory relative to PaperToolkit/
     */
    public static File getToolkitFile(String relativePath) {
        return new File(getToolkitRootPath(), relativePath);
    }

    /**
     * There are no plans for PaperToolkit to be deployed as a single JAR file. Thus, this assumes you have it
     * installed on the file system.
     * 
     * @return the root path to the toolkit (e.g., C:\Documents and Settings\User Name\Projects\PaperToolkit\)
     */
    public static File getToolkitRootPath() {

        if (toolkitRootPath == null) {
            // get the runtime directory for the papertoolkit package (e.g.,
            // PaperToolkit/bin/papertoolkit) the parent will be the root
            // directory!
            // 
            // darn... this doesn't work if it is accessed by eclipse
            File resourceFile = getResourceFile("/papertoolkitroot");
            toolkitRootPath = resourceFile.getParentFile().getParentFile();
        }
        return toolkitRootPath;
    }

    /**
     * @return a menu for the System Tray Icon.
     */
    private static PopupMenu getTrayMenu() {
        if (trayMenu == null) {
            trayMenu = new PopupMenu("Paper Toolkit Options");

            // for exiting the application
            final MenuItem exitItem = new MenuItem("Exit " + toolkitIconTitle);
            exitItem.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent ae) {
                    exitPaperToolkit();
                }
            });
            trayMenu.add(exitItem);

            // for event save and replay
            final Menu replayItem = new Menu("Event Replay");
            final MenuItem replayNow = new MenuItem("Play");
            replayNow.addActionListener(SaveAndReplay.getInstance().getActionListenerForReplay());
            final MenuItem latestSession = new MenuItem("Load Most Recent Session");
            latestSession.addActionListener(SaveAndReplay.getInstance().getActionListenerForLoadLatest());
            final MenuItem chooseSession = new MenuItem("Load a Different Session...");
            chooseSession.addActionListener(SaveAndReplay.getInstance().getActionListenerForChooseSession());
            final Menu playBookmarked = new Menu("Bookmarked Sessions");
            SaveAndReplay.getInstance().populateBookmarks(playBookmarked);
            replayItem.add(chooseSession);
            replayItem.add(latestSession);
            replayItem.add(playBookmarked);
            replayItem.add(replayNow);
            trayMenu.add(replayItem);
        }

        return trayMenu;
    }

    /**
     * @return the XStream processor that parses and creates XML.
     */
    private static synchronized XStream getXMLEngine() {
        if (xmlEngine == null) {
            xmlEngine = new XStream();

            // Add Aliases Here (for more concise XML)
            xmlEngine.alias("Sheet", Sheet.class);
            xmlEngine.alias("Inches", Inches.class);
            xmlEngine.alias("Centimeters", Centimeters.class);
            xmlEngine.alias("Pixels", Pixels.class);
            xmlEngine.alias("Points", Points.class);
            xmlEngine.alias("RegionConfiguration", RegionConfiguration.class);
            xmlEngine.alias("Region", Region.class);
            xmlEngine.alias("Rectangle2DDouble", Rectangle2D.Double.class);
            xmlEngine.alias("TiledPatternCoordinateConverter", TiledPatternCoordinateConverter.class);
            xmlEngine.alias("RegionID", RegionID.class);
            xmlEngine.alias("PenEvent", PenEvent.class);
            xmlEngine.alias("PenSample", PenSample.class);
        }
        return xmlEngine;
    }

    /**
     * Called only once, on toolkit startup. (Also is called manually by the Pen object, if you use it without
     * the toolkit.)
     */
    public static void init() {
        if (alreadyInitialized) {
            return;
        }
        alreadyInitialized = true;

        printInitializationMessages();
        createSystemTrayIcon(); // load the system tray icon...
    }

    /**
     * Sets up parameters for any Java Swing UI we need. Feel free to call this from an external class. If you
     * use the default PaperToolkit() constructor, it will also use the custom look and feel. All PaperToolkit
     * utility classes will also use this look and feel.
     */
    public static void initializeLookAndFeel() {
        if (!lookAndFeelInitialized) {
            // JGoodies Look and Feel
            try {
                final DarkStar theme = new DarkStar();
                PlasticLookAndFeel.setPlasticTheme(theme);
                UIManager.setLookAndFeel(new PlasticXPLookAndFeel());
            } catch (Exception e) {
            }
            lookAndFeelInitialized = true;
        }
    }

    /**
     * 
     */
    public static void initializeNativeLookAndFeel() {
        if (!lookAndFeelInitialized) {
            WindowUtils.setNativeLookAndFeel();
        }
        lookAndFeelInitialized = true;
    }

    /**
     * @return
     */
    public static SaveAndReplay initializeSaveAndReplay() {
        // the static initializer will have run by this time, so getInstance will be valid!
        return SaveAndReplay.getInstance();
    }

    /**
     * Alternatively, try using the *.bat files instead.
     * 
     * @param args
     */
    public static void main(String[] args) {
        if (args.length == 0) {
            // the 0 args branch will run the Paper Toolkit GUI, which helps
            // designers learn what you can do
            // with this toolkit. It integrates with the documentation and stuff too!
            printUsage();
            getInstance().startToolExplorer();
        } else if (args[0].startsWith("-actions")) {
            ActionReceiverTrayApp.main(new String[] {});
        } else if (args[0].startsWith("-pen")) {
            PenServerTrayApp.main(new String[] {});
        }
    }

    /**
     * @param popupMenu
     */
    private static void populateTrayMenuForSideCar(Menu popupMenu) {
        final MenuItem openSideCarGUIItem = new MenuItem("Open SideCar GUI");
        openSideCarGUIItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                monitoringService.openSideCarGUI();
            }
        });
        popupMenu.add(openSideCarGUIItem);

    }

    /**
     * A Welcome message.
     */
    private static void printInitializationMessages() {
        System.out.println("---------------------------------------------");
        System.out.println(" PaperToolkit version " + versionString);
        System.out.println(" Copyright (c) 2006-2007 Stanford University ");
        System.out.println(" Ron B. Yeh [ronyeh@cs.stanford.edu] ");
        System.out.println("---------------------------------------------");
    }

    /**
     * 
     */
    private static void printUsage() {
        System.out.println("Without any arguments, we run the PaperToolkit Explorer. "
                + "You can also run Papertoolkit with one argument: ");
        System.out.println("   -actions   // runs the action receiver");
        System.out.println("   -pen      // runs the pen server");
        System.out.println("Thank you for using PaperToolkit! Feel free to send feedback (good & bad) to "
                + "ronyeh@cs.stanford.edu.");
    }

    /**
     * 
     */
    public static void startAcrobatDesigner() {
        PaperUIDesigner.start();
    }

    /**
     * @param obj
     * @return a string representing the object translated into XML
     */
    public static String toXML(Object obj) {
        return getXMLEngine().toXML(obj);
    }

    /**
     * @param object
     * @param outputFile
     */
    public static void toXML(Object object, File outputFile) {
        try {
            FileOutputStream fos = new FileOutputStream(outputFile);
            toXML(object, fos);
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * @param object
     * @param stream
     *            write the xml to disk or another output stream.
     */
    public static void toXML(Object object, OutputStream stream) {
        getXMLEngine().toXML(object, stream);
        try {
            stream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * @param o
     * @return an XML string representation of the object, without line breaks.
     */
    public static String toXMLNoLineBreaks(Object o) {
        return toXML(o).replace("\n", "");
    }

    /**
     * Processes batched ink.
     */
    @SuppressWarnings("unused")
    private BatchedDataDispatcher batchedDataDispatcher;

    /**
     * The engine that processes all pen events, producing the correct outputs and calling the right event
     * handlers.
     */
    private EventDispatcher eventDispatcher;

    /**
     * The list of running or stopped applications.
     */
    private List<Application> loadedApplications = new ArrayList<Application>();

    /**
     * Feel free to edit the PaperToolkit.xml in your local directory, to add configuration properties for
     * your own program. Then, you can get the local properties from this properties object.
     */
    private final Properties localProperties = new Properties();

    /**
     * For allowing external apps (e.g., SideCar) to monitor the toolkit's actions...
     */
    @SuppressWarnings("unused")
    private static ToolkitMonitoringService monitoringService;

    /**
     * Whether or not to use handwriting recognition. It will start the HWRec Server...
     */
    private boolean useHandwriting;

    /**
     * Start up a paper toolkit. A toolkit can load multiple applications, and dispatch events accordingly
     * (and between applications, ideally). There will be one event engine in the paper toolkit, and all
     * events that applications generate will be fed through this single event engine.
     */
    public PaperToolkit() {
        this(new StartupOptions()); // default options...
    }

    /**
     * Use a custom parameter block, that lets you customize the look and feel, handwriting recognition,
     * etc...
     */
    public PaperToolkit(StartupOptions startupOptions) {
        loadStartupConfiguration();

        if (startupOptions.getParamApplyGUILookAndFeel()) {
            initializeLookAndFeel();
        }

        eventDispatcher = new EventDispatcher();
        batchedDataDispatcher = new BatchedDataDispatcher(eventDispatcher);

        // the handwriting server starts up only if the sheet has a handwriting
        // recognizer... (or something
        // like that)
        // Start the local server up whenever the paper toolkit is initialized.
        // the either flag can override the other. They will both need to be
        // TRUE to actually load it.
        if (useHandwriting && startupOptions.getParamTurnOnHandwritingRecognitionServer()) {
            HandwritingRecognitionService.getInstance();
        }
    }

    /**
     * EXPERTS ONLY: Interact with the EventEngine at runtime!
     * 
     * @return
     */
    public EventDispatcher getEventDispatcher() {
        return eventDispatcher;
    }

    public List<Application> getLoadedApps() {
        return loadedApplications;
    }

    /**
     * @param propertyKey
     * @return
     */
    public String getProperty(String propertyKey) {
        return localProperties.getProperty(propertyKey);
    }

    /**
     * For every application, ask it to load themost recent mappings...
     */
    public void loadMostRecentPatternMappings() {
        DebugUtils.println("Loading most recent Pattern Mappings...");
        for (Application a : loadedApplications) {
            a.loadMostRecentPatternMappings();
        }
    }

    /**
     * Load the configuration information on startup...
     */
    private void loadStartupConfiguration() {
        final Properties props = Configuration.getPropertiesFromConfigFile(CONFIG_FILE_KEY);
        setStartupProperties(props);

        // also check for a custom PaperToolkit.xml in the run directory of the
        // application properties in that file will override the ones we just
        // loaded from
        // the default location
        // alternatively, you can just edit the default PaperToolkit.xml,
        // located in data/config/PaperToolkit.xml
        File localPropsFile = new File("PaperToolkit.xml");
        if (localPropsFile.exists()) {
            DebugUtils.println("Local Properties File Exists. Loading Properties from " + localPropsFile);
            try {
                localProperties.loadFromXML(new FileInputStream(localPropsFile));
                setStartupProperties(localProperties);
            } catch (InvalidPropertiesFormatException e) {
                e.printStackTrace();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            DebugUtils.println("Local Properties File Does Not Exist");
        }
    }

    /**
     * TODO: Figure out the easiest way to send a PS/PDF (with or without regions) to the default printer.
     * 
     * @param sheet
     */
    public void print(Sheet sheet) {
        // Implement this...
        DebugUtils.println("Unimplemented Method");
    }

    /**
     * @param props
     */
    private void setStartupProperties(Properties props) {
        if (props.containsKey(HW_REC_KEY)) {
            String theProp = props.getProperty(HW_REC_KEY);
            // DebugUtils.println(HW_REC_KEY + " was: [" + useHandwriting + "]
            // and is now [" + theProp + "]");
            useHandwriting = Boolean.parseBoolean(theProp);
        }

        // add the Pens that we use most frequently...
        Pen.addToQuickList("localhost");
        if (props.containsKey(REMOTE_PENS_KEY)) {
            String theProp = props.getProperty(REMOTE_PENS_KEY);
            // DebugUtils.println("Loading Frequently Used Remote Pens from
            // PaperToolkit.xml: " + theProp);
            String[] pens = theProp.split(","); // comma delimited
            for (String pen : pens) {
                Pen.addToQuickList(pen);
            }
        }

    }

    /**
     * Start this application and register all live pens with the event engine. The event engine will then
     * start dispatching events for this application until the application is stopped.
     * 
     * @param paperApp
     */
    public void startApplication(Application paperApp) {
        if (!loadedApplications.contains(paperApp)) {
            if (isfirstAppPopulatingSystemTray) {
                populateTrayMenuForSideCar(trayMenu); // add the sidecar menu if there is an application!
                trayMenu.add(new MenuItem("-")); // separator
                isfirstAppPopulatingSystemTray = false;
            }

            paperApp.populateTrayMenu(getTrayMenu());

            // run any initializers that need to happen before we begin
            paperApp.initializeBeforeStarting();

            // get all the pens and start them in live mode...
            // we assume we have decided where each pen server will run
            // start live mode will connect to that pen server.
            if (paperApp.getPenInputDevices().size() == 0) {
                // DebugUtils.println(paperApp.getName()
                // + " does not have any pens! We will add a single streaming pen for you.");
                final Pen aPen = new Pen();
                paperApp.addPenInput(aPen);
            }

            loadedApplications.add(paperApp);
            // provides access back to the toolkit object
            paperApp.setHostToolkit(this);
        }

        // set up the monitoring
        // assume only one for now...
        if (monitoringService == null) {
            monitoringService = new ToolkitMonitoringService(this);
        }
        // tell the monitor...
        monitoringService.startedApp(paperApp);

        // register all the live pens with the dispatcher
        final List<InputDevice> pens = paperApp.getPenInputDevices();
        for (InputDevice pen : pens) {
            pen.startLiveMode(); // starts live mode at the pen's default place
            if (pen.isLive()) {
                eventDispatcher.register(pen);
            }
        }

        // keep track of the pattern assigned to different sheets and regions
        final Collection<PatternToSheetMapping> patternMappings = paperApp.getPatternMaps();
        eventDispatcher.registerPatternMapsForEventHandling(patternMappings);
        paperApp.setRunning(true);
    }

    /**
     * Helps guide developers through the Paper Toolkit.
     */
    private void startToolExplorer() {
        new ToolExplorer(this);
    }

    /**
     * Stop receiving events from its pens....
     * 
     * @param paperApp
     */
    public void stopApplication(Application paperApp) {
        final List<InputDevice> pens = paperApp.getPenInputDevices();
        for (InputDevice pen : pens) {
            if (pen.isLive()) {
                eventDispatcher.unregisterPen(pen);
                // stop the pen from listening!
                pen.stopLiveMode();
            }
        }
        eventDispatcher.unregisterPatternMapsForEventHandling(paperApp.getPatternMaps());
        paperApp.setRunning(false);
    }
}