1 /*******************************************************************************
2 * Copyright (c) 2000, 2012 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 *******************************************************************************/
14 package org.eclipse.swt.widgets;
17 import org.eclipse.swt.*;
18 import org.eclipse.swt.internal.*;
19 import org.eclipse.swt.internal.win32.*;
22 * Instances of this class allow the user to navigate
23 * the file system and select or enter a file name.
25 * <dt><b>Styles:</b></dt>
26 * <dd>SAVE, OPEN, MULTI</dd>
27 * <dt><b>Events:</b></dt>
31 * Note: Only one of the styles SAVE and OPEN may be specified.
33 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
36 * @see <a href="http://www.eclipse.org/swt/snippets/#filedialog">FileDialog 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 FileDialog extends Dialog {
42 String [] filterNames = new String [0];
43 String [] filterExtensions = new String [0];
44 String [] fileNames = new String [0];
45 String filterPath = "", fileName = "";
47 boolean overwrite = false;
48 static final String FILTER = "*.*";
49 static int BUFFER_SIZE = 1024 * 32;
50 static boolean USE_HOOK = true;
53 * Feature in Vista. When OFN_ENABLEHOOK is set in the
54 * save or open file dialog, Vista uses the old XP look
55 * and feel. OFN_ENABLEHOOK is used to grow the file
56 * name buffer in a multi-select file dialog. The fix
57 * is to only use OFN_ENABLEHOOK when the buffer has
64 * Constructs a new instance of this class given only its parent.
66 * @param parent a shell which will be the parent of the new instance
68 * @exception IllegalArgumentException <ul>
69 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
71 * @exception SWTException <ul>
72 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
73 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
76 public FileDialog (Shell parent) {
77 this (parent, SWT.APPLICATION_MODAL);
81 * Constructs a new instance of this class given its parent
82 * and a style value describing its behavior and appearance.
84 * The style value is either one of the style constants defined in
85 * class <code>SWT</code> which is applicable to instances of this
86 * class, or must be built by <em>bitwise OR</em>'ing together
87 * (that is, using the <code>int</code> "|" operator) two or more
88 * of those <code>SWT</code> style constants. The class description
89 * lists the style constants that are applicable to the class.
90 * Style bits are also inherited from superclasses.
93 * @param parent a shell which will be the parent of the new instance
94 * @param style the style of dialog to construct
96 * @exception IllegalArgumentException <ul>
97 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
99 * @exception SWTException <ul>
100 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
101 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
108 public FileDialog (Shell parent, int style) {
109 super (parent, checkStyle (parent, style));
114 * Returns the path of the first file that was
115 * selected in the dialog relative to the filter path, or an
116 * empty string if no such file has been selected.
118 * @return the relative path of the file
120 public String getFileName () {
125 * Returns a (possibly empty) array with the paths of all files
126 * that were selected in the dialog relative to the filter path.
128 * @return the relative paths of the files
130 public String [] getFileNames () {
135 * Returns the file extensions which the dialog will
136 * use to filter the files it shows.
138 * @return the file extensions filter
140 public String [] getFilterExtensions () {
141 return filterExtensions;
145 * Get the 0-based index of the file extension filter
146 * which was selected by the user, or -1 if no filter
149 * This is an index into the FilterExtensions array and
150 * the FilterNames array.
153 * @return index the file extension filter index
155 * @see #getFilterExtensions
156 * @see #getFilterNames
160 public int getFilterIndex () {
165 * Returns the names that describe the filter extensions
166 * which the dialog will use to filter the files it shows.
168 * @return the list of filter names
170 public String [] getFilterNames () {
175 * Returns the directory path that the dialog will use, or an empty
176 * string if this is not set. File names in this path will appear
177 * in the dialog, filtered according to the filter extensions.
179 * @return the directory path string
181 * @see #setFilterExtensions
183 public String getFilterPath () {
188 * Returns the flag that the dialog will use to
189 * determine whether to prompt the user for file
190 * overwrite if the selected file already exists.
192 * @return true if the dialog will prompt for file overwrite, false otherwise
196 public boolean getOverwrite () {
200 long OFNHookProc (long hdlg, long uiMsg, long wParam, long lParam) {
201 switch ((int)uiMsg) {
203 OFNOTIFY ofn = new OFNOTIFY ();
204 OS.MoveMemory (ofn, lParam, OFNOTIFY.sizeof);
205 if (ofn.code == OS.CDN_SELCHANGE) {
206 int lResult = (int)OS.SendMessage (ofn.hwndFrom, OS.CDM_GETSPEC, 0, 0);
208 lResult += OS.MAX_PATH;
209 OPENFILENAME lpofn = new OPENFILENAME ();
210 OS.MoveMemory (lpofn, ofn.lpOFN, OPENFILENAME.sizeof);
211 if (lpofn.nMaxFile < lResult) {
212 long hHeap = OS.GetProcessHeap ();
213 long lpstrFile = OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, lResult * TCHAR.sizeof);
214 if (lpstrFile != 0) {
215 if (lpofn.lpstrFile != 0) OS.HeapFree (hHeap, 0, lpofn.lpstrFile);
216 lpofn.lpstrFile = lpstrFile;
217 lpofn.nMaxFile = lResult;
218 OS.MoveMemory (ofn.lpOFN, lpofn, OPENFILENAME.sizeof);
229 * Makes the dialog visible and brings it to the front
232 * @return a string describing the absolute path of the first selected file,
233 * or null if the dialog was cancelled or an error occurred
235 * @exception SWTException <ul>
236 * <li>ERROR_WIDGET_DISPOSED - if the dialog has been disposed</li>
237 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the dialog</li>
240 public String open () {
241 long hHeap = OS.GetProcessHeap ();
243 /* Get the owner HWND for the dialog */
244 long hwndOwner = parent.handle;
245 long hwndParent = parent.handle;
248 * Feature in Windows. There is no API to set the orientation of a
249 * file dialog. It is always inherited from the parent. The fix is
250 * to create a hidden parent and set the orientation in the hidden
251 * parent for the dialog to inherit.
253 boolean enabled = false;
254 int dialogOrientation = style & (SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT);
255 int parentOrientation = parent.style & (SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT);
256 if (dialogOrientation != parentOrientation) {
257 int exStyle = OS.WS_EX_NOINHERITLAYOUT;
258 if (dialogOrientation == SWT.RIGHT_TO_LEFT) exStyle |= OS.WS_EX_LAYOUTRTL;
259 hwndOwner = OS.CreateWindowEx (
264 OS.CW_USEDEFAULT, 0, OS.CW_USEDEFAULT, 0,
267 OS.GetModuleHandle (null),
269 enabled = OS.IsWindowEnabled (hwndParent);
270 if (enabled) OS.EnableWindow (hwndParent, false);
273 /* Convert the title and copy it into lpstrTitle */
274 if (title == null) title = "";
275 /* Use the character encoding for the default locale */
276 TCHAR buffer3 = new TCHAR (0, title, true);
277 int byteCount3 = buffer3.length () * TCHAR.sizeof;
278 long lpstrTitle = OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, byteCount3);
279 OS.MoveMemory (lpstrTitle, buffer3, byteCount3);
281 /* Compute filters and copy into lpstrFilter */
282 String strFilter = "";
283 if (filterNames == null) filterNames = new String [0];
284 if (filterExtensions == null) filterExtensions = new String [0];
285 for (int i=0; i<filterExtensions.length; i++) {
286 String filterName = filterExtensions [i];
287 if (i < filterNames.length) filterName = filterNames [i];
288 strFilter = strFilter + filterName + '\0' + filterExtensions [i] + '\0';
290 if (filterExtensions.length == 0) {
291 strFilter = strFilter + FILTER + '\0' + FILTER + '\0';
293 /* Use the character encoding for the default locale */
294 TCHAR buffer4 = new TCHAR (0, strFilter, true);
295 int byteCount4 = buffer4.length () * TCHAR.sizeof;
296 long lpstrFilter = OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, byteCount4);
297 OS.MoveMemory (lpstrFilter, buffer4, byteCount4);
299 /* Convert the fileName and filterName to C strings */
300 if (fileName == null) fileName = "";
301 /* Use the character encoding for the default locale */
302 TCHAR name = new TCHAR (0, fileName, true);
305 * Copy the name into lpstrFile and ensure that the
306 * last byte is NULL and the buffer does not overrun.
308 int nMaxFile = OS.MAX_PATH;
309 if ((style & SWT.MULTI) != 0) nMaxFile = Math.max (nMaxFile, BUFFER_SIZE);
310 int byteCount = nMaxFile * TCHAR.sizeof;
311 long lpstrFile = OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, byteCount);
312 int byteCountFile = Math.min (name.length () * TCHAR.sizeof, byteCount - TCHAR.sizeof);
313 OS.MoveMemory (lpstrFile, name, byteCountFile);
316 * Copy the path into lpstrInitialDir and ensure that
317 * the last byte is NULL and the buffer does not overrun.
319 if (filterPath == null) filterPath = "";
320 /* Use the character encoding for the default locale */
321 TCHAR path = new TCHAR (0, filterPath.replace ('/', '\\'), true);
322 int byteCount5 = OS.MAX_PATH * TCHAR.sizeof;
323 long lpstrInitialDir = OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, byteCount5);
324 int byteCountDir = Math.min (path.length () * TCHAR.sizeof, byteCount5 - TCHAR.sizeof);
325 OS.MoveMemory (lpstrInitialDir, path, byteCountDir);
327 /* Create the file dialog struct */
328 OPENFILENAME struct = new OPENFILENAME ();
329 struct.lStructSize = OPENFILENAME.sizeof;
330 struct.Flags = OS.OFN_HIDEREADONLY | OS.OFN_NOCHANGEDIR;
331 boolean save = (style & SWT.SAVE) != 0;
332 if (save && overwrite) struct.Flags |= OS.OFN_OVERWRITEPROMPT;
333 Callback callback = null;
334 if ((style & SWT.MULTI) != 0) {
335 struct.Flags |= OS.OFN_ALLOWMULTISELECT | OS.OFN_EXPLORER | OS.OFN_ENABLESIZING;
337 callback = new Callback (this, "OFNHookProc", 4); //$NON-NLS-1$
338 long lpfnHook = callback.getAddress ();
339 if (lpfnHook == 0) error (SWT.ERROR_NO_MORE_CALLBACKS);
340 struct.lpfnHook = lpfnHook;
341 struct.Flags |= OS.OFN_ENABLEHOOK;
344 struct.hwndOwner = hwndOwner;
345 struct.lpstrTitle = lpstrTitle;
346 struct.lpstrFile = lpstrFile;
347 struct.nMaxFile = nMaxFile;
348 struct.lpstrInitialDir = lpstrInitialDir;
349 struct.lpstrFilter = lpstrFilter;
350 struct.nFilterIndex = filterIndex == 0 ? filterIndex : filterIndex + 1;
353 * Set the default extension to an empty string. If the
354 * user fails to type an extension and this extension is
355 * empty, Windows uses the current value of the filter
356 * extension at the time that the dialog is closed.
358 long lpstrDefExt = 0;
360 lpstrDefExt = OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, TCHAR.sizeof);
361 struct.lpstrDefExt = lpstrDefExt;
364 /* Make the parent shell be temporary modal */
365 Dialog oldModal = null;
366 Display display = parent.getDisplay ();
367 if ((style & (SWT.APPLICATION_MODAL | SWT.SYSTEM_MODAL)) != 0) {
368 oldModal = display.getModalDialog ();
369 display.setModalDialog (this);
373 * Feature in Windows. For some reason, the WH_MSGFILTER filter
374 * does not run for GetSaveFileName() or GetOpenFileName(). The
375 * fix is to allow async messages to run in the WH_FOREGROUNDIDLE
378 boolean oldRunMessagesInIdle = display.runMessagesInIdle;
379 display.runMessagesInIdle = true;
380 display.externalEventLoop = true;
381 display.sendPreExternalEventDispatchEvent ();
383 * Open the dialog. If the open fails due to an invalid
384 * file name, use an empty file name and open it again.
386 boolean success = (save) ? OS.GetSaveFileName (struct) : OS.GetOpenFileName (struct);
387 display.externalEventLoop = false;
388 display.sendPostExternalEventDispatchEvent ();
389 switch (OS.CommDlgExtendedError ()) {
390 case OS.FNERR_INVALIDFILENAME:
391 OS.MoveMemory (lpstrFile, new char [1], TCHAR.sizeof);
392 display.externalEventLoop = true;
393 display.sendPreExternalEventDispatchEvent ();
394 success = (save) ? OS.GetSaveFileName (struct) : OS.GetOpenFileName (struct);
395 display.externalEventLoop = false;
396 display.sendPostExternalEventDispatchEvent ();
398 case OS.FNERR_BUFFERTOOSMALL:
402 display.runMessagesInIdle = oldRunMessagesInIdle;
404 /* Clear the temporary dialog modal parent */
405 if ((style & (SWT.APPLICATION_MODAL | SWT.SYSTEM_MODAL)) != 0) {
406 display.setModalDialog (oldModal);
409 /* Dispose the callback and reassign the buffer */
410 if (callback != null) callback.dispose ();
411 lpstrFile = struct.lpstrFile;
413 /* Set the new path, file name and filter */
414 fileNames = new String [0];
415 String fullPath = null;
417 char [] buffer = new char [struct.nMaxFile];
418 OS.MoveMemory (buffer, lpstrFile, buffer.length * TCHAR.sizeof);
419 int nFileOffset = struct.nFileOffset;
420 if (nFileOffset > 0) {
421 filterPath = new String (buffer, 0, nFileOffset - 1);
424 * Get each file from the buffer. Files are delimited
425 * by a NULL character with 2 NULL characters at the end.
428 fileNames = new String [(style & SWT.MULTI) != 0 ? 4 : 1];
429 int start = nFileOffset;
432 while (end < buffer.length && buffer [end] != 0) end++;
433 String string = new String (buffer, start, end - start);
435 if (count == fileNames.length) {
436 String [] newFileNames = new String [fileNames.length + 4];
437 System.arraycopy (fileNames, 0, newFileNames, 0, fileNames.length);
438 fileNames = newFileNames;
440 fileNames [count++] = string;
441 if ((style & SWT.MULTI) == 0) break;
442 } while (start < buffer.length && buffer[start] != 0);
444 if (fileNames.length > 0) fileName = fileNames [0];
445 String separator = "";
446 int length = filterPath.length ();
447 if (length > 0 && filterPath.charAt (length - 1) != '\\') {
450 fullPath = filterPath + separator + fileName;
451 if (count < fileNames.length) {
452 String [] newFileNames = new String [count];
453 System.arraycopy (fileNames, 0, newFileNames, 0, count);
454 fileNames = newFileNames;
457 filterIndex = struct.nFilterIndex - 1;
460 /* Free the memory that was allocated. */
461 OS.HeapFree (hHeap, 0, lpstrFile);
462 OS.HeapFree (hHeap, 0, lpstrFilter);
463 OS.HeapFree (hHeap, 0, lpstrInitialDir);
464 OS.HeapFree (hHeap, 0, lpstrTitle);
465 if (lpstrDefExt != 0) OS.HeapFree (hHeap, 0, lpstrDefExt);
467 /* Destroy the BIDI orientation window */
468 if (hwndParent != hwndOwner) {
469 if (enabled) OS.EnableWindow (hwndParent, true);
470 OS.SetActiveWindow (hwndParent);
471 OS.DestroyWindow (hwndOwner);
475 * This code is intentionally commented. On some
476 * platforms, the owner window is repainted right
477 * away when a dialog window exits. This behavior
478 * is currently unspecified.
480 // if (hwndOwner != 0) OS.UpdateWindow (hwndOwner);
482 /* Answer the full path or null */
487 * Set the initial filename which the dialog will
488 * select by default when opened to the argument,
489 * which may be null. The name will be prefixed with
490 * the filter path when one is supplied.
492 * @param string the file name
494 public void setFileName (String string) {
499 * Set the file extensions which the dialog will
500 * use to filter the files it shows to the argument,
503 * The strings are platform specific. For example, on
504 * some platforms, an extension filter string is typically
505 * of the form "*.extension", where "*.*" matches all files.
506 * For filters with multiple extensions, use semicolon as
507 * a separator, e.g. "*.jpg;*.png".
510 * Note: On Mac, setting the file extension filter affects how
511 * app bundles are treated by the dialog. When a filter extension
512 * having the app extension (.app) is selected, bundles are treated
513 * as files. For all other extension filters, bundles are treated
514 * as directories. When no filter extension is set, bundles are
518 * @param extensions the file extension filter
520 * @see #setFilterNames to specify the user-friendly
521 * names corresponding to the extensions
523 public void setFilterExtensions (String [] extensions) {
524 filterExtensions = extensions;
528 * Set the 0-based index of the file extension filter
529 * which the dialog will use initially to filter the files
530 * it shows to the argument.
532 * This is an index into the FilterExtensions array and
533 * the FilterNames array.
536 * @param index the file extension filter index
538 * @see #setFilterExtensions
539 * @see #setFilterNames
543 public void setFilterIndex (int index) {
548 * Sets the names that describe the filter extensions
549 * which the dialog will use to filter the files it shows
550 * to the argument, which may be null.
552 * Each name is a user-friendly short description shown for
553 * its corresponding filter. The <code>names</code> array must
554 * be the same length as the <code>extensions</code> array.
557 * @param names the list of filter names, or null for no filter names
559 * @see #setFilterExtensions
561 public void setFilterNames (String [] names) {
566 * Sets the directory path that the dialog will use
567 * to the argument, which may be null. File names in this
568 * path will appear in the dialog, filtered according
569 * to the filter extensions. If the string is null,
570 * then the operating system's default filter path
573 * Note that the path string is platform dependent.
574 * For convenience, either '/' or '\' can be used
575 * as a path separator.
578 * @param string the directory path
580 * @see #setFilterExtensions
582 public void setFilterPath (String string) {
587 * Sets the flag that the dialog will use to
588 * determine whether to prompt the user for file
589 * overwrite if the selected file already exists.
591 * @param overwrite true if the dialog will prompt for file overwrite, false otherwise
595 public void setOverwrite (boolean overwrite) {
596 this.overwrite = overwrite;