From 82a14c55c627c9189137fff7f9ba5999204ea4ea Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Wed, 14 Jan 2015 20:47:46 +0000 Subject: [PATCH] * refactor NSOpenPanel code in the hopes of fixing random deadlock issues when NSOpenPanel is shown (somehow only affects a very small number of users) --- source/net/filebot/UserFiles.java | 23 +- source/net/filebot/mac/MacAppUtilities.java | 39 ++ source/net/filebot/mac/NativeFileDialog.java | 580 ------------------- 3 files changed, 42 insertions(+), 600 deletions(-) delete mode 100644 source/net/filebot/mac/NativeFileDialog.java diff --git a/source/net/filebot/UserFiles.java b/source/net/filebot/UserFiles.java index 79f182cc..6106e51f 100644 --- a/source/net/filebot/UserFiles.java +++ b/source/net/filebot/UserFiles.java @@ -16,13 +16,11 @@ import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import javax.swing.JFileChooser; import net.filebot.mac.MacAppUtilities; -import net.filebot.mac.NativeFileDialog; import net.filebot.util.FileUtilities.ExtensionFileFilter; public class UserFiles { @@ -211,28 +209,13 @@ public class UserFiles { // NSOpenPanel causes deadlocks on some machines Preferences persistence = Preferences.userNodeForPackage(UserFiles.class); if (!persistence.getBoolean(KEY_NSOPENPANEL_BROKEN, false)) { - // assume that NSOpenPanel may freeze the application until it is killed, and make sure to not use NSOpenPanel on subsequent runs try { + // assume that NSOpenPanel may freeze the application until it is killed, and make sure to not use NSOpenPanel on subsequent runs persistence.putBoolean(KEY_NSOPENPANEL_BROKEN, true); persistence.flush(); - } catch (BackingStoreException e) { - Logger.getLogger(UserFiles.class.getName()).log(Level.WARNING, e.toString(), e); - } - try { - NativeFileDialog nsOpenPanel = new NativeFileDialog(title, FileDialog.LOAD); - nsOpenPanel.setMultipleMode(true); - nsOpenPanel.setCanChooseDirectories(true); - nsOpenPanel.setCanChooseFiles(true); - if (!filter.acceptAny()) { - nsOpenPanel.setAllowedFileTypes(asList(filter.extensions())); - } - nsOpenPanel.setVisible(true); - if (!nsOpenPanel.isCancelled()) { - return asList(nsOpenPanel.getFiles()); - } else { - return emptyList(); - } + // call native NSOpenPanel openPanel via Objective-C bridge + return MacAppUtilities.NSOpenPanel_openPanel_runModal(title, true, true, true, filter.acceptAny() ? filter.extensions() : null); } catch (Throwable e) { Logger.getLogger(UserFiles.class.getName()).log(Level.WARNING, e.toString()); } finally { diff --git a/source/net/filebot/mac/MacAppUtilities.java b/source/net/filebot/mac/MacAppUtilities.java index 6fef02ae..2513faf1 100644 --- a/source/net/filebot/mac/MacAppUtilities.java +++ b/source/net/filebot/mac/MacAppUtilities.java @@ -1,15 +1,20 @@ package net.filebot.mac; +import static ca.weblite.objc.util.CocoaUtils.*; + import java.awt.Window; import java.io.File; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.UIManager; import ca.weblite.objc.Client; +import ca.weblite.objc.Proxy; public class MacAppUtilities { @@ -34,6 +39,40 @@ public class MacAppUtilities { return objc().sendProxy("NSURL", "URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error:", NSData_initWithBase64Encoding(text), 1024, null, false, null).send("startAccessingSecurityScopedResource"); } + public static List NSOpenPanel_openPanel_runModal(String title, boolean multipleMode, boolean canChooseDirectories, boolean canChooseFiles, String[] allowedFileTypes) { + List result = new ArrayList(); + dispatch_sync(new Runnable() { + + @Override + public void run() { + Proxy peer = objc().sendProxy("NSOpenPanel", "openPanel"); + peer.send("setTitle:", title); + peer.send("setAllowsMultipleSelection:", multipleMode ? 1 : 0); + peer.send("setCanChooseDirectories:", canChooseDirectories ? 1 : 0); + peer.send("setCanChooseFiles:", canChooseFiles ? 1 : 0); + + if (allowedFileTypes != null) { + Proxy mutableArray = objc().sendProxy("NSMutableArray", "arrayWithCapacity:", allowedFileTypes.length); + for (String type : allowedFileTypes) { + mutableArray.send("addObject:", type); + } + peer.send("setAllowedFileTypes:", mutableArray); + } + + if (peer.sendInt("runModal") != 0) { + Proxy nsArray = peer.getProxy("URLs"); + int size = nsArray.sendInt("count"); + for (int i = 0; i < size; i++) { + Proxy url = nsArray.sendProxy("objectAtIndex:", i); + String path = url.sendString("path"); + result.add(new File(path)); + } + } + } + }); + return result; + } + public static void setWindowCanFullScreen(Window window) { try { Class fullScreenUtilities = Class.forName("com.apple.eawt.FullScreenUtilities"); diff --git a/source/net/filebot/mac/NativeFileDialog.java b/source/net/filebot/mac/NativeFileDialog.java deleted file mode 100644 index e7479a6a..00000000 --- a/source/net/filebot/mac/NativeFileDialog.java +++ /dev/null @@ -1,580 +0,0 @@ -package net.filebot.mac; - -import static ca.weblite.objc.RuntimeUtils.*; -import static ca.weblite.objc.util.CocoaUtils.*; - -import java.awt.FileDialog; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import ca.weblite.objc.NSObject; -import ca.weblite.objc.Proxy; - -import com.sun.jna.Pointer; - -/** - * A wrapper around NSSavePanel and NSOpenPanel with some methods similar to java.awt.FileDialog. - * - *

- * This class includes wrappers for most settings of both NSSavePanel and NSOpen panel so that you have full flexibility (e.g. select directories only, files only, force a certain extension, allow user to add folders, show hidden files, etc... - *

- * - *

Example Save Panel

- *  NativeFileDialog dlg = new NativeFileDialog("Save as...", FileDialog.SAVE);
-    dlg.setMessage("Will save only as .txt file");
-    dlg.setExtensionHidden(true);
-    dlg.setAllowedFileTypes(Arrays.asList("txt"));
-    dlg.setVisible(true);  // this is modal
-
-    String f = dlg.getFile();
-    if ( f == null ){
-        return;
-    }
-    File file = new File(f);
-    saveFile(file);
- * 
- * - *

Example Open Panel

- * - *
- *  NativeFileDialog dlg = new NativeFileDialog("Select file to open", FileDialog.LOAD);
-    dlg.setVisible(true); // this is modal.
-    String f = dlg.getFile();
-    if ( f != null ){
-        openFile(new File(f));
-    }
- * 
- * - * @author shannah - * @see NSSavePanel Class Reference - * @see NSOpenPanel Class Reference - * @see java.awt.FileDialog API docs - */ -public class NativeFileDialog extends NSObject { - - /** - * Reference to the Obj-C NSOpenPanel or NSSavePanel class. - */ - private Proxy peer; - - /** - * Either FileDialog.LOAD or FileDialog.SAVE - */ - private int mode; - - /** - * Creates a new file dialog with the specified title and mode. - * - * @param title - * The title for the dialog. - * @param mode - * Whether to be an open panel or save panel. Either java.awt.FileDialog.SAVE or java.awt.FileDialog.LOAD - */ - public NativeFileDialog(final String title, final int mode) { - super("NSObject"); - this.mode = mode; - dispatch_sync(new Runnable() { - - @Override - public void run() { - if (mode == FileDialog.LOAD) { - peer = getClient().sendProxy("NSOpenPanel", "openPanel"); - } else { - peer = getClient().sendProxy("NSSavePanel", "savePanel"); - } - peer.send("setTitle:", title); - peer.send("retain"); - } - - }); - } - - /** - * Sets a given selector with a string value on the main thread. - * - * @param selector - * An objective-c selector on the NSSavePanel object. e.g. "setTitle:" - * @param value - * The value to set the selector. - */ - private void set(final String selector, final String value) { - dispatch_sync(new Runnable() { - - @Override - public void run() { - if (peer.getPeer().equals(Pointer.NULL)) { - - throw new RuntimeException("The peer is null"); - } - peer.send(sel(selector), value); - } - - }); - } - - /** - * Sets a given selector with an int value on the main thread. - * - * @param selector - * An objective-c selector on the NSSavePanel object. e.g. "setShowsHiddenFiles:" - * @param value - * The int value to set. - */ - private void set(final String selector, final int value) { - dispatch_sync(new Runnable() { - - @Override - public void run() { - peer.send(selector, value); - } - - }); - } - - /** - * Returns the result of a selector on the main thread for the NSSavePanel object. - * - * @param selector - * The selector to be run. e.g. "title". - * @return The result of calling the given selector on the NSSavePanel object. - */ - private String getString(final String selector) { - final String[] out = new String[1]; - dispatch_sync(new Runnable() { - - @Override - public void run() { - out[0] = peer.sendString(selector); - } - - }); - return out[0]; - } - - /** - * Returns the result of running a selector on the NSSavePanel on the main thread. - * - * @param selector - * The selector to be run. E.g. "showsHiddenFiles" - * @return The int result. - */ - private int getI(final String selector) { - final int[] out = new int[1]; - dispatch_sync(new Runnable() { - - @Override - public void run() { - out[0] = peer.sendInt(sel(selector)); - } - - }); - return out[0]; - } - - /** - * Sets title of the dialog. - * - * @param title - * The title. - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - */ - public void setTitle(String title) { - set("setTitle:", title); - } - - /** - * Gets the title of the dialog. - * - * @return The title - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - */ - public String getTitle() { - return getString("title"); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @param prompt - */ - public void setPrompt(String prompt) { - set("setPrompt:", prompt); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @param label - */ - public void setNameFieldLabel(String label) { - set("setNameFieldLabel:", label); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @return - */ - public String getNameFieldLabel() { - return getString("nameFieldLabel"); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @param message - */ - public void setMessage(String message) { - set("setMessage:", message); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @return - */ - public String getMessage() { - return getString("message"); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @return - */ - public String getPrompt() { - return getString("prompt"); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @return - */ - public boolean canCreateDirectories() { - return getI("canCreateDirectories") != 0; - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @param can - */ - public void setCanCreateDirectories(boolean can) { - set("setCanCreateDirectories:", can ? 1 : 0); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @return - */ - public boolean showsHiddenFiles() { - return getI("showsHiddenFiles") != 0; - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @param shows - */ - public void setShowsHiddenFiles(boolean shows) { - set("setShowsHiddenFiles:", shows ? 1 : 0); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @return - */ - public boolean isExtensionHidden() { - return getI("isExtensionHidden") != 0; - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @param hidden - */ - public void setExtensionHidden(boolean hidden) { - set("setExtensionHidden:", hidden ? 1 : 0); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @return - */ - public boolean canSelectHiddenExtension() { - return getI("canSelectHiddenExtension") != 0; - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @param sel - */ - public void setCanSelectHiddenExtension(boolean sel) { - set("setCanSelectHiddenExtension:", sel ? 1 : 0); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @param val - */ - public void setNameFieldStringValue(String val) { - set("setNameFieldStringValue:", val); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @return - */ - public List getAllowedFileTypes() { - final List out = new ArrayList(); - dispatch_sync(new Runnable() { - - @Override - public void run() { - Proxy types = peer.sendProxy("allowedFileTypes"); - int size = types.getInt("count"); - for (int i = 0; i < size; i++) { - String nex = types.sendString("objectAtIndex:", i); - out.add(nex); - } - } - - }); - - return out; - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @param types - */ - public void setAllowedFileTypes(final List types) { - dispatch_sync(new Runnable() { - - @Override - public void run() { - Proxy mutableArray = getClient().sendProxy("NSMutableArray", "arrayWithCapacity:", types.size()); - - for (String type : types) { - - mutableArray.send("addObject:", type); - - } - - peer.send("setAllowedFileTypes:", mutableArray); - - // mutableArray.send("release"); - } - - }); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @return - */ - public boolean allowsOtherFileTypes() { - return getI("allowsOtherFileTypes") != 0; - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @param allowed - */ - public void setAllowsOtherFileTypes(boolean allowed) { - set("setAllowsOtherFileTypes:", allowed ? 1 : 0); - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @return - */ - public boolean getTreatsFilePackagesAsDirectories() { - return getI("treatsFilePackagesAsDirectories") != 0; - } - - /** - * @see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/nssavepanel_Class/Reference/Reference.html - * @param treat - */ - public void setTreatsFilePackagesAsDirectories(boolean treat) { - set("setTreatsFilePackagesAsDirectories:", treat ? 1 : 0); - } - - /** - * Returns the path to the directory that was selected. - * - * @return - */ - public String getDirectory() { - final String[] out = new String[1]; - dispatch_sync(new Runnable() { - - @Override - public void run() { - Proxy dirUrl = peer.sendProxy("directoryURL"); - if (dirUrl.getPeer().equals(Pointer.NULL)) { - out[0] = null; - } else { - out[0] = dirUrl.sendString("path"); - } - - } - - }); - return out[0]; - } - - /** - * Gets the path to the file that was selected. - * - * @return - */ - public String getFile() { - final String[] out = new String[1]; - dispatch_sync(new Runnable() { - - @Override - public void run() { - Proxy fileUrl = peer.sendProxy("URL"); - if (fileUrl == null || fileUrl.getPeer().equals(Pointer.NULL)) { - out[0] = null; - } else { - out[0] = fileUrl.sendString("path"); - } - } - - }); - return out[0]; - } - - /** - * Returns an array of files that were selected by the user. - * - * @return - */ - public File[] getFiles() { - final List out = new ArrayList(); - dispatch_sync(new Runnable() { - - @Override - public void run() { - if (mode == FileDialog.LOAD) { - Proxy nsArray = peer.getProxy("URLs"); - if (!nsArray.getPeer().equals(Pointer.NULL)) { - int size = nsArray.sendInt("count"); - for (int i = 0; i < size; i++) { - Proxy url = nsArray.sendProxy("objectAtIndex:", i); - String path = url.sendString("path"); - out.add(new File(path)); - } - } - } - } - - }); - return out.toArray(new File[0]); - } - - /** - * Returns the mode of this dialog. - * - * @return either FileDialog.LOAD or FileDialog.SAVE - */ - public int getMode() { - return mode; - } - - /** - * Returns true if the dialog allows multiple selection. - * - * @return - */ - public boolean isMultipleMode() { - return getI("allowsMultipleSelection") != 0; - } - - /** - * Sets whether the dialog allows the user to select multiple files or not. - * - * @param enable - */ - public void setMultipleMode(boolean enable) { - set("setAllowsMultipleSelection:", enable ? 1 : 0); - } - - /** - * Returns whether the user can select files in this dialog. - * - * @return - */ - public boolean canChooseFiles() { - return getI("canChooseFiles") != 0; - } - - /** - * Sets whether the user can select files in this dialog. - * - * @param can - */ - public void setCanChooseFiles(boolean can) { - set("setCanChooseFiles:", can ? 1 : 0); - } - - public boolean getCanChooseDirectories() { - return getI("canChooseDirectories") != 0; - } - - public void setCanChooseDirectories(boolean can) { - set("setCanChooseDirectories:", can ? 1 : 0); - } - - public boolean getResolvesAliases() { - return getI("resolvesAliases") != 0; - } - - public void setResolvesAliases(boolean resolves) { - set("setResolvesAliases:", resolves ? 1 : 0); - } - - /** - * Sets the directory that the dialog displays. - * - * @param dir - */ - public void setDirectory(final String dir) { - dispatch_sync(new Runnable() { - - @Override - public void run() { - Proxy url = getClient().sendProxy("NSURL", "fileURLWithPath:isDirectory:", dir, 1); - peer.send("setDirectoryURL:", url.getPeer()); - } - - }); - } - - @Override - protected void finalize() throws Throwable { - super.finalize(); - } - - public void setVisible(boolean visible) { - dispatch_sync(new Runnable() { - - @Override - public void run() { - int result = peer.sendInt("runModal"); - nsFileHandlingButton.set(result); - } - - }); - } - - public int getResult() { - return nsFileHandlingButton.get(); - } - - public boolean isCancelled() { - return nsFileHandlingButton.get() == NSFileHandlingPanelCancelButton; - } - - private final AtomicInteger nsFileHandlingButton = new AtomicInteger(NSFileHandlingPanelCancelButton); - - public static final int NSFileHandlingPanelOKButton = 1; - public static final int NSFileHandlingPanelCancelButton = 0; - -}