changeset 2280:2ba381560071

6705345: Enable multiple file selection in AWT FileDialog Reviewed-by: art, anthony, alexp
author dcherepanov
date Fri, 12 Feb 2010 19:58:10 +0300
parents 66c193082586
children d6d2de6ee2d1
files src/share/classes/java/awt/FileDialog.java src/share/classes/sun/awt/AWTAccessor.java src/solaris/classes/sun/awt/X11/XFileDialogPeer.java src/windows/classes/sun/awt/windows/WFileDialogPeer.java src/windows/native/sun/windows/awt_FileDialog.cpp src/windows/native/sun/windows/awt_FileDialog.h test/java/awt/FileDialog/MultipleMode/MultipleMode.html test/java/awt/FileDialog/MultipleMode/MultipleMode.java
diffstat 8 files changed, 639 insertions(+), 44 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/awt/FileDialog.java	Mon Feb 08 17:02:43 2010 +0300
+++ b/src/share/classes/java/awt/FileDialog.java	Fri Feb 12 19:58:10 2010 +0300
@@ -28,6 +28,8 @@
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.ObjectInputStream;
+import java.io.File;
+import sun.awt.AWTAccessor;
 
 /**
  * The <code>FileDialog</code> class displays a dialog window
@@ -93,6 +95,25 @@
      */
     String file;
 
+    /**
+     * Contains the File instances for all the files that the user selects.
+     *
+     * @serial
+     * @see getFiles
+     * @since 1.7
+     */
+    private File[] files;
+
+    /**
+     * Represents whether the file dialog allows the multiple file selection.
+     *
+     * @serial
+     * @see #setMultipleMode
+     * @see #isMultipleMode
+     * @since 1.7
+     */
+    private boolean multipleMode = false;
+
     /*
      * The filter used as the file dialog's filename filter.
      * The file dialog will only be displaying files whose
@@ -123,6 +144,26 @@
         }
     }
 
+    static {
+        AWTAccessor.setFileDialogAccessor(
+            new AWTAccessor.FileDialogAccessor() {
+                public void setFiles(FileDialog fileDialog, String directory, String files[]) {
+                    fileDialog.setFiles(directory, files);
+                }
+                public void setFile(FileDialog fileDialog, String file) {
+                    fileDialog.file = ("".equals(file)) ? null : file;
+                }
+                public void setDirectory(FileDialog fileDialog, String directory) {
+                    fileDialog.dir = ("".equals(directory)) ? null : directory;
+                }
+                public boolean isMultipleMode(FileDialog fileDialog) {
+                    synchronized (fileDialog.getObjectLock()) {
+                        return fileDialog.multipleMode;
+                    }
+                }
+            });
+    }
+
     /**
      * Initialize JNI field and method IDs for fields that may be
        accessed from C.
@@ -371,6 +412,51 @@
     }
 
     /**
+     * Returns files that the user selects.
+     * <p>
+     * If the user cancels the file dialog,
+     * then the method returns an empty array.
+     *
+     * @return    files that the user selects or an empty array
+     *            if the user cancels the file dialog.
+     * @see       #setFile(String)
+     * @see       #getFile
+     * @since 1.7
+     */
+    public File[] getFiles() {
+        synchronized (getObjectLock()) {
+            if (files != null) {
+                return files.clone();
+            } else {
+                return new File[0];
+            }
+        }
+    }
+
+    /**
+     * Stores the names of all the files that the user selects.
+     *
+     * Note that the method is private and it's intended to be used
+     * by the peers through the AWTAccessor API.
+     *
+     * @param directory the current directory
+     * @param files     the array that contains the short names of
+     *                  all the files that the user selects.
+     *
+     * @see #getFiles
+     * @since 1.7
+     */
+    private void setFiles(String directory, String files[]) {
+        synchronized (getObjectLock()) {
+            int filesNumber = (files != null) ? files.length : 0;
+            this.files = new File[filesNumber];
+            for (int i = 0; i < filesNumber; i++) {
+                this.files[i] = new File(directory, files[i]);
+            }
+        }
+    }
+
+    /**
      * Sets the selected file for this file dialog window to be the
      * specified file. This file becomes the default file if it is set
      * before the file dialog window is first shown.
@@ -380,7 +466,8 @@
      * as the file.
      *
      * @param    file   the file being set
-     * @see      java.awt.FileDialog#getFile
+     * @see      #getFile
+     * @see      #getFiles
      */
     public void setFile(String file) {
         this.file = (file != null && file.equals("")) ? null : file;
@@ -391,6 +478,34 @@
     }
 
     /**
+     * Enables or disables multiple file selection for the file dialog.
+     *
+     * @param enable    if {@code true}, multiple file selection is enabled;
+     *                  {@code false} - disabled.
+     * @see #isMultipleMode
+     * @since 1.7
+     */
+    public void setMultipleMode(boolean enable) {
+        synchronized (getObjectLock()) {
+            this.multipleMode = enable;
+        }
+    }
+
+    /**
+     * Returns whether the file dialog allows the multiple file selection.
+     *
+     * @return          {@code true} if the file dialog allows the multiple
+     *                  file selection; {@code false} otherwise.
+     * @see #setMultipleMode
+     * @since 1.7
+     */
+    public boolean isMultipleMode() {
+        synchronized (getObjectLock()) {
+            return multipleMode;
+        }
+    }
+
+    /**
      * Determines this file dialog's filename filter. A filename filter
      * allows the user to specify which files appear in the file dialog
      * window.  Filename filters do not function in Sun's reference
--- a/src/share/classes/sun/awt/AWTAccessor.java	Mon Feb 08 17:02:43 2010 +0300
+++ b/src/share/classes/sun/awt/AWTAccessor.java	Fri Feb 12 19:58:10 2010 +0300
@@ -390,6 +390,30 @@
         boolean isTrayIconPopup(PopupMenu popupMenu);
     }
 
+    /*
+     * An accessor for the FileDialog class
+     */
+    public interface FileDialogAccessor {
+        /*
+         * Sets the files the user selects
+         */
+        void setFiles(FileDialog fileDialog, String directory, String files[]);
+
+        /*
+         * Sets the file the user selects
+         */
+        void setFile(FileDialog fileDialog, String file);
+
+        /*
+         * Sets the directory the user selects
+         */
+        void setDirectory(FileDialog fileDialog, String directory);
+
+        /*
+         * Returns whether the file dialog allows the multiple file selection.
+         */
+        boolean isMultipleMode(FileDialog fileDialog);
+    }
 
     /*
      * The java.awt.Component class accessor object.
@@ -432,6 +456,11 @@
     private static PopupMenuAccessor popupMenuAccessor;
 
     /*
+     * The java.awt.FileDialog class accessor object.
+     */
+    private static FileDialogAccessor fileDialogAccessor;
+
+    /*
      * Set an accessor object for the java.awt.Component class.
      */
     public static void setComponentAccessor(ComponentAccessor ca) {
@@ -567,4 +596,22 @@
         }
         return popupMenuAccessor;
     }
