1 /*******************************************************************************
2 * Copyright (c) 2000, 2017 IBM Corporation and others.
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
9 * SPDX-License-Identifier: EPL-2.0
12 * IBM Corporation - initial API and implementation
13 * Martin Karpisek <martin.karpisek@gmail.com> - Bug 443250
14 *******************************************************************************/
15 package org.eclipse.swt.widgets;
18 import org.eclipse.swt.*;
19 import org.eclipse.swt.internal.*;
20 import org.eclipse.swt.internal.ole.win32.*;
21 import org.eclipse.swt.internal.win32.*;
24 * Instances of this class allow the user to navigate
25 * the file system and select a directory.
27 * <dt><b>Styles:</b></dt>
29 * <dt><b>Events:</b></dt>
33 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
36 * @see <a href="http://www.eclipse.org/swt/snippets/#directorydialog">DirectoryDialog snippets</a>
37 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample, Dialog tab</a>
38 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
39 * @noextend This class is not intended to be subclassed by clients.
41 public class DirectoryDialog extends Dialog {
42 String message = "", filterPath = ""; //$NON-NLS-1$//$NON-NLS-2$
46 * Constructs a new instance of this class given only its parent.
48 * @param parent a shell which will be the parent of the new instance
50 * @exception IllegalArgumentException <ul>
51 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
53 * @exception SWTException <ul>
54 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
55 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
58 public DirectoryDialog (Shell parent) {
59 this (parent, SWT.APPLICATION_MODAL);
63 * Constructs a new instance of this class given its parent
64 * and a style value describing its behavior and appearance.
66 * The style value is either one of the style constants defined in
67 * class <code>SWT</code> which is applicable to instances of this
68 * class, or must be built by <em>bitwise OR</em>'ing together
69 * (that is, using the <code>int</code> "|" operator) two or more
70 * of those <code>SWT</code> style constants. The class description
71 * lists the style constants that are applicable to the class.
72 * Style bits are also inherited from superclasses.
75 * @param parent a shell which will be the parent of the new instance
76 * @param style the style of dialog to construct
78 * @exception IllegalArgumentException <ul>
79 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
81 * @exception SWTException <ul>
82 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
83 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
86 public DirectoryDialog (Shell parent, int style) {
87 super (parent, checkStyle (parent, style));
91 long BrowseCallbackProc (long hwnd, long uMsg, long lParam, long lpData) {
93 case OS.BFFM_INITIALIZED:
94 if (filterPath != null && filterPath.length () != 0) {
95 /* Use the character encoding for the default locale */
96 TCHAR buffer = new TCHAR (0, filterPath.replace ('/', '\\'), true);
97 OS.SendMessage (hwnd, OS.BFFM_SETSELECTION, 1, buffer);
99 if (title != null && title.length () != 0) {
100 /* Use the character encoding for the default locale */
101 TCHAR buffer = new TCHAR (0, title, true);
102 OS.SetWindowText (hwnd, buffer);
105 case OS.BFFM_VALIDATEFAILED:
106 /* Use the character encoding for the default locale */
107 int length = OS.wcslen (lParam);
108 TCHAR buffer = new TCHAR (0, length);
109 int byteCount = buffer.length () * TCHAR.sizeof;
110 OS.MoveMemory (buffer, lParam, byteCount);
111 directoryPath = buffer.toString (0, length);
118 * Returns the path which the dialog will use to filter
119 * the directories it shows.
121 * @return the filter path
123 * @see #setFilterPath
125 public String getFilterPath () {
130 * Returns the dialog's message, which is a description of
131 * the purpose for which it was opened. This message will be
132 * visible on the dialog while it is open.
134 * @return the message
136 public String getMessage () {
141 * Makes the dialog visible and brings it to the front
144 * @return a string describing the absolute path of the selected directory,
145 * or null if the dialog was cancelled or an error occurred
147 * @exception SWTException <ul>
148 * <li>ERROR_WIDGET_DISPOSED - if the dialog has been disposed</li>
149 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the dialog</li>
152 public String open() {
153 if (OS.WIN32_VERSION >= OS.VERSION (6, 0)) {
154 return openCommonItemDialog();
156 return openCommonFileDialog();
159 private String openCommonFileDialog () {
160 long hHeap = OS.GetProcessHeap ();
162 /* Get the owner HWND for the dialog */
164 if (parent != null) hwndOwner = parent.handle;
166 /* Copy the message to OS memory */
168 if (message.length () != 0) {
169 String string = message;
170 if (string.indexOf ('&') != -1) {
171 int length = string.length ();
172 char [] buffer = new char [length * 2];
174 for (int i=0; i<length; i++) {
175 char ch = string.charAt (i);
176 if (ch == '&') buffer [index++] = '&';
177 buffer [index++] = ch;
179 string = new String (buffer, 0, index);
181 /* Use the character encoding for the default locale */
182 TCHAR buffer = new TCHAR (0, string, true);
183 int byteCount = buffer.length () * TCHAR.sizeof;
184 lpszTitle = OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, byteCount);
185 OS.MoveMemory (lpszTitle, buffer, byteCount);
188 /* Create the BrowseCallbackProc */
189 Callback callback = new Callback (this, "BrowseCallbackProc", 4); //$NON-NLS-1$
190 long lpfn = callback.getAddress ();
191 if (lpfn == 0) error (SWT.ERROR_NO_MORE_CALLBACKS);
193 /* Make the parent shell be temporary modal */
194 Dialog oldModal = null;
195 Display display = parent.getDisplay ();
196 if ((style & (SWT.APPLICATION_MODAL | SWT.SYSTEM_MODAL)) != 0) {
197 oldModal = display.getModalDialog ();
198 display.setModalDialog (this);
201 directoryPath = null;
202 BROWSEINFO lpbi = new BROWSEINFO ();
203 lpbi.hwndOwner = hwndOwner;
204 lpbi.lpszTitle = lpszTitle;
205 lpbi.ulFlags = OS.BIF_NEWDIALOGSTYLE | OS.BIF_RETURNONLYFSDIRS | OS.BIF_EDITBOX | OS.BIF_VALIDATE;
208 * Bug in Windows. On some hardware configurations, SHBrowseForFolder()
209 * causes warning dialogs with the message "There is no disk in the drive
210 * Please insert a disk into \Device\Harddisk0\DR0". This is possibly
211 * caused by SHBrowseForFolder() calling internally GetVolumeInformation().
212 * MSDN for GetVolumeInformation() says:
214 * "If you are attempting to obtain information about a floppy drive
215 * that does not have a floppy disk or a CD-ROM drive that does not
216 * have a compact disc, the system displays a message box asking the
217 * user to insert a floppy disk or a compact disc, respectively.
218 * To prevent the system from displaying this message box, call the
219 * SetErrorMode function with SEM_FAILCRITICALERRORS."
221 * The fix is to save and restore the error mode using SetErrorMode()
222 * with the SEM_FAILCRITICALERRORS flag around SHBrowseForFolder().
224 int oldErrorMode = OS.SetErrorMode (OS.SEM_FAILCRITICALERRORS);
226 display.sendPreExternalEventDispatchEvent ();
227 display.externalEventLoop = true;
228 long lpItemIdList = OS.SHBrowseForFolder (lpbi);
229 display.externalEventLoop = false;
230 display.sendPostExternalEventDispatchEvent ();
231 OS.SetErrorMode (oldErrorMode);
233 /* Clear the temporary dialog modal parent */
234 if ((style & (SWT.APPLICATION_MODAL | SWT.SYSTEM_MODAL)) != 0) {
235 display.setModalDialog (oldModal);
238 boolean success = lpItemIdList != 0;
240 /* Use the character encoding for the default locale */
241 TCHAR buffer = new TCHAR (0, OS.MAX_PATH);
242 if (OS.SHGetPathFromIDList (lpItemIdList, buffer)) {
243 directoryPath = buffer.toString (0, buffer.strlen ());
244 filterPath = directoryPath;
248 /* Free the BrowseCallbackProc */
251 /* Free the OS memory */
252 if (lpszTitle != 0) OS.HeapFree (hHeap, 0, lpszTitle);
254 /* Free the pointer to the ITEMIDLIST */
255 long [] ppMalloc = new long [1];
256 if (OS.SHGetMalloc (ppMalloc) == OS.S_OK) {
257 /* void Free (struct IMalloc *this, void *pv); */
258 COM.VtblCall (5, ppMalloc [0], lpItemIdList);
262 * This code is intentionally commented. On some
263 * platforms, the owner window is repainted right
264 * away when a dialog window exits. This behavior
265 * is currently unspecified.
267 // if (hwndOwner != 0) OS.UpdateWindow (hwndOwner);
269 /* Return the directory path */
270 if (!success) return null;
271 return directoryPath;
274 private String openCommonItemDialog() {
275 this.directoryPath = null;
277 long [] ppv = new long [1];
278 if (COM.CoCreateInstance(COM.CLSID_FileOpenDialog, 0, COM.CLSCTX_INPROC_SERVER, COM.IID_IFileOpenDialog, ppv) == OS.S_OK) {
279 IFileDialog fileDialog = new IFileDialog(ppv[0]);
281 int[] options = new int[1];
282 if (fileDialog.GetOptions(options) == OS.S_OK) {
283 options[0] |= OS.FOS_PICKFOLDERS | OS.FOS_FORCEFILESYSTEM | OS.FOS_NOCHANGEDIR;
284 fileDialog.SetOptions(options[0]);
287 if (title == null) title = "";
288 if (title.length() > 0) {
289 char[] buffer = new char[title.length() + 1];
290 title.getChars(0, title.length(), buffer, 0);
291 fileDialog.SetTitle(buffer);
294 if (filterPath != null && filterPath.length() > 0) {
295 String path = filterPath.replace('/', '\\');
296 char[] buffer = new char[path.length() + 1];
297 path.getChars(0, path.length(), buffer, 0);
298 if (COM.SHCreateItemFromParsingName(buffer, 0, COM.IID_IShellItem, ppv) == OS.S_OK) {
299 IShellItem psi = new IShellItem(ppv[0]);
301 * SetDefaultDirectory does not work if the dialog has
302 * persisted recently used folder. The fix is to clear the
305 fileDialog.ClearClientData();
306 fileDialog.SetDefaultFolder(psi);
311 Display display = parent.getDisplay();
312 long hwndOwner = parent.handle;
313 display.externalEventLoop = true;
314 if (fileDialog.Show(hwndOwner) == OS.S_OK) {
315 if (fileDialog.GetResult(ppv) == OS.S_OK) {
316 IShellItem psi = new IShellItem(ppv[0]);
317 if (psi.GetDisplayName(OS.SIGDN_FILESYSPATH, ppv) == OS.S_OK) {
319 int length = OS.wcslen(wstr);
320 char[] buffer = new char[length];
321 OS.MoveMemory(buffer, wstr, length * 2);
322 OS.CoTaskMemFree(wstr);
324 directoryPath = new String(buffer);
329 display.externalEventLoop = false;
331 fileDialog.Release();
334 return directoryPath;
338 * Sets the path that the dialog will use to filter
339 * the directories it shows to the argument, which may
340 * be null. If the string is null, then the operating
341 * system's default filter path will be used.
343 * Note that the path string is platform dependent.
344 * For convenience, either '/' or '\' can be used
345 * as a path separator.
348 * @param string the filter path
350 public void setFilterPath (String string) {
355 * Sets the dialog's message, which is a description of
356 * the purpose for which it was opened. This message will be
357 * visible on the dialog while it is open.
359 * NOTE: This operation is a hint and is not supported on some platforms. For
360 * example, on Windows (Vista and later), the <code>DirectoryDialog</code>
361 * doesn't have any provision to set a message.
364 * @param string the message
366 * @exception IllegalArgumentException <ul>
367 * <li>ERROR_NULL_ARGUMENT - if the string is null</li>
370 public void setMessage (String string) {
371 if (string == null) error (SWT.ERROR_NULL_ARGUMENT);