1
0
mirror of https://github.com/arduino/Arduino.git synced 2025-01-17 06:52:18 +01:00

MacOSX: double click on a .ino file works again. Fixes #2888

This commit is contained in:
Federico Fissore 2015-04-29 09:10:26 +02:00
parent 0e93798455
commit 74dea286a7
4 changed files with 284 additions and 283 deletions

Binary file not shown.

View File

@ -22,6 +22,7 @@
package processing.app; package processing.app;
import cc.arduino.contributions.DownloadableContributionVersionComparator;
import cc.arduino.contributions.VersionHelper; import cc.arduino.contributions.VersionHelper;
import cc.arduino.contributions.libraries.ContributedLibrary; import cc.arduino.contributions.libraries.ContributedLibrary;
import cc.arduino.contributions.libraries.LibrariesIndexer; import cc.arduino.contributions.libraries.LibrariesIndexer;
@ -30,7 +31,6 @@ import cc.arduino.contributions.libraries.ui.LibraryManagerUI;
import cc.arduino.contributions.packages.ContributedPlatform; import cc.arduino.contributions.packages.ContributedPlatform;
import cc.arduino.contributions.packages.ContributionInstaller; import cc.arduino.contributions.packages.ContributionInstaller;
import cc.arduino.contributions.packages.ContributionsIndexer; import cc.arduino.contributions.packages.ContributionsIndexer;
import cc.arduino.contributions.DownloadableContributionVersionComparator;
import cc.arduino.contributions.packages.ui.ContributionManagerUI; import cc.arduino.contributions.packages.ui.ContributionManagerUI;
import cc.arduino.files.DeleteFilesOnShutdown; import cc.arduino.files.DeleteFilesOnShutdown;
import cc.arduino.packages.DiscoveryManager; import cc.arduino.packages.DiscoveryManager;
@ -79,7 +79,9 @@ public class Base {
} }
}; };
static private boolean commandLine; private static boolean commandLine;
public static volatile Base INSTANCE;
public static SplashScreenHelper splashScreenHelper = new SplashScreenHelper(SplashScreen.getSplashScreen()); public static SplashScreenHelper splashScreenHelper = new SplashScreenHelper(SplashScreen.getSplashScreen());
// A single instance of the preferences window // A single instance of the preferences window
@ -104,7 +106,7 @@ public class Base {
// p5 icon for the window // p5 icon for the window
// static Image icon; // static Image icon;
// int editorCount; // int editorCount;
List<Editor> editors = Collections.synchronizedList(new ArrayList<Editor>()); List<Editor> editors = Collections.synchronizedList(new ArrayList<Editor>());
Editor activeEditor; Editor activeEditor;
@ -119,6 +121,10 @@ public class Base {
splashScreenHelper.splashText(_("Loading configuration...")); splashScreenHelper.splashText(_("Loading configuration..."));
if (OSUtils.isMacOS()) {
ThinkDifferent.init();
}
try { try {
guardedMain(args); guardedMain(args);
} catch (Throwable e) { } catch (Throwable e) {
@ -126,12 +132,12 @@ public class Base {
System.exit(255); System.exit(255);
} }
} }
static public void guardedMain(String args[]) throws Exception { static public void guardedMain(String args[]) throws Exception {
Runtime.getRuntime().addShutdownHook(new Thread(DeleteFilesOnShutdown.INSTANCE)); Runtime.getRuntime().addShutdownHook(new Thread(DeleteFilesOnShutdown.INSTANCE));
BaseNoGui.initLogger(); BaseNoGui.initLogger();
BaseNoGui.notifier = new GUIUserNotifier(); BaseNoGui.notifier = new GUIUserNotifier();
initPlatform(); initPlatform();
@ -207,7 +213,7 @@ public class Base {
untitledFolder = createTempFolder("untitled"); untitledFolder = createTempFolder("untitled");
DeleteFilesOnShutdown.add(untitledFolder); DeleteFilesOnShutdown.add(untitledFolder);
new Base(args); INSTANCE = new Base(args);
} }
@ -231,9 +237,9 @@ public class Base {
Class.forName("com.sun.jdi.VirtualMachine"); Class.forName("com.sun.jdi.VirtualMachine");
} catch (ClassNotFoundException cnfe) { } catch (ClassNotFoundException cnfe) {
showError(_("Please install JDK 1.5 or later"), showError(_("Please install JDK 1.5 or later"),
_("Arduino requires a full JDK (not just a JRE)\n" + _("Arduino requires a full JDK (not just a JRE)\n" +
"to run. Please install JDK 1.5 or later.\n" + "to run. Please install JDK 1.5 or later.\n" +
"More information can be found in the reference."), cnfe); "More information can be found in the reference."), cnfe);
} }
} }
@ -247,8 +253,6 @@ public class Base {
public Base(String[] args) throws Exception { public Base(String[] args) throws Exception {
getPlatform().init(); getPlatform().init();
if (OSUtils.isMacOS())
ThinkDifferent.init(this);
String sketchbookPath = BaseNoGui.getSketchbookPath(); String sketchbookPath = BaseNoGui.getSketchbookPath();
@ -268,13 +272,13 @@ public class Base {
BaseNoGui.initPackages(); BaseNoGui.initPackages();
splashScreenHelper.splashText(_("Preparing boards...")); splashScreenHelper.splashText(_("Preparing boards..."));
rebuildBoardsMenu(); rebuildBoardsMenu();
// Setup board-dependent variables. // Setup board-dependent variables.
onBoardOrPortChange(); onBoardOrPortChange();
CommandlineParser parser = CommandlineParser.newCommandlineParser(args); CommandlineParser parser = CommandlineParser.newCommandlineParser(args);
for (String path: parser.getFilenames()) { for (String path : parser.getFilenames()) {
// Correctly resolve relative paths // Correctly resolve relative paths
File file = absoluteFile(path); File file = absoluteFile(path);
@ -312,6 +316,7 @@ public class Base {
ContributionsIndexer indexer = new ContributionsIndexer(BaseNoGui.getSettingsFolder()); ContributionsIndexer indexer = new ContributionsIndexer(BaseNoGui.getSettingsFolder());
ContributionInstaller installer = new ContributionInstaller(indexer) { ContributionInstaller installer = new ContributionInstaller(indexer) {
private String lastStatus = ""; private String lastStatus = "";
@Override @Override
protected void onProgress(Progress progress) { protected void onProgress(Progress progress) {
if (!lastStatus.equals(progress.getStatus())) { if (!lastStatus.equals(progress.getStatus())) {
@ -357,6 +362,7 @@ public class Base {
LibrariesIndexer indexer = new LibrariesIndexer(BaseNoGui.getSettingsFolder()); LibrariesIndexer indexer = new LibrariesIndexer(BaseNoGui.getSettingsFolder());
LibraryInstaller installer = new LibraryInstaller(indexer) { LibraryInstaller installer = new LibraryInstaller(indexer) {
private String lastStatus = ""; private String lastStatus = "";
@Override @Override
protected void onProgress(Progress progress) { protected void onProgress(Progress progress) {
if (!lastStatus.equals(progress.getStatus())) { if (!lastStatus.equals(progress.getStatus())) {
@ -374,7 +380,7 @@ public class Base {
for (String library : parser.getLibraryToInstall().split(",")) { for (String library : parser.getLibraryToInstall().split(",")) {
String[] libraryToInstallParts = library.split(":"); String[] libraryToInstallParts = library.split(":");
ContributedLibrary selected=null; ContributedLibrary selected = null;
if (libraryToInstallParts.length == 2) { if (libraryToInstallParts.length == 2) {
selected = indexer.getIndex().find(libraryToInstallParts[0], VersionHelper.valueOf(libraryToInstallParts[1]).toString()); selected = indexer.getIndex().find(libraryToInstallParts[0], VersionHelper.valueOf(libraryToInstallParts[1]).toString());
} else if (libraryToInstallParts.length == 1) { } else if (libraryToInstallParts.length == 1) {
@ -400,63 +406,60 @@ public class Base {
System.exit(0); System.exit(0);
} else if (parser.isVerifyOrUploadMode()) { } else if (parser.isVerifyOrUploadMode()) {
splashScreenHelper.close(); splashScreenHelper.close();
// Set verbosity for command line build // Set verbosity for command line build
Preferences.set("build.verbose", "" + parser.isDoVerboseBuild()); Preferences.set("build.verbose", "" + parser.isDoVerboseBuild());
Preferences.set("upload.verbose", "" + parser.isDoVerboseUpload()); Preferences.set("upload.verbose", "" + parser.isDoVerboseUpload());
Preferences.set("runtime.preserve.temp.files", Boolean.toString(parser.isPreserveTempFiles())); Preferences.set("runtime.preserve.temp.files", Boolean.toString(parser.isPreserveTempFiles()));
// Make sure these verbosity preferences are only for the // Make sure these verbosity preferences are only for the
// current session // current session
Preferences.setDoSave(false); Preferences.setDoSave(false);
Editor editor = editors.get(0); Editor editor = editors.get(0);
if (parser.isUploadMode()) { if (parser.isUploadMode()) {
splashScreenHelper.splashText(_("Verifying and uploading...")); splashScreenHelper.splashText(_("Verifying and uploading..."));
editor.exportHandler.run(); editor.exportHandler.run();
} else { } else {
splashScreenHelper.splashText(_("Verifying...")); splashScreenHelper.splashText(_("Verifying..."));
editor.runHandler.run(); editor.runHandler.run();
} }
// Error during build or upload // Error during build or upload
int res = editor.status.mode; int res = editor.status.mode;
if (res == EditorStatus.ERR) if (res == EditorStatus.ERR)
System.exit(1); System.exit(1);
// No errors exit gracefully // No errors exit gracefully
System.exit(0);
} else if (parser.isGuiMode()) {
splashScreenHelper.splashText(_("Starting..."));
// Check if there were previously opened sketches to be restored
restoreSketches();
// Create a new empty window (will be replaced with any files to be opened)
if (editors.isEmpty()) {
handleNew();
}
// Check for updates
if (Preferences.getBoolean("update.check")) {
new UpdateCheck(this);
}
} else if (parser.isNoOpMode()) {
// Do nothing (intended for only changing preferences)
System.exit(0);
} else if (parser.isGetPrefMode()) {
String value = Preferences.get(parser.getGetPref(), null);
if (value != null) {
System.out.println(value);
System.exit(0); System.exit(0);
} else {
System.exit(4);
} }
else if (parser.isGuiMode()) { }
splashScreenHelper.splashText(_("Starting..."));
// Check if there were previously opened sketches to be restored
restoreSketches();
// Create a new empty window (will be replaced with any files to be opened)
if (editors.isEmpty()) {
handleNew();
}
// Check for updates
if (Preferences.getBoolean("update.check")) {
new UpdateCheck(this);
}
}
else if (parser.isNoOpMode()) {
// Do nothing (intended for only changing preferences)
System.exit(0);
}
else if (parser.isGetPrefMode()) {
String value = Preferences.get(parser.getGetPref(), null);
if (value != null) {
System.out.println(value);
System.exit(0);
} else {
System.exit(4);
}
}
} }
/** /**
@ -464,7 +467,8 @@ public class Base {
* sketch that was used (if any), and restores other Editor settings. * sketch that was used (if any), and restores other Editor settings.
* The complement to "storePreferences", this is called when the * The complement to "storePreferences", this is called when the
* application is first launched. * application is first launched.
* @throws Exception *
* @throws Exception
*/ */
protected boolean restoreSketches() throws Exception { protected boolean restoreSketches() throws Exception {
// figure out window placement // figure out window placement
@ -544,7 +548,7 @@ public class Base {
// In case of a crash, save untitled sketches if they contain changes. // In case of a crash, save untitled sketches if they contain changes.
// (Added this for release 0158, may not be a good idea.) // (Added this for release 0158, may not be a good idea.)
if (path.startsWith(untitledPath) && if (path.startsWith(untitledPath) &&
!editor.getSketch().isModified()) { !editor.getSketch().isModified()) {
continue; continue;
} }
if (BaseNoGui.getPortableFolder() != null) { if (BaseNoGui.getPortableFolder() != null) {
@ -570,8 +574,7 @@ public class Base {
String untitledPath = untitledFolder.getAbsolutePath(); String untitledPath = untitledFolder.getAbsolutePath();
if (path.startsWith(untitledPath)) { if (path.startsWith(untitledPath)) {
path = ""; path = "";
} else } else if (BaseNoGui.getPortableFolder() != null) {
if (BaseNoGui.getPortableFolder() != null) {
path = FileUtils.relativePath(BaseNoGui.getPortableFolder().toString(), path); path = FileUtils.relativePath(BaseNoGui.getPortableFolder().toString(), path);
if (path == null) if (path == null)
path = ""; path = "";
@ -621,10 +624,10 @@ public class Base {
if (activeEditor == null) { if (activeEditor == null) {
Rectangle screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().getBounds(); Rectangle screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().getBounds();
// If no current active editor, use default placement // If no current active editor, use default placement
return new int[] { return new int[]{
(screen.width - defaultWidth) / 2, (screen.width - defaultWidth) / 2,
(screen.height - defaultHeight) / 2, (screen.height - defaultHeight) / 2,
defaultWidth, defaultHeight, 0 defaultWidth, defaultHeight, 0
}; };
} else { } else {
@ -643,14 +646,14 @@ public class Base {
location[1] += OVER; location[1] += OVER;
if (location[0] == OVER || if (location[0] == OVER ||
location[2] == OVER || location[2] == OVER ||
location[0] + location[2] > screen.width || location[0] + location[2] > screen.width ||
location[1] + location[3] > screen.height) { location[1] + location[3] > screen.height) {
// Warp the next window to a randomish location on screen. // Warp the next window to a randomish location on screen.
return new int[] { return new int[]{
(int) (Math.random() * (screen.width - defaultWidth)), (int) (Math.random() * (screen.width - defaultWidth)),
(int) (Math.random() * (screen.height - defaultHeight)), (int) (Math.random() * (screen.height - defaultHeight)),
defaultWidth, defaultHeight, 0 defaultWidth, defaultHeight, 0
}; };
} }
@ -665,14 +668,15 @@ public class Base {
boolean breakTime = false; boolean breakTime = false;
String[] months = { String[] months = {
"jan", "feb", "mar", "apr", "may", "jun", "jan", "feb", "mar", "apr", "may", "jun",
"jul", "aug", "sep", "oct", "nov", "dec" "jul", "aug", "sep", "oct", "nov", "dec"
}; };
/** /**
* Handle creating a sketch folder, return its base .pde file * Handle creating a sketch folder, return its base .pde file
* or null if the operation was canceled. * or null if the operation was canceled.
* @param shift whether shift is pressed, which will invert prompt setting *
* @param shift whether shift is pressed, which will invert prompt setting
* @param noPrompt disable prompt, no matter the setting * @param noPrompt disable prompt, no matter the setting
*/ */
protected File createNewUntitled() throws IOException { protected File createNewUntitled() throws IOException {
@ -698,12 +702,12 @@ public class Base {
// In 0159, avoid running past z by sending people outdoors. // In 0159, avoid running past z by sending people outdoors.
if (!breakTime) { if (!breakTime) {
showWarning(_("Time for a Break"), showWarning(_("Time for a Break"),
_("You've reached the limit for auto naming of new sketches\n" + _("You've reached the limit for auto naming of new sketches\n" +
"for the day. How about going for a walk instead?"), null); "for the day. How about going for a walk instead?"), null);
breakTime = true; breakTime = true;
} else { } else {
showWarning(_("Sunshine"), showWarning(_("Sunshine"),
_("No really, time for some fresh air for you."), null); _("No really, time for some fresh air for you."), null);
} }
return null; return null;
} }
@ -728,7 +732,8 @@ public class Base {
/** /**
* Create a new untitled document in a new sketch window. * Create a new untitled document in a new sketch window.
* @throws Exception *
* @throws Exception
*/ */
public void handleNew() throws Exception { public void handleNew() throws Exception {
try { try {
@ -779,9 +784,11 @@ public class Base {
/** /**
* Open a sketch, replacing the sketch in the current window. * Open a sketch, replacing the sketch in the current window.
*
* @param path Location of the primary pde file for the sketch. * @param path Location of the primary pde file for the sketch.
*/ */
public void handleOpenReplace(File file) { public void handleOpenReplace(File file) {
System.out.println("handleOpenReplace");
if (!activeEditor.checkModified()) { if (!activeEditor.checkModified()) {
return; // sketch was modified, and user canceled return; // sketch was modified, and user canceled
} }
@ -798,7 +805,8 @@ public class Base {
/** /**
* Prompt for a sketch to open, and open it in a new window. * Prompt for a sketch to open, and open it in a new window.
* @throws Exception *
* @throws Exception
*/ */
public void handleOpenPrompt() throws Exception { public void handleOpenPrompt() throws Exception {
// get the frontmost window frame for placing file dialog // get the frontmost window frame for placing file dialog
@ -834,10 +842,11 @@ public class Base {
/** /**
* Open a sketch in a new window. * Open a sketch in a new window.
*
* @param file File to open * @param file File to open
* @return the Editor object, so that properties (like 'untitled') * @return the Editor object, so that properties (like 'untitled')
* can be set by the caller * can be set by the caller
* @throws Exception * @throws Exception
*/ */
public Editor handleOpen(File file) throws Exception { public Editor handleOpen(File file) throws Exception {
return handleOpen(file, nextEditorLocation(), true); return handleOpen(file, nextEditorLocation(), true);
@ -923,6 +932,7 @@ public class Base {
/** /**
* Close a sketch as specified by its editor window. * Close a sketch as specified by its editor window.
*
* @param editor Editor object of the sketch to be closed. * @param editor Editor object of the sketch to be closed.
* @return true if succeeded in closing, false if canceled. * @return true if succeeded in closing, false if canceled.
*/ */
@ -942,26 +952,26 @@ public class Base {
// if (Preferences.getBoolean("sketchbook.closing_last_window_quits") || // if (Preferences.getBoolean("sketchbook.closing_last_window_quits") ||
// (editor.untitled && !editor.getSketch().isModified())) { // (editor.untitled && !editor.getSketch().isModified())) {
if (OSUtils.isMacOS()) { if (OSUtils.isMacOS()) {
Object[] options = { "OK", "Cancel" }; Object[] options = {"OK", "Cancel"};
String prompt = String prompt =
_("<html> " + _("<html> " +
"<head> <style type=\"text/css\">"+ "<head> <style type=\"text/css\">" +
"b { font: 13pt \"Lucida Grande\" }"+ "b { font: 13pt \"Lucida Grande\" }" +
"p { font: 11pt \"Lucida Grande\"; margin-top: 8px }"+ "p { font: 11pt \"Lucida Grande\"; margin-top: 8px }" +
"</style> </head>" + "</style> </head>" +
"<b>Are you sure you want to Quit?</b>" + "<b>Are you sure you want to Quit?</b>" +
"<p>Closing the last open sketch will quit Arduino."); "<p>Closing the last open sketch will quit Arduino.");
int result = JOptionPane.showOptionDialog(editor, int result = JOptionPane.showOptionDialog(editor,
prompt, prompt,
_("Quit"), _("Quit"),
JOptionPane.YES_NO_OPTION, JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE, JOptionPane.QUESTION_MESSAGE,
null, null,
options, options,
options[0]); options[0]);
if (result == JOptionPane.NO_OPTION || if (result == JOptionPane.NO_OPTION ||
result == JOptionPane.CLOSED_OPTION) { result == JOptionPane.CLOSED_OPTION) {
return false; return false;
} }
} }
@ -1004,9 +1014,11 @@ public class Base {
/** /**
* Handler for File &rarr; Quit. * Handler for File &rarr; Quit.
*
* @return false if canceled, true otherwise. * @return false if canceled, true otherwise.
*/ */
public boolean handleQuit() { public boolean handleQuit() {
System.out.println("handleQuit");
// If quit is canceled, this will be replaced anyway // If quit is canceled, this will be replaced anyway
// by a later handleQuit() that is not canceled. // by a later handleQuit() that is not canceled.
storeSketches(); storeSketches();
@ -1037,6 +1049,7 @@ public class Base {
/** /**
* Attempt to close each open sketch in preparation for quitting. * Attempt to close each open sketch in preparation for quitting.
*
* @return false if canceled along the way * @return false if canceled along the way
*/ */
protected boolean handleQuitEach() { protected boolean handleQuitEach() {
@ -1084,14 +1097,14 @@ public class Base {
// Add the single "Open" item // Add the single "Open" item
item = Editor.newJMenuItem(_("Open..."), 'O'); item = Editor.newJMenuItem(_("Open..."), 'O');
item.addActionListener(new ActionListener() { item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
try { try {
handleOpenPrompt(); handleOpenPrompt();
} catch (Exception e1) { } catch (Exception e1) {
e1.printStackTrace(); e1.printStackTrace();
}
} }
}); }
});
menu.add(item); menu.add(item);
menu.addSeparator(); menu.addSeparator();
@ -1117,7 +1130,7 @@ public class Base {
//System.out.println("rebuilding sketchbook menu"); //System.out.println("rebuilding sketchbook menu");
//new Exception().printStackTrace(); //new Exception().printStackTrace();
try { try {
menu.removeAll(); menu.removeAll();
addSketches(menu, getSketchbookFolder(), false); addSketches(menu, getSketchbookFolder(), false);
//addSketches(menu, getSketchbookFolder()); //addSketches(menu, getSketchbookFolder());
} catch (IOException e) { } catch (IOException e) {
@ -1165,7 +1178,7 @@ public class Base {
// Split between user supplied libraries and IDE libraries // Split between user supplied libraries and IDE libraries
TargetPlatform targetPlatform = getTargetPlatform(); TargetPlatform targetPlatform = getTargetPlatform();
if (targetPlatform != null) { if (targetPlatform != null) {
LibraryList ideLibs = getIDELibs(); LibraryList ideLibs = getIDELibs();
LibraryList userLibs = getUserLibs(); LibraryList userLibs = getUserLibs();
@ -1215,7 +1228,7 @@ public class Base {
addSketchesSubmenu(menu, lib, false); addSketchesSubmenu(menu, lib, false);
LibraryList userLibs = getUserLibs(); LibraryList userLibs = getUserLibs();
if (userLibs.size()>0) { if (userLibs.size() > 0) {
menu.addSeparator(); menu.addSeparator();
userLibs.sort(); userLibs.sort();
for (UserLibrary lib : userLibs) for (UserLibrary lib : userLibs)
@ -1249,7 +1262,7 @@ public class Base {
managerUI.setIndexer(BaseNoGui.librariesIndexer); managerUI.setIndexer(BaseNoGui.librariesIndexer);
managerUI.setVisible(true); managerUI.setVisible(true);
// Manager dialog is modal, waits here until closed // Manager dialog is modal, waits here until closed
//handleAddLibrary(); //handleAddLibrary();
onBoardOrPortChange(); onBoardOrPortChange();
rebuildImportMenu(Editor.importMenu); rebuildImportMenu(Editor.importMenu);
@ -1302,7 +1315,7 @@ public class Base {
// If there are no platforms installed we are done // If there are no platforms installed we are done
if (BaseNoGui.packages.size() == 0) if (BaseNoGui.packages.size() == 0)
return; return;
// Separate "Install boards..." command from installed boards // Separate "Install boards..." command from installed boards
boardMenu.add(new JSeparator()); boardMenu.add(new JSeparator());
@ -1346,8 +1359,8 @@ public class Base {
// Cycle through all boards of this platform // Cycle through all boards of this platform
for (TargetBoard board : targetPlatform.getBoards().values()) { for (TargetBoard board : targetPlatform.getBoards().values()) {
JMenuItem item = createBoardMenusAndCustomMenus(boardsCustomMenus, menuItemsToClickAfterStartup, JMenuItem item = createBoardMenusAndCustomMenus(boardsCustomMenus, menuItemsToClickAfterStartup,
buttonGroupsMap, buttonGroupsMap,
board, targetPlatform, targetPackage); board, targetPlatform, targetPackage);
boardMenu.add(item); boardMenu.add(item);
boardsButtonGroup.add(item); boardsButtonGroup.add(item);
} }
@ -1368,7 +1381,7 @@ public class Base {
final List<JMenu> boardsCustomMenus, List<JMenuItem> menuItemsToClickAfterStartup, final List<JMenu> boardsCustomMenus, List<JMenuItem> menuItemsToClickAfterStartup,
Map<String, ButtonGroup> buttonGroupsMap, Map<String, ButtonGroup> buttonGroupsMap,
TargetBoard board, TargetPlatform targetPlatform, TargetPackage targetPackage) TargetBoard board, TargetPlatform targetPlatform, TargetPackage targetPackage)
throws Exception { throws Exception {
String selPackage = Preferences.get("target_package"); String selPackage = Preferences.get("target_package");
String selPlatform = Preferences.get("target_platform"); String selPlatform = Preferences.get("target_platform");
String selBoard = Preferences.get("board"); String selBoard = Preferences.get("board");
@ -1376,13 +1389,13 @@ public class Base {
String boardId = board.getId(); String boardId = board.getId();
String packageName = targetPackage.getId(); String packageName = targetPackage.getId();
String platformName = targetPlatform.getId(); String platformName = targetPlatform.getId();
// Setup a menu item for the current board // Setup a menu item for the current board
@SuppressWarnings("serial") @SuppressWarnings("serial")
Action action = new AbstractAction(board.getName()) { Action action = new AbstractAction(board.getName()) {
public void actionPerformed(ActionEvent actionevent) { public void actionPerformed(ActionEvent actionevent) {
selectBoard((TargetBoard)getValue("b")); selectBoard((TargetBoard) getValue("b"));
filterVisibilityOfSubsequentBoardMenus(boardsCustomMenus, (TargetBoard)getValue("b"), 1); filterVisibilityOfSubsequentBoardMenus(boardsCustomMenus, (TargetBoard) getValue("b"), 1);
onBoardOrPortChange(); onBoardOrPortChange();
rebuildImportMenu(Editor.importMenu); rebuildImportMenu(Editor.importMenu);
@ -1394,7 +1407,7 @@ public class Base {
JRadioButtonMenuItem item = new JRadioButtonMenuItem(action); JRadioButtonMenuItem item = new JRadioButtonMenuItem(action);
if (selBoard.equals(boardId) && selPackage.equals(packageName) if (selBoard.equals(boardId) && selPackage.equals(packageName)
&& selPlatform.equals(platformName)) { && selPlatform.equals(platformName)) {
menuItemsToClickAfterStartup.add(item); menuItemsToClickAfterStartup.add(item);
} }
@ -1402,14 +1415,14 @@ public class Base {
for (final String menuId : customMenus.keySet()) { for (final String menuId : customMenus.keySet()) {
String title = customMenus.get(menuId); String title = customMenus.get(menuId);
JMenu menu = getBoardCustomMenu(_(title)); JMenu menu = getBoardCustomMenu(_(title));
if (board.hasMenu(menuId)) { if (board.hasMenu(menuId)) {
PreferencesMap boardCustomMenu = board.getMenuLabels(menuId); PreferencesMap boardCustomMenu = board.getMenuLabels(menuId);
for (String customMenuOption : boardCustomMenu.keySet()) { for (String customMenuOption : boardCustomMenu.keySet()) {
@SuppressWarnings("serial") @SuppressWarnings("serial")
Action subAction = new AbstractAction(_(boardCustomMenu.get(customMenuOption))) { Action subAction = new AbstractAction(_(boardCustomMenu.get(customMenuOption))) {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
Preferences.set("custom_" + menuId, ((TargetBoard)getValue("board")).getId() + "_" + getValue("custom_menu_option")); Preferences.set("custom_" + menuId, ((TargetBoard) getValue("board")).getId() + "_" + getValue("custom_menu_option"));
onBoardOrPortChange(); onBoardOrPortChange();
} }
}; };
@ -1431,7 +1444,7 @@ public class Base {
} }
} }
} }
return item; return item;
} }
@ -1523,7 +1536,7 @@ public class Base {
@SuppressWarnings("serial") @SuppressWarnings("serial")
AbstractAction action = new AbstractAction(targetPlatform AbstractAction action = new AbstractAction(targetPlatform
.getProgrammer(programmer).get("name")) { .getProgrammer(programmer).get("name")) {
public void actionPerformed(ActionEvent actionevent) { public void actionPerformed(ActionEvent actionevent) {
Preferences.set("programmer", "" + getValue("id")); Preferences.set("programmer", "" + getValue("id"));
} }
@ -1582,13 +1595,13 @@ public class Base {
private boolean addSketchesSubmenu(JMenu menu, UserLibrary lib, private boolean addSketchesSubmenu(JMenu menu, UserLibrary lib,
boolean replaceExisting) boolean replaceExisting)
throws IOException { throws IOException {
return addSketchesSubmenu(menu, lib.getName(), lib.getInstalledFolder(), return addSketchesSubmenu(menu, lib.getName(), lib.getInstalledFolder(),
replaceExisting); replaceExisting);
} }
private boolean addSketchesSubmenu(JMenu menu, String name, File folder, private boolean addSketchesSubmenu(JMenu menu, String name, File folder,
final boolean replaceExisting) throws IOException { final boolean replaceExisting) throws IOException {
ActionListener listener = new ActionListener() { ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
@ -1610,7 +1623,7 @@ public class Base {
} }
} else { } else {
showWarning(_("Sketch Does Not Exist"), showWarning(_("Sketch Does Not Exist"),
_("The selected sketch no longer exists.\n" _("The selected sketch no longer exists.\n"
+ "You may need to restart Arduino to update\n" + "You may need to restart Arduino to update\n"
+ "the sketchbook menu."), null); + "the sketchbook menu."), null);
} }
@ -1627,13 +1640,13 @@ public class Base {
if (!BaseNoGui.isSanitaryName(name)) { if (!BaseNoGui.isSanitaryName(name)) {
if (!builtOnce) { if (!builtOnce) {
String complaining = I18n String complaining = I18n
.format( .format(
_("The sketch \"{0}\" cannot be used.\n" _("The sketch \"{0}\" cannot be used.\n"
+ "Sketch names must contain only basic letters and numbers\n" + "Sketch names must contain only basic letters and numbers\n"
+ "(ASCII-only with no spaces, " + "(ASCII-only with no spaces, "
+ "and it cannot start with a number).\n" + "and it cannot start with a number).\n"
+ "To get rid of this message, remove the sketch from\n" + "To get rid of this message, remove the sketch from\n"
+ "{1}"), name, entry.getAbsolutePath()); + "{1}"), name, entry.getAbsolutePath());
showMessage(_("Ignoring sketch with bad name"), complaining); showMessage(_("Ignoring sketch with bad name"), complaining);
} }
return false; return false;
@ -1778,8 +1791,6 @@ public class Base {
// return PConstants.OTHER; // return PConstants.OTHER;
// } // }
// } // }
static public Platform getPlatform() { static public Platform getPlatform() {
return BaseNoGui.getPlatform(); return BaseNoGui.getPlatform();
} }
@ -1815,6 +1826,7 @@ public class Base {
* Convenience method to get a File object for the specified filename inside * Convenience method to get a File object for the specified filename inside
* the settings folder. * the settings folder.
* For now, only used by Preferences to get the preferences.txt file. * For now, only used by Preferences to get the preferences.txt file.
*
* @param filename A file inside the settings folder. * @param filename A file inside the settings folder.
* @return filename wrapped as a File object inside the settings folder * @return filename wrapped as a File object inside the settings folder
*/ */
@ -1872,7 +1884,7 @@ public class Base {
//Get the core libraries //Get the core libraries
static public File getCoreLibraries(String path) { static public File getCoreLibraries(String path) {
return getContentFile(path); return getContentFile(path);
} }
static public String getHardwarePath() { static public String getHardwarePath() {
@ -1973,8 +1985,8 @@ public class Base {
if (!result) { if (!result) {
showError(_("You forgot your sketchbook"), showError(_("You forgot your sketchbook"),
_("Arduino cannot run because it could not\n" + _("Arduino cannot run because it could not\n" +
"create a folder to store your sketchbook."), null); "create a folder to store your sketchbook."), null);
} }
return sketchbookFolder; return sketchbookFolder;
@ -2018,13 +2030,14 @@ public class Base {
} catch (Exception e) { } catch (Exception e) {
showWarning(_("Problem Opening URL"), showWarning(_("Problem Opening URL"),
I18n.format(_("Could not open the URL\n{0}"), url), e); I18n.format(_("Could not open the URL\n{0}"), url), e);
} }
} }
/** /**
* Used to determine whether to disable the "Show Sketch Folder" option. * Used to determine whether to disable the "Show Sketch Folder" option.
*
* @return true If a means of opening a folder is known to be available. * @return true If a means of opening a folder is known to be available.
*/ */
static protected boolean openFolderAvailable() { static protected boolean openFolderAvailable() {
@ -2042,7 +2055,7 @@ public class Base {
} catch (Exception e) { } catch (Exception e) {
showWarning(_("Problem Opening Folder"), showWarning(_("Problem Opening Folder"),
I18n.format(_("Could not open the folder\n{0}"), file.getAbsolutePath()), e); I18n.format(_("Could not open the folder\n{0}"), file.getAbsolutePath()), e);
} }
} }
@ -2109,12 +2122,12 @@ public class Base {
ActionListener disposer) { ActionListener disposer) {
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
root.registerKeyboardAction(disposer, stroke, root.registerKeyboardAction(disposer, stroke,
JComponent.WHEN_IN_FOCUSED_WINDOW); JComponent.WHEN_IN_FOCUSED_WINDOW);
int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
stroke = KeyStroke.getKeyStroke('W', modifiers); stroke = KeyStroke.getKeyStroke('W', modifiers);
root.registerKeyboardAction(disposer, stroke, root.registerKeyboardAction(disposer, stroke,
JComponent.WHEN_IN_FOCUSED_WINDOW); JComponent.WHEN_IN_FOCUSED_WINDOW);
} }
@ -2208,15 +2221,14 @@ public class Base {
// ................................................................... // ...................................................................
// incomplete // incomplete
static public int showYesNoCancelQuestion(Editor editor, String title, static public int showYesNoCancelQuestion(Editor editor, String title,
String primary, String secondary) { String primary, String secondary) {
if (!OSUtils.isMacOS()) { if (!OSUtils.isMacOS()) {
int result = int result =
JOptionPane.showConfirmDialog(null, primary + "\n" + secondary, title, JOptionPane.showConfirmDialog(null, primary + "\n" + secondary, title,
JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE); JOptionPane.QUESTION_MESSAGE);
return result; return result;
// if (result == JOptionPane.YES_OPTION) { // if (result == JOptionPane.YES_OPTION) {
// //
@ -2234,18 +2246,18 @@ public class Base {
// Pane formatting adapted from the Quaqua guide // Pane formatting adapted from the Quaqua guide
// http://www.randelshofer.ch/quaqua/guide/joptionpane.html // http://www.randelshofer.ch/quaqua/guide/joptionpane.html
JOptionPane pane = JOptionPane pane =
new JOptionPane("<html> " + new JOptionPane("<html> " +
"<head> <style type=\"text/css\">"+ "<head> <style type=\"text/css\">" +
"b { font: 13pt \"Lucida Grande\" }"+ "b { font: 13pt \"Lucida Grande\" }" +
"p { font: 11pt \"Lucida Grande\"; margin-top: 8px }"+ "p { font: 11pt \"Lucida Grande\"; margin-top: 8px }" +
"</style> </head>" + "</style> </head>" +
"<b>Do you want to save changes to this sketch<BR>" + "<b>Do you want to save changes to this sketch<BR>" +
" before closing?</b>" + " before closing?</b>" +
"<p>If you don't save, your changes will be lost.", "<p>If you don't save, your changes will be lost.",
JOptionPane.QUESTION_MESSAGE); JOptionPane.QUESTION_MESSAGE);
String[] options = new String[] { String[] options = new String[]{
"Save", "Cancel", "Don't Save" "Save", "Cancel", "Don't Save"
}; };
pane.setOptions(options); pane.setOptions(options);
@ -2255,7 +2267,7 @@ public class Base {
// on macosx, setting the destructive property places this option // on macosx, setting the destructive property places this option
// away from the others at the lefthand side // away from the others at the lefthand side
pane.putClientProperty("Quaqua.OptionPane.destructiveOption", pane.putClientProperty("Quaqua.OptionPane.destructiveOption",
new Integer(2)); new Integer(2));
JDialog dialog = pane.createDialog(editor, null); JDialog dialog = pane.createDialog(editor, null);
dialog.setVisible(true); dialog.setVisible(true);
@ -2287,29 +2299,29 @@ public class Base {
// } // }
static public int showYesNoQuestion(Frame editor, String title, static public int showYesNoQuestion(Frame editor, String title,
String primary, String secondary) { String primary, String secondary) {
if (!OSUtils.isMacOS()) { if (!OSUtils.isMacOS()) {
return JOptionPane.showConfirmDialog(editor, return JOptionPane.showConfirmDialog(editor,
"<html><body>" + "<html><body>" +
"<b>" + primary + "</b>" + "<b>" + primary + "</b>" +
"<br>" + secondary, title, "<br>" + secondary, title,
JOptionPane.YES_NO_OPTION, JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE); JOptionPane.QUESTION_MESSAGE);
} else { } else {
// Pane formatting adapted from the Quaqua guide // Pane formatting adapted from the Quaqua guide
// http://www.randelshofer.ch/quaqua/guide/joptionpane.html // http://www.randelshofer.ch/quaqua/guide/joptionpane.html
JOptionPane pane = JOptionPane pane =
new JOptionPane("<html> " + new JOptionPane("<html> " +
"<head> <style type=\"text/css\">"+ "<head> <style type=\"text/css\">" +
"b { font: 13pt \"Lucida Grande\" }"+ "b { font: 13pt \"Lucida Grande\" }" +
"p { font: 11pt \"Lucida Grande\"; margin-top: 8px }"+ "p { font: 11pt \"Lucida Grande\"; margin-top: 8px }" +
"</style> </head>" + "</style> </head>" +
"<b>" + primary + "</b>" + "<b>" + primary + "</b>" +
"<p>" + secondary + "</p>", "<p>" + secondary + "</p>",
JOptionPane.QUESTION_MESSAGE); JOptionPane.QUESTION_MESSAGE);
String[] options = new String[] { String[] options = new String[]{
"Yes", "No" "Yes", "No"
}; };
pane.setOptions(options); pane.setOptions(options);
@ -2367,7 +2379,6 @@ public class Base {
return null; return null;
} }
*/ */
static public File getContentFile(String name) { static public File getContentFile(String name) {
return BaseNoGui.getContentFile(name); return BaseNoGui.getContentFile(name);
} }
@ -2393,7 +2404,8 @@ public class Base {
tracker.addImage(image, 0); tracker.addImage(image, 0);
try { try {
tracker.waitForAll(); tracker.waitForAll();
} catch (InterruptedException e) { } } catch (InterruptedException e) {
}
return image; return image;
} }
@ -2427,7 +2439,7 @@ public class Base {
byte buffer[] = new byte[size]; byte buffer[] = new byte[size];
int offset = 0; int offset = 0;
int bytesRead; int bytesRead;
while ((bytesRead = input.read(buffer, offset, size-offset)) != -1) { while ((bytesRead = input.read(buffer, offset, size - offset)) != -1) {
offset += bytesRead; offset += bytesRead;
if (bytesRead == 0) break; if (bytesRead == 0) break;
} }
@ -2437,20 +2449,19 @@ public class Base {
} }
/** /**
* Read from a file with a bunch of attribute/value pairs * Read from a file with a bunch of attribute/value pairs
* that are separated by = and ignore comments with #. * that are separated by = and ignore comments with #.
*/ */
static public HashMap<String,String> readSettings(File inputFile) { static public HashMap<String, String> readSettings(File inputFile) {
HashMap<String,String> outgoing = new HashMap<String,String>(); HashMap<String, String> outgoing = new HashMap<String, String>();
if (!inputFile.exists()) return outgoing; // return empty hash if (!inputFile.exists()) return outgoing; // return empty hash
String lines[] = PApplet.loadStrings(inputFile); String lines[] = PApplet.loadStrings(inputFile);
for (int i = 0; i < lines.length; i++) { for (int i = 0; i < lines.length; i++) {
int hash = lines[i].indexOf('#'); int hash = lines[i].indexOf('#');
String line = (hash == -1) ? String line = (hash == -1) ?
lines[i].trim() : lines[i].substring(0, hash).trim(); lines[i].trim() : lines[i].substring(0, hash).trim();
if (line.length() == 0) continue; if (line.length() == 0) continue;
int equals = line.indexOf('='); int equals = line.indexOf('=');
@ -2470,9 +2481,9 @@ public class Base {
static public void copyFile(File sourceFile, static public void copyFile(File sourceFile,
File targetFile) throws IOException { File targetFile) throws IOException {
InputStream from = InputStream from =
new BufferedInputStream(new FileInputStream(sourceFile)); new BufferedInputStream(new FileInputStream(sourceFile));
OutputStream to = OutputStream to =
new BufferedOutputStream(new FileOutputStream(targetFile)); new BufferedOutputStream(new FileOutputStream(targetFile));
byte[] buffer = new byte[16 * 1024]; byte[] buffer = new byte[16 * 1024];
int bytesRead; int bytesRead;
while ((bytesRead = from.read(buffer)) != -1) { while ((bytesRead = from.read(buffer)) != -1) {
@ -2563,7 +2574,7 @@ public class Base {
for (int i = 0; i < files.length; i++) { for (int i = 0; i < files.length; i++) {
if (files[i].equals(".") || (files[i].equals("..")) || if (files[i].equals(".") || (files[i].equals("..")) ||
files[i].equals(".DS_Store")) continue; files[i].equals(".DS_Store")) continue;
File fella = new File(folder, files[i]); File fella = new File(folder, files[i]);
if (fella.isDirectory()) { if (fella.isDirectory()) {
size += calcFolderSize(fella); size += calcFolderSize(fella);
@ -2657,9 +2668,9 @@ public class Base {
String libName = libFolder.getName(); String libName = libFolder.getName();
if (!BaseNoGui.isSanitaryName(libName)) { if (!BaseNoGui.isSanitaryName(libName)) {
String mess = I18n.format(_("The library \"{0}\" cannot be used.\n" String mess = I18n.format(_("The library \"{0}\" cannot be used.\n"
+ "Library names must contain only basic letters and numbers.\n" + "Library names must contain only basic letters and numbers.\n"
+ "(ASCII only and no spaces, and it cannot start with a number)"), + "(ASCII only and no spaces, and it cannot start with a number)"),
libName); libName);
activeEditor.statusError(mess); activeEditor.statusError(mess);
return; return;
} }
@ -2686,4 +2697,12 @@ public class Base {
public static DiscoveryManager getDiscoveryManager() { public static DiscoveryManager getDiscoveryManager() {
return BaseNoGui.getDiscoveryManager(); return BaseNoGui.getDiscoveryManager();
} }
public Editor getActiveEditor() {
return activeEditor;
}
public List<Editor> getEditors() {
return new LinkedList<Editor>(editors);
}
} }

View File

@ -22,113 +22,95 @@
package processing.app.macosx; package processing.app.macosx;
import processing.app.Base;
import com.apple.eawt.*; import com.apple.eawt.*;
import processing.app.Base;
import processing.app.Editor;
import java.io.File; import java.io.File;
import java.util.List;
/** /**
* Deal with issues related to thinking different. This handles the basic * Deal with issues related to thinking different. This handles the basic
* Mac OS X menu commands (and apple events) for open, about, prefs, etc. * Mac OS X menu commands (and apple events) for open, about, prefs, etc.
* * <p/>
* Based on OSXAdapter.java from Apple DTS. * Based on OSXAdapter.java from Apple DTS.
* * <p/>
* As of 0140, this code need not be built on platforms other than OS X, * As of 0140, this code need not be built on platforms other than OS X,
* because of the new platform structure which isolates through reflection. * because of the new platform structure which isolates through reflection.
*/ */
public class ThinkDifferent implements ApplicationListener { public class ThinkDifferent {
// pseudo-singleton model; no point in making multiple instances private static final int MAX_WAIT_FOR_BASE = 10000;
// of the EAWT application or our adapter
private static ThinkDifferent adapter;
// http://developer.apple.com/documentation/Java/Reference/1.4.2/appledoc/api/com/apple/eawt/Application.html
private static Application application;
// reference to the app where the existing quit, about, prefs code is static public void init() {
private Base base; Application application = Application.getApplication();
application.setAboutHandler(new AboutHandler() {
@Override
public void handleAbout(AppEvent.AboutEvent aboutEvent) {
if (waitForBase()) {
Base.INSTANCE.handleAbout();
}
}
});
application.setPreferencesHandler(new PreferencesHandler() {
@Override
public void handlePreferences(AppEvent.PreferencesEvent preferencesEvent) {
if (waitForBase()) {
Base.INSTANCE.handlePrefs();
}
}
});
application.setOpenFileHandler(new OpenFilesHandler() {
@Override
public void openFiles(final AppEvent.OpenFilesEvent openFilesEvent) {
if (waitForBase()) {
for (File file : openFilesEvent.getFiles()) {
try {
Base.INSTANCE.handleOpen(file);
List<Editor> editors = Base.INSTANCE.getEditors();
if (editors.size() == 2 && editors.get(0).getSketch().isUntitled()) {
Base.INSTANCE.handleClose(editors.get(0));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
});
application.setQuitHandler(new QuitHandler() {
@Override
public void handleQuitRequestWith(AppEvent.QuitEvent quitEvent, QuitResponse quitResponse) {
if (waitForBase()) {
if (Base.INSTANCE.handleClose(Base.INSTANCE.getActiveEditor())) {
quitResponse.performQuit();
} else {
quitResponse.cancelQuit();
}
}
}
});
}
private static boolean waitForBase() {
static public void init(Base base) { int slept = 0;
if (application == null) { while (Base.INSTANCE == null) {
//application = new com.apple.eawt.Application(); if (slept >= MAX_WAIT_FOR_BASE) {
application = com.apple.eawt.Application.getApplication(); return false;
} }
if (adapter == null) { sleep(100);
adapter = new ThinkDifferent(base); slept += 100;
}
application.addApplicationListener(adapter);
application.setEnabledAboutMenu(true);
application.setEnabledPreferencesMenu(true);
}
public ThinkDifferent(Base base) {
this.base = base;
}
// implemented handler methods. These are basically hooks into existing
// functionality from the main app, as if it came over from another platform.
public void handleAbout(ApplicationEvent ae) {
if (base != null) {
ae.setHandled(true);
base.handleAbout();
} else {
throw new IllegalStateException("handleAbout: Base instance detached from listener");
}
}
public void handlePreferences(ApplicationEvent ae) {
if (base != null) {
base.handlePrefs();
ae.setHandled(true);
} else {
throw new IllegalStateException("handlePreferences: Base instance detached from listener");
} }
return true;
} }
private static void sleep(int millis) {
public void handleOpenApplication(ApplicationEvent ae) {
}
public void handleOpenFile(ApplicationEvent ae) {
// System.out.println("got open file event " + ae.getFilename());
String filename = ae.getFilename();
try { try {
base.handleOpen(new File(filename)); Thread.sleep(100);
} catch (Exception e) { } catch (InterruptedException e) {
e.printStackTrace(); //ignore
}
ae.setHandled(true);
}
public void handlePrintFile(ApplicationEvent ae) {
// TODO implement os x print handler here (open app, call handlePrint, quit)
}
public void handleQuit(ApplicationEvent ae) {
if (base != null) {
/*
/ You MUST setHandled(false) if you want to delay or cancel the quit.
/ This is important for cross-platform development -- have a universal quit
/ routine that chooses whether or not to quit, so the functionality is identical
/ on all platforms. This example simply cancels the AppleEvent-based quit and
/ defers to that universal method.
*/
boolean result = base.handleQuit();
ae.setHandled(result);
} else {
throw new IllegalStateException("handleQuit: Base instance detached from listener");
} }
} }
public void handleReOpenApplication(ApplicationEvent arg0) {
}
} }

View File

@ -324,7 +324,7 @@
<bundledocument extensions="ino,c,cpp,h" <bundledocument extensions="ino,c,cpp,h"
icon="macosx/template.app/Contents/Resources/pde.icns" icon="macosx/template.app/Contents/Resources/pde.icns"
name="Arduino Source File" name="Arduino Source File"
role="Editor"> role="Editor" ispackage="false">
</bundledocument> </bundledocument>
</bundleapp> </bundleapp>