+
+    /*
+     * Set an accessor object for the java.awt.FileDialog class.
+     */
+    public static void setFileDialogAccessor(FileDialogAccessor fda) {
+        fileDialogAccessor = fda;
+    }
+
+    /*
+     * Retrieve the accessor object for the java.awt.FileDialog class.
+     */
+    public static FileDialogAccessor getFileDialogAccessor() {
+        if (fileDialogAccessor == null) {
+            unsafe.ensureClassInitialized(FileDialog.class);
+        }
+        return fileDialogAccessor;
+    }
+
 }
--- a/src/solaris/classes/sun/awt/X11/XFileDialogPeer.java	Mon Feb 08 17:02:43 2010 +0300
+++ b/src/solaris/classes/sun/awt/X11/XFileDialogPeer.java	Fri Feb 12 19:58:10 2010 +0300
@@ -37,6 +37,7 @@
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import sun.util.logging.PlatformLogger;
+import sun.awt.AWTAccessor;
 
 class XFileDialogPeer extends XDialogPeer implements FileDialogPeer, ActionListener, ItemListener, KeyEventDispatcher, XChoicePeerListener {
     private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XFileDialogPeer");
@@ -171,6 +172,10 @@
         filterField = new TextField();
         selectionField = new TextField();
 
+        boolean isMultipleMode =
+            AWTAccessor.getFileDialogAccessor().isMultipleMode(target);
+        fileList.setMultipleMode(isMultipleMode);
+
         // the insets used by the components in the fileDialog
         Insets noInset = new Insets(0, 0, 0, 0);
         Insets textFieldInset = new Insets(0, 8, 0, 8);
@@ -380,7 +385,8 @@
      * handle the selection event
      */
     void handleSelection(String file) {
-        int index = file.lastIndexOf('/');
+
+        int index = file.lastIndexOf(java.io.File.separatorChar);
 
         if (index == -1) {
             savedDir = this.dir;
@@ -389,8 +395,12 @@
             savedDir = file.substring(0, index+1);
             savedFile = file.substring(index+1);
         }
-        target.setDirectory(savedDir);
-        target.setFile(savedFile);
+
+        AWTAccessor.FileDialogAccessor fileDialogAccessor = AWTAccessor.getFileDialogAccessor();
+
+        fileDialogAccessor.setDirectory(target, savedDir);
+        fileDialogAccessor.setFile(target, savedFile);
+        fileDialogAccessor.setFiles(target, savedDir, fileList.getSelectedItems());
     }
 
     /**
@@ -404,8 +414,13 @@
         setFilterField(null);
         directoryList.clear();
         fileList.clear();
-        target.setFile(null);
-        target.setDirectory(null);
+
+        AWTAccessor.FileDialogAccessor fileDialogAccessor = AWTAccessor.getFileDialogAccessor();
+
+        fileDialogAccessor.setDirectory(target, null);
+        fileDialogAccessor.setFile(target, null);
+        fileDialogAccessor.setFiles(target, null, null);
+
         handleQuitButton();
     }
 
--- a/src/windows/classes/sun/awt/windows/WFileDialogPeer.java	Mon Feb 08 17:02:43 2010 +0300
+++ b/src/windows/classes/sun/awt/windows/WFileDialogPeer.java	Fri Feb 12 19:58:10 2010 +0300
@@ -117,26 +117,57 @@
         }
     }
 
-    // NOTE: This method is called by privileged threads.
-    //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
-    void handleSelected(final String file) {
+    /*
+     * The function converts the file names (the buffer parameter)
+     * in the Windows format into the Java format and saves the results
+     * into the FileDialog instance.
+     *
+     * If it's the multi-select mode, the buffer contains the current
+     * directory followed by the short names of the files.
+     * The directory and file name strings are NULL separated.
+     * If it's the single-select mode, the buffer doesn't have the NULL
+     * separator between the path and the file name.
+     *
+     * NOTE: This method is called by privileged threads.
+     *       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
+     */
+    void handleSelected(final char[] buffer)
+    {
+        String[] wFiles = (new String(buffer)).split("\0"); // NULL is the delimiter
+        boolean multiple = (wFiles.length > 1);
+
+        String jDirectory = null;
+        String jFile = null;
+        String jFiles[] = null;
+
+        if (multiple) {
+            jDirectory = wFiles[0];
+            jFiles = new String[wFiles.length - 1];
+            System.arraycopy(wFiles, 1, jFiles, 0, jFiles.length);
+            jFile = jFiles[1]; // choose any file
+        } else {
+            int index = wFiles[0].lastIndexOf(java.io.File.separatorChar);
+            if (index == -1) {
+                jDirectory = "."+java.io.File.separator;
+                jFile = wFiles[0];
+            } else {
+                jDirectory = wFiles[0].substring(0, index + 1);
+                jFile = wFiles[0].substring(index + 1);
+            }
+            jFiles = new String[] { jFile };
+        }
+
         final FileDialog fileDialog = (FileDialog)target;
+        AWTAccessor.FileDialogAccessor fileDialogAccessor = AWTAccessor.getFileDialogAccessor();
+
+        fileDialogAccessor.setDirectory(fileDialog, jDirectory);
+        fileDialogAccessor.setFile(fileDialog, jFile);
+        fileDialogAccessor.setFiles(fileDialog, jDirectory, jFiles);
+
         WToolkit.executeOnEventHandlerThread(fileDialog, new Runnable() {
-            public void run() {
-                int index = file.lastIndexOf(java.io.File.separatorChar);/*2509*//*ibm*/
-                String dir;
-
-                if (index == -1) {
-                    dir = "."+java.io.File.separator;
-                    fileDialog.setFile(file);
-                }
-                else {
-                    dir = file.substring(0, index + 1);
-                    fileDialog.setFile(file.substring(index + 1));
-                }
-                fileDialog.setDirectory(dir);
-                fileDialog.hide();
-            }
+             public void run() {
+                 fileDialog.hide();
+             }
         });
     } // handleSelected()
 
@@ -144,11 +175,14 @@
     //       DO NOT INVOKE CLIENT CODE ON THIS THREAD!
     void handleCancel() {
         final FileDialog fileDialog = (FileDialog)target;
+
+        AWTAccessor.getFileDialogAccessor().setFile(fileDialog, null);
+        AWTAccessor.getFileDialogAccessor().setFiles(fileDialog, null, null);
+
         WToolkit.executeOnEventHandlerThread(fileDialog, new Runnable() {
-            public void run() {
-                fileDialog.setFile(null);
-                fileDialog.hide();
-            }
+             public void run() {
+                 fileDialog.hide();
+             }
         });
     } // handleCancel()
 
@@ -244,4 +278,9 @@
     public void createScreenSurface(boolean isResize) {}
     @Override
     public void replaceSurfaceData() {}
+
+    public boolean isMultipleMode() {
+        FileDialog fileDialog = (FileDialog)target;
+        return AWTAccessor.getFileDialogAccessor().isMultipleMode(fileDialog);
+    }
 }
--- a/src/windows/native/sun/windows/awt_FileDialog.cpp	Mon Feb 08 17:02:43 2010 +0300
+++ b/src/windows/native/sun/windows/awt_FileDialog.cpp	Fri Feb 12 19:58:10 2010 +0300
@@ -44,6 +44,7 @@
 jmethodID AwtFileDialog::handleSelectedMID;
 jmethodID AwtFileDialog::handleCancelMID;
 jmethodID AwtFileDialog::checkFilenameFilterMID;
+jmethodID AwtFileDialog::isMultipleModeMID;
 
 /* FileDialog ids */
 jfieldID AwtFileDialog::modeID;
@@ -57,6 +58,13 @@
 /* Non-localized suffix of the filter string */
 static const TCHAR s_additionalString[] = TEXT(" (*.*)\0*.*\0");
 
+// Default limit of the output buffer.
+#define SINGLE_MODE_BUFFER_LIMIT     MAX_PATH+1
+#define MULTIPLE_MODE_BUFFER_LIMIT   32768
+
+// The name of the property holding the pointer to the OPENFILENAME structure.
+static LPCTSTR OpenFileNameProp = TEXT("AWT_OFN");
+
 /***********************************************************************/
 
 void
@@ -140,6 +148,8 @@
                                                                            FileDialogWndProc);
             ::SetProp(parent, NativeDialogWndProcProp, reinterpret_cast<HANDLE>(lpfnWndProc));
 
+            ::SetProp(parent, OpenFileNameProp, (void *)lParam);
+
             break;
         }
         case WM_DESTROY: {
@@ -149,6 +159,7 @@
                                                        lpfnWndProc);
             ::RemoveProp(parent, ModalDialogPeerProp);
             ::RemoveProp(parent, NativeDialogWndProcProp);
+            ::RemoveProp(parent, OpenFileNameProp);
             break;
         }
         case WM_NOTIFY: {
@@ -174,6 +185,30 @@
                     // to unblock all the windows blocked by this dialog as it will
                     // be closed soon
                     env->CallVoidMethod(peer, AwtFileDialog::setHWndMID, (jlong)0);
+                } else if (notifyEx->hdr.code == CDN_SELCHANGE) {
+                    // reallocate the buffer if the buffer is too small
+                    LPOPENFILENAME lpofn = (LPOPENFILENAME)GetProp(parent, OpenFileNameProp);
+
+                    UINT nLength = CommDlg_OpenSave_GetSpec(parent, NULL, 0) +
+                                   CommDlg_OpenSave_GetFolderPath(parent, NULL, 0);
+
+                    if (lpofn->nMaxFile < nLength)
+                    {
+                        // allocate new buffer
+                        LPTSTR newBuffer = new TCHAR[nLength];
+
+                        if (newBuffer) {
+                            memset(newBuffer, 0, nLength * sizeof(TCHAR));
+                            LPTSTR oldBuffer = lpofn->lpstrFile;
+                            lpofn->lpstrFile = newBuffer;
+                            lpofn->nMaxFile = nLength;
+                            // free the previously allocated buffer
+                            if (oldBuffer) {
+                                delete[] oldBuffer;
+                            }
+
+                        }
+                    }
                 }
             }
             break;
@@ -193,7 +228,6 @@
     WCHAR unicodeChar = L' ';
     LPTSTR fileBuffer = NULL;
     LPTSTR currentDirectory = NULL;
-    OPENFILENAME ofn;
     jint mode = 0;
     BOOL result = FALSE;
     DWORD dlgerr;
@@ -204,6 +238,10 @@
     jobject target = NULL;
     jobject parent = NULL;
     AwtComponent* awtParent = NULL;
+    jboolean multipleMode = JNI_FALSE;
+
+    OPENFILENAME ofn;
+    memset(&ofn, 0, sizeof(ofn));
 
     /*
      * There's a situation (see bug 4906972) when InvokeFunction (by which this method is called)
@@ -233,7 +271,16 @@
             (jstring)env->GetObjectField(target, AwtFileDialog::dirID);
         JavaStringBuffer directoryBuffer(env, directory);
 
-        fileBuffer = new TCHAR[MAX_PATH+1];
+        multipleMode = env->CallBooleanMethod(peer, AwtFileDialog::isMultipleModeMID);
+
+        UINT bufferLimit;
+        if (multipleMode == JNI_TRUE) {
+            bufferLimit = MULTIPLE_MODE_BUFFER_LIMIT;
+        } else {
+            bufferLimit = SINGLE_MODE_BUFFER_LIMIT;
+        }
+        LPTSTR fileBuffer = new TCHAR[bufferLimit];
+        memset(fileBuffer, 0, bufferLimit * sizeof(TCHAR));
 
         file = (jstring)env->GetObjectField(target, AwtFileDialog::fileID);
         if (file != NULL) {
@@ -244,8 +291,6 @@
             fileBuffer[0] = _T('\0');
         }
 
-        memset(&ofn, 0, sizeof(ofn));
-
         ofn.lStructSize = sizeof(ofn);
         ofn.lpstrFilter = s_fileFilterString;
         ofn.nFilterIndex = 1;
@@ -265,19 +310,23 @@
             ofn.hwndOwner = NULL;
         }
         ofn.lpstrFile = fileBuffer;
-        ofn.nMaxFile = MAX_PATH;
+        ofn.nMaxFile = bufferLimit;
         ofn.lpstrTitle = titleBuffer;
         ofn.lpstrInitialDir = directoryBuffer;
         ofn.Flags = OFN_LONGNAMES | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY |
                     OFN_ENABLEHOOK | OFN_EXPLORER | OFN_ENABLESIZING;
         fileFilter = env->GetObjectField(peer,
         AwtFileDialog::fileFilterID);
-    if (!JNU_IsNull(env,fileFilter)) {
-        ofn.Flags |= OFN_ENABLEINCLUDENOTIFY;
-    }
+        if (!JNU_IsNull(env,fileFilter)) {
+            ofn.Flags |= OFN_ENABLEINCLUDENOTIFY;
+        }
         ofn.lCustData = (LPARAM)peer;
         ofn.lpfnHook = (LPOFNHOOKPROC)FileDialogHookProc;
 
+        if (multipleMode == JNI_TRUE) {
+            ofn.Flags |= OFN_ALLOWMULTISELECT;
+        }
+
         // Save current directory, so we can reset if it changes.
         currentDirectory = new TCHAR[MAX_PATH+1];
 
@@ -318,11 +367,12 @@
 
         // Report result to peer.
         if (result) {
-            jstring tmpJString = (_tcslen(ofn.lpstrFile) == 0 ?
-                JNU_NewStringPlatform(env, L"") :
-                JNU_NewStringPlatform(env, ofn.lpstrFile));
-            env->CallVoidMethod(peer, AwtFileDialog::handleSelectedMID, tmpJString);
-            env->DeleteLocalRef(tmpJString);
+            jint length = (jint)GetBufferLength(ofn.lpstrFile, ofn.nMaxFile);
+            jcharArray jnames = env->NewCharArray(length);
+            env->SetCharArrayRegion(jnames, 0, length, (jchar*)ofn.lpstrFile);
+
+            env->CallVoidMethod(peer, AwtFileDialog::handleSelectedMID, jnames);
+            env->DeleteLocalRef(jnames);
         } else {
             env->CallVoidMethod(peer, AwtFileDialog::handleCancelMID);
         }
@@ -338,7 +388,8 @@
         env->DeleteGlobalRef(peer);
 
         delete[] currentDirectory;
-        delete[] fileBuffer;
+        if (ofn.lpstrFile)
+            delete[] ofn.lpstrFile;
         throw;
     }
 
@@ -351,7 +402,8 @@
     env->DeleteGlobalRef(peer);
 
     delete[] currentDirectory;
-    delete[] fileBuffer;
+    if (ofn.lpstrFile)
+        delete[] ofn.lpstrFile;
 }
 
 BOOL
@@ -416,6 +468,18 @@
     env->DeleteGlobalRef(self);
 }
 
+// Returns the length of the double null terminated output buffer
+UINT AwtFileDialog::GetBufferLength(LPTSTR buffer, UINT limit)
+{
+    UINT index = 0;
+    while ((index < limit) &&
+           (buffer[index] != NULL || buffer[index+1] != NULL))
+    {
+        index++;
+    }
+    return index;
+}
+
 /************************************************************************
  * WFileDialogPeer native methods
  */
@@ -434,11 +498,12 @@
     AwtFileDialog::setHWndMID =
         env->GetMethodID(cls, "setHWnd", "(J)V");
     AwtFileDialog::handleSelectedMID =
-        env->GetMethodID(cls, "handleSelected", "(Ljava/lang/String;)V");
+        env->GetMethodID(cls, "handleSelected", "([C)V");
     AwtFileDialog::handleCancelMID =
         env->GetMethodID(cls, "handleCancel", "()V");
     AwtFileDialog::checkFilenameFilterMID =
         env->GetMethodID(cls, "checkFilenameFilter", "(Ljava/lang/String;)Z");
+    AwtFileDialog::isMultipleModeMID = env->GetMethodID(cls, "isMultipleMode", "()Z");
 
     /* java.awt.FileDialog fields */
     cls = env->FindClass("java/awt/FileDialog");
@@ -455,6 +520,7 @@
     DASSERT(AwtFileDialog::setHWndMID != NULL);
     DASSERT(AwtFileDialog::handleSelectedMID != NULL);
     DASSERT(AwtFileDialog::handleCancelMID != NULL);
+    DASSERT(AwtFileDialog::isMultipleModeMID != NULL);
 
     DASSERT(AwtFileDialog::modeID != NULL);
     DASSERT(AwtFileDialog::dirID != NULL);
--- a/src/windows/native/sun/windows/awt_FileDialog.h	Mon Feb 08 17:02:43 2010 +0300
+++ b/src/windows/native/sun/windows/awt_FileDialog.h	Fri Feb 12 19:58:10 2010 +0300
@@ -49,6 +49,7 @@
     static jmethodID handleSelectedMID;
     static jmethodID handleCancelMID;
     static jmethodID checkFilenameFilterMID;
+    static jmethodID isMultipleModeMID;
 
     /* java.awt.FileDialog field and method ids */
     static jfieldID modeID;
@@ -68,6 +69,9 @@
     static void _DisposeOrHide(void *param);
     static void _ToFront(void *param);
     static void _ToBack(void *param);
+
+private:
+    static UINT GetBufferLength(LPTSTR buffer, UINT limit);
 };
 
 #endif /* FILE_DIALOG_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/awt/FileDialog/MultipleMode/MultipleMode.html	Fri Feb 12 19:58:10 2010 +0300
@@ -0,0 +1,20 @@
+<html>
+<!--  
+  @test
+  @bug 6467204
+  @summary Need to implement "extended" native FileDialog for JFileChooser
+  @author dmitry.cherepanov@sun.com area=awt.filedialog
+  @run applet/manual=yesno MultipleMode.html
+  -->
+<head>
+<title> MultipleMode </title>
+</head>
+<body>
+
+<h1>MultipleMode<br>Bug ID: 6467204</h1>
+
+<p> See the dialog box (usually in upper left corner) for instructions</p>
+
+<APPLET CODE="MultipleMode.class" WIDTH=200 HEIGHT=200></APPLET>
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/awt/FileDialog/MultipleMode/MultipleMode.java	Fri Feb 12 19:58:10 2010 +0300
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+  test
+  @bug 6467204
+  @summary Need to implement "extended" native FileDialog for JFileChooser
+  @author dmitry.cherepanov@sun.com area=awt.filedialog
+  @run applet/manual=yesno MultipleMode.html
+*/
+
+// Note there is no @ in front of test above.  This is so that the
+//  harness will not mistake this file as a test file.  It should
+//  only see the html file as a test file. (the harness runs all
+//  valid test files, so it would run this test twice if this file
+//  were valid as well as the html file.)
+// Also, note the area= after Your Name in the author tag.  Here, you
+//  should put which functional area the test falls in.  See the
+//  AWT-core home page -> test areas and/or -> AWT team  for a list of
+//  areas.
+// There are several places where ManualYesNoTest appear.  It is
+//  recommended that these be changed by a global search and replace,
+//  such as  ESC-%  in xemacs.
+
+
+
+/**
+ * MultipleMode.java
+ *
+ * summary:
+ */
+
+import java.applet.Applet;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+
+
+//Manual tests should run as applet tests if possible because they
+// get their environments cleaned up, including AWT threads, any
+// test created threads, and any system resources used by the test
+// such as file descriptors.  (This is normally not a problem as
+// main tests usually run in a separate VM, however on some platforms
+// such as the Mac, separate VMs are not possible and non-applet
+// tests will cause problems).  Also, you don't have to worry about
+// synchronisation stuff in Applet tests the way you do in main
+// tests...
+
+
+public class MultipleMode extends Applet
+{
+    //Declare things used in the test, like buttons and labels here
+
+    public void init()
+    {
+        //Create instructions for the user here, as well as set up
+        // the environment -- set the layout manager, add buttons,
+        // etc.
+        this.setLayout (new BorderLayout ());
+
+        String[] instructions =
+        {
+            " 1. Turn the 'multiple' checkbox off and press the 'open' button ",
+            " 2. Verify that the file dialog doesn't allow the multiple file selection ",
+            " 3. Select any file and close the file dialog ",
+            " 4. The results will be displayed, verify the results ",
+            " 5. Turn the 'multiple' checkbox on and press the 'open' button ",
+            " 6. Verify that the file dialog allows the multiple file selection ",
+            " 7. Select several files and close the file dialog ",
+            " 8. The results will be displayed, verify the results "
+        };
+        Sysout.createDialogWithInstructions( instructions );
+
+    }//End  init()
+
+    public void start ()
+    {
+        final Checkbox mode = new Checkbox("multiple", true);
+        Button open = new Button("open");
+        open.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                FileDialog d = new FileDialog((Frame)null);
+                d.setMultipleMode(mode.getState());
+                d.setVisible(true);
+
+                // print the results
+                Sysout.println("DIR:");
+                Sysout.println(d.getDirectory());
+                Sysout.println("FILE:");
+                Sysout.println(d.getFile());
+                Sysout.println("FILES:");
+                File files[] = d.getFiles();
+                for (File f : files) {
+                    Sysout.println(String.valueOf(f));
+                }
+            }
+        });
+
+        setLayout(new FlowLayout());
+        add(mode);
+        add(open);
+
+        //Get things going.  Request focus, set size, et cetera
+        setSize (200,200);
+        setVisible(true);
+        validate();
+
+    }// start()
+
+    //The rest of this class is the actions which perform the test...
+
+    //Use Sysout.println to communicate with the user NOT System.out!!
+    //Sysout.println ("Something Happened!");
+
+}// class ManualYesNoTest
+
+/* Place other classes related to the test after this line */
+
+
+
+
+
+/****************************************************
+ Standard Test Machinery
+ DO NOT modify anything below -- it's a standard
+  chunk of code whose purpose is to make user
+  interaction uniform, and thereby make it simpler
+  to read and understand someone else's test.
+ ****************************************************/
+
+/**
+ This is part of the standard test machinery.
+ It creates a dialog (with the instructions), and is the interface
+  for sending text messages to the user.
+ To print the instructions, send an array of strings to Sysout.createDialog
+  WithInstructions method.  Put one line of instructions per array entry.
+ To display a message for the tester to see, simply call Sysout.println
+  with the string to be displayed.
+ This mimics System.out.println but works within the test harness as well
+  as standalone.
+ */
+
+class Sysout
+{
+    private static TestDialog dialog;
+    private static boolean numbering = false;
+    private static int messageNumber = 0;
+
+    public static void createDialogWithInstructions( String[] instructions )
+    {
+        dialog = new TestDialog( new Frame(), "Instructions" );
+        dialog.printInstructions( instructions );
+        dialog.setVisible(true);
+        println( "Any messages for the tester will display here." );
+    }
+
+    public static void createDialog( )
+    {
+        dialog = new TestDialog( new Frame(), "Instructions" );
+        String[] defInstr = { "Instructions will appear here. ", "" } ;
+        dialog.printInstructions( defInstr );
+        dialog.setVisible(true);
+        println( "Any messages for the tester will display here." );
+    }
+
+    /* Enables message counting for the tester. */
+    public static void enableNumbering(boolean enable){
+        numbering = enable;
+    }
+
+    public static void printInstructions( String[] instructions )
+    {
+        dialog.printInstructions( instructions );
+    }
+
+
+    public static void println( String messageIn )
+    {
+        if (numbering) {
+            messageIn = "" + messageNumber + " " + messageIn;
+            messageNumber++;
+        }
+        dialog.displayMessage( messageIn );
+    }
+
+}// Sysout  class
+
+/**
+  This is part of the standard test machinery.  It provides a place for the
+   test instructions to be displayed, and a place for interactive messages
+   to the user to be displayed.
+  To have the test instructions displayed, see Sysout.
+  To have a message to the user be displayed, see Sysout.
+  Do not call anything in this dialog directly.
+  */
+class TestDialog extends Dialog
+{
+
+    TextArea instructionsText;
+    TextArea messageText;
+    int maxStringLength = 80;
+
+    //DO NOT call this directly, go through Sysout
+    public TestDialog( Frame frame, String name )
+    {
+        super( frame, name );
+        int scrollBoth = TextArea.SCROLLBARS_BOTH;
+        instructionsText = new TextArea( "", 15, maxStringLength, scrollBoth );
+        add( "North", instructionsText );
+
+        messageText = new TextArea( "", 5, maxStringLength, scrollBoth );
+        add("Center", messageText);
+
+        pack();
+
+        setVisible(true);
+    }// TestDialog()
+
+    //DO NOT call this directly, go through Sysout
+    public void printInstructions( String[] instructions )
+    {
+        //Clear out any current instructions
+        instructionsText.setText( "" );
+
+        //Go down array of instruction strings
+
+        String printStr, remainingStr;
+        for( int i=0; i < instructions.length; i++ )
+        {
+            //chop up each into pieces maxSringLength long
+            remainingStr = instructions[ i ];
+            while( remainingStr.length() > 0 )
+            {
+                //if longer than max then chop off first max chars to print
+                if( remainingStr.length() >= maxStringLength )
+                {
+                    //Try to chop on a word boundary
+                    int posOfSpace = remainingStr.
+                        lastIndexOf( ' ', maxStringLength - 1 );
+
+                    if( posOfSpace <= 0 ) posOfSpace = maxStringLength - 1;
+
+                    printStr = remainingStr.substring( 0, posOfSpace + 1 );
+                    remainingStr = remainingStr.substring( posOfSpace + 1 );
+                }
+                //else just print
+                else
+                {
+                    printStr = remainingStr;
+                    remainingStr = "";
+                }
+
+                instructionsText.append( printStr + "\n" );
+
+            }// while
+
+        }// for
+
+    }//printInstructions()
+
+    //DO NOT call this directly, go through Sysout
+    public void displayMessage( String messageIn )
+    {
+        messageText.append( messageIn + "\n" );
+        System.out.println(messageIn);
+    }
+
+}// TestDialog  class