diff options
Diffstat (limited to 'win/tkWinDialog.c')
-rw-r--r-- | win/tkWinDialog.c | 416 |
1 files changed, 304 insertions, 112 deletions
diff --git a/win/tkWinDialog.c b/win/tkWinDialog.c index 93d81b6..f22ea45 100644 --- a/win/tkWinDialog.c +++ b/win/tkWinDialog.c @@ -8,7 +8,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkWinDialog.c,v 1.50.2.4 2009/10/22 10:27:58 dkf Exp $ + * RCS: @(#) $Id: tkWinDialog.c,v 1.50.2.5 2010/01/05 22:36:59 patthoyts Exp $ * */ @@ -170,6 +170,21 @@ typedef struct ChooseDir { } ChooseDir; /* + * The following structure is used to pass information between GetFileName/W + * functions and OFN dialog hook procedures. [Bug 2896501, Patch 2898255] + */ + +typedef struct OFNData { + Tcl_Interp *interp; /* Interp, used only if debug is turned on, + * for setting the "tk_dialog" variable. */ + int dynFileBufferSize; /* Dynamic filename buffer size, stored to + * avoid shrinking and expanding the buffer + * when selection changes */ + char *dynFileBuffer; /* Dynamic filename buffer, cast to WCHAR* in + * UNICODE procedures */ +} OFNData; + +/* * Definitions of functions used only in this file. */ @@ -186,7 +201,7 @@ static int GetFileNameW(ClientData clientData, static int MakeFilter(Tcl_Interp *interp, Tcl_Obj *valuePtr, Tcl_DString *dsPtr, Tcl_Obj *initialPtr, int *index); -static UINT APIENTRY OFNHookProc(HWND hdlg, UINT uMsg, WPARAM wParam, +static UINT APIENTRY OFNHookProcA(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam); static UINT APIENTRY OFNHookProcW(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam); @@ -573,12 +588,14 @@ GetFileNameW( { OPENFILENAMEW ofn; WCHAR file[TK_MULTI_MAX_PATH]; + OFNData ofnData; + int cdlgerr; int filterIndex, result, winCode, oldMode, i, multi = 0; char *extension, *filter, *title; Tk_Window tkwin; HWND hWnd; Tcl_Obj *filterObj, *initialTypeObj, *typeVariableObj; - Tcl_DString utfFilterString, utfDirString; + Tcl_DString utfFilterString, utfDirString, ds; Tcl_DString extString, filterString, dirString, titleString; Tcl_Encoding unicodeEncoding = TkWinGetUnicodeEncoding(); ThreadSpecificData *tsdPtr = (ThreadSpecificData *) @@ -600,6 +617,7 @@ GetFileNameW( result = TCL_ERROR; file[0] = '\0'; + ZeroMemory(&ofnData, sizeof(OFNData)); /* * Parse the arguments. @@ -674,9 +692,7 @@ GetFileNameW( goto end; } break; - case FILE_INITFILE: { - Tcl_DString ds; - + case FILE_INITFILE: if (Tcl_TranslateFileName(interp, string, &ds) == NULL) { goto end; } @@ -685,7 +701,6 @@ GetFileNameW( sizeof(file), NULL, NULL, NULL); Tcl_DStringFree(&ds); break; - } case FILE_MULTIPLE: if (Tcl_GetBooleanFromObj(interp, valuePtr, &multi) != TCL_OK) { return TCL_ERROR; @@ -724,9 +739,9 @@ GetFileNameW( ofn.lpstrFile = (WCHAR *) file; ofn.nMaxFile = TK_MULTI_MAX_PATH; ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR - | OFN_EXPLORER; + | OFN_EXPLORER | OFN_ENABLEHOOK; ofn.lpfnHook = (LPOFNHOOKPROC) OFNHookProcW; - ofn.lCustData = (LPARAM) interp; + ofn.lCustData = (LPARAM) &ofnData; if (open != 0) { ofn.Flags |= OFN_FILEMUSTEXIST; @@ -735,11 +750,19 @@ GetFileNameW( } if (tsdPtr->debugFlag != 0) { - ofn.Flags |= OFN_ENABLEHOOK; + ofnData.interp = interp; } if (multi != 0) { ofn.Flags |= OFN_ALLOWMULTISELECT; + + /* + * Starting buffer size. The buffer will be expanded by the OFN dialog + * procedure when necessary + */ + + ofnData.dynFileBufferSize = 1024; + ofnData.dynFileBuffer = ckalloc(1024); } if (extension != NULL) { @@ -813,32 +836,43 @@ GetFileNameW( /* * Process the results. + * + * Use the CommDlgExtendedError() function to retrieve the error code. + * This function can return one of about two dozen codes; most of these + * indicate some sort of gross system failure (insufficient memory, bad + * window handles, etc.). Most of the error codes will be ignored; as we + * find we want more specific error messages for particular errors, we can + * extend the code as needed. */ - if (winCode != 0) { + cdlgerr = CommDlgExtendedError(); + + /* + * We now allow FNERR_BUFFERTOOSMALL when multiselection is enabled. The + * filename buffer has been dynamically allocated by the OFN dialog + * procedure to accomodate all selected files. + */ + + if ((winCode != 0) + || ((cdlgerr == FNERR_BUFFERTOOSMALL) + && (ofn.Flags & OFN_ALLOWMULTISELECT))) { if (ofn.Flags & OFN_ALLOWMULTISELECT) { /* - * The result in custData->szFile contains many items, separated - * with null characters. It is terminated with two nulls in a row. - * The first element is the directory path. + * The result in dynFileBuffer contains many items, separated by + * NUL characters. It is terminated with two nulls in a row. The + * first element is the directory path. */ - WCHAR *files; - Tcl_DString dirBuf; - Tcl_Obj *returnList; + WCHAR *files = (WCHAR *) ofnData.dynFileBuffer; + Tcl_Obj *returnList = Tcl_NewObj(); int count = 0; - returnList = Tcl_NewObj(); - Tcl_IncrRefCount(returnList); - - files = ofn.lpstrFile; - /* * Get directory. */ (void) ConvertExternalFilename(unicodeEncoding, (char *) files, - &dirBuf); + &ds); while (*files != '\0') { while (*files != '\0') { @@ -853,8 +887,8 @@ GetFileNameW( (void) ConvertExternalFilename(unicodeEncoding, (char *) files, &filenameBuf); - fullnameObj = Tcl_NewStringObj(Tcl_DStringValue(&dirBuf), - Tcl_DStringLength(&dirBuf)); + fullnameObj = Tcl_NewStringObj(Tcl_DStringValue(&ds), + Tcl_DStringLength(&ds)); Tcl_AppendToObj(fullnameObj, "/", -1); Tcl_AppendToObj(fullnameObj, Tcl_DStringValue(&filenameBuf), Tcl_DStringLength(&filenameBuf)); @@ -869,15 +903,12 @@ GetFileNameW( */ Tcl_ListObjAppendElement(NULL, returnList, - Tcl_NewStringObj(Tcl_DStringValue(&dirBuf), - Tcl_DStringLength(&dirBuf))); + Tcl_NewStringObj(Tcl_DStringValue(&ds), + Tcl_DStringLength(&ds))); } Tcl_SetObjResult(interp, returnList); - Tcl_DecrRefCount(returnList); - Tcl_DStringFree(&dirBuf); + Tcl_DStringFree(&ds); } else { - Tcl_DString ds; - Tcl_AppendResult(interp, ConvertExternalFilename(unicodeEncoding, (char *) ofn.lpstrFile, &ds), NULL); Tcl_DStringFree(&ds); @@ -902,29 +933,13 @@ GetFileNameW( result = TCL_ERROR; } } + } else if (cdlgerr == FNERR_INVALIDFILENAME) { + Tcl_SetResult(interp, "invalid filename \"", TCL_STATIC); + Tcl_AppendResult(interp, ConvertExternalFilename(unicodeEncoding, + (char *) ofn.lpstrFile, &ds), "\"", NULL); + Tcl_DStringFree(&ds); } else { - /* - * Use the CommDlgExtendedError() function to retrieve the error code. - * This function can return one of about two dozen codes; most of - * these indicate some sort of gross system failure (insufficient - * memory, bad window handles, etc.). Most of the error codes will be - * ignored; as we find we want more specific error messages for - * particular errors, we can extend the code as needed. - * - * We could also check for FNERR_BUFFERTOOSMALL, but we can't really - * do anything about it when it happens. - */ - - if (CommDlgExtendedError() == FNERR_INVALIDFILENAME) { - Tcl_DString ds; - - Tcl_SetResult(interp, "invalid filename \"", TCL_STATIC); - Tcl_AppendResult(interp, ConvertExternalFilename(unicodeEncoding, - (char *) ofn.lpstrFile, &ds), "\"", NULL); - Tcl_DStringFree(&ds); - } else { - result = TCL_OK; - } + result = TCL_OK; } if (ofn.lpstrTitle != NULL) { @@ -941,6 +956,10 @@ GetFileNameW( end: Tcl_DStringFree(&utfDirString); Tcl_DStringFree(&utfFilterString); + if (ofnData.dynFileBuffer != NULL) { + ckfree(ofnData.dynFileBuffer); + ofnData.dynFileBuffer = NULL; + } return result; } @@ -950,8 +969,10 @@ GetFileNameW( * * OFNHookProcW -- * - * Hook function called only if debugging is turned on. Sets the - * "tk_dialog" variable when the dialog is ready to receive messages. + * Dialog box hook function. This is used to sets the "tk_dialog" + * variable for test/debugging when the dialog is ready to receive + * messages. When multiple file selection is enabled this function + * is used to process the list of names. * * Results: * Returns 0 to allow default processing of messages to occur. @@ -972,9 +993,88 @@ OFNHookProcW( ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); OPENFILENAMEW *ofnPtr; + OFNData *ofnData; if (uMsg == WM_INITDIALOG) { TkWinSetUserData(hdlg, lParam); + } else if (uMsg == WM_NOTIFY) { + OFNOTIFYW *notifyPtr = (OFNOTIFYW *) lParam; + + if (notifyPtr->hdr.code == CDN_SELCHANGE) { + int dirsize, selsize; + WCHAR *buffer; + int buffersize; + + /* + * Change of selection. Unscramble the unholy mess that's in the + * selection buffer, resizing it if necessary. + */ + + ofnPtr = notifyPtr->lpOFN; + ofnData = (OFNData *) ofnPtr->lCustData; + buffer = (WCHAR *) ofnData->dynFileBuffer; + hdlg = GetParent(hdlg); + + selsize = SendMessageW(hdlg, CDM_GETSPEC, 0, 0); + dirsize = SendMessageW(hdlg, CDM_GETFOLDERPATH, 0, 0); + buffersize = (selsize + dirsize + 1) * 2; + + if (selsize > 1) { + if (ofnData->dynFileBufferSize < buffersize) { + buffer = (WCHAR *) ckrealloc((char *) buffer, buffersize); + ofnData->dynFileBufferSize = buffersize; + ofnData->dynFileBuffer = (char *) buffer; + } + + SendMessageW(hdlg, CDM_GETFOLDERPATH, dirsize, (int) buffer); + buffer += dirsize; + + SendMessageW(hdlg, CDM_GETSPEC, selsize, (int) buffer); + + /* + * If there are multiple files, delete the quotes and change + * every second quote to NULL terminator + */ + + if (buffer[0] == '"') { + BOOL findquote = TRUE; + WCHAR *tmp = buffer; + + while(*buffer != '\0') { + if (findquote) { + if (*buffer == '"') { + findquote = FALSE; + } + buffer++; + } else { + if (*buffer == '"') { + findquote = TRUE; + *buffer = '\0'; + } + *tmp++ = *buffer++; + } + } + *tmp = '\0'; /* Second NULL terminator. */ + } else { + buffer[selsize] = '\0'; /* Second NULL terminator. */ + + /* + * Replace directory terminating NULL with a backslash. + */ + + buffer--; + *buffer = '\\'; + } + } else { + /* + * Nothing is selected, so just empty the string. + */ + + if (buffer != NULL) { + *buffer = '\0'; + } + } + } } else if (uMsg == WM_WINDOWPOSCHANGED) { /* * This message is delivered at the right time to enable Tk to set the @@ -984,9 +1084,12 @@ OFNHookProcW( ofnPtr = (OPENFILENAMEW *) TkWinGetUserData(hdlg); if (ofnPtr != NULL) { - hdlg = GetParent(hdlg); - tsdPtr->debugInterp = (Tcl_Interp *) ofnPtr->lCustData; - Tcl_DoWhenIdle(SetTkDialog, (ClientData) hdlg); + ofnData = (OFNData *) ofnPtr->lCustData; + if (ofnData->interp != NULL) { + hdlg = GetParent(hdlg); + tsdPtr->debugInterp = ofnData->interp; + Tcl_DoWhenIdle(SetTkDialog, hdlg); + } TkWinSetUserData(hdlg, NULL); } } @@ -1020,12 +1123,14 @@ GetFileNameA( { OPENFILENAME ofn; TCHAR file[TK_MULTI_MAX_PATH], savePath[MAX_PATH]; + OFNData ofnData; + int cdlgerr; int filterIndex, result, winCode, oldMode, i, multi = 0; char *extension, *filter, *title; Tk_Window tkwin; HWND hWnd; Tcl_Obj *filterObj, *initialTypeObj, *typeVariableObj; - Tcl_DString utfFilterString, utfDirString; + Tcl_DString utfFilterString, utfDirString, ds; Tcl_DString extString, filterString, dirString, titleString; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); @@ -1046,6 +1151,7 @@ GetFileNameA( result = TCL_ERROR; file[0] = '\0'; + ZeroMemory(&ofnData, sizeof(OFNData)); /* * Parse the arguments. @@ -1119,9 +1225,7 @@ GetFileNameA( goto end; } break; - case FILE_INITFILE: { - Tcl_DString ds; - + case FILE_INITFILE: if (Tcl_TranslateFileName(interp, string, &ds) == NULL) { goto end; } @@ -1130,7 +1234,6 @@ GetFileNameA( sizeof(file), NULL, NULL, NULL); Tcl_DStringFree(&ds); break; - } case FILE_MULTIPLE: if (Tcl_GetBooleanFromObj(interp, valuePtr, &multi) != TCL_OK) { return TCL_ERROR; @@ -1162,6 +1265,7 @@ GetFileNameA( Tk_MakeWindowExist(tkwin); hWnd = Tk_GetHWND(Tk_WindowId(tkwin)); + ZeroMemory(&ofn, sizeof(OPENFILENAMEA)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hWnd; ofn.hInstance = TkWinGetHInstance(ofn.hwndOwner); @@ -1176,12 +1280,12 @@ GetFileNameA( ofn.lpstrInitialDir = NULL; ofn.lpstrTitle = NULL; ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR - | OFN_EXPLORER; + | OFN_EXPLORER | OFN_ENABLEHOOK; ofn.nFileOffset = 0; ofn.nFileExtension = 0; ofn.lpstrDefExt = NULL; - ofn.lpfnHook = (LPOFNHOOKPROC) OFNHookProc; - ofn.lCustData = (LPARAM) interp; + ofn.lpfnHook = (LPOFNHOOKPROC) OFNHookProcA; + ofn.lCustData = (LPARAM) &ofnData; ofn.lpTemplateName = NULL; if (open != 0) { @@ -1191,11 +1295,19 @@ GetFileNameA( } if (tsdPtr->debugFlag != 0) { - ofn.Flags |= OFN_ENABLEHOOK; + ofnData.interp = interp; } if (multi != 0) { ofn.Flags |= OFN_ALLOWMULTISELECT; + + /* + * Starting buffer size. The buffer will be expanded by the OFN dialog + * procedure when necessary + */ + + ofnData.dynFileBufferSize = 1024; + ofnData.dynFileBuffer = ckalloc(1024); } if (extension != NULL) { @@ -1267,28 +1379,39 @@ GetFileNameA( /* * Process the results. + * + * Use the CommDlgExtendedError() function to retrieve the error code. + * This function can return one of about two dozen codes; most of these + * indicate some sort of gross system failure (insufficient memory, bad + * window handles, etc.) Most of the error codes will be ignored; as we + * find we want specific error messages for particular errors, we can + * extend the code as needed. + */ + + cdlgerr = CommDlgExtendedError(); + + /* + * We now allow FNERR_BUFFERTOOSMALL when multiselection is enabled. The + * filename buffer has been dynamically allocated by the OFN dialog + * procedure to accomodate all selected files. */ - if (winCode != 0) { + if ((winCode != 0) + || ((cdlgerr == FNERR_BUFFERTOOSMALL) + && (ofn.Flags & OFN_ALLOWMULTISELECT))) { if (ofn.Flags & OFN_ALLOWMULTISELECT) { /* - * The result in custData->szFile contains many items, separated - * with null characters. It is terminated with two nulls in a row. - * The first element is the directory path (if multiple files are + * The result in dynFileBuffer contains many items, separated by + * NUL characters. It is terminated with two nulls in a row. The + * first element is the directory path (if multiple files are * selected) or the only returned file (if only a single file has * been chosen). */ - char *files; - Tcl_DString ds; - Tcl_Obj *returnList; + char *files = ofnData.dynFileBuffer; + Tcl_Obj *returnList = Tcl_NewObj(); int count = 0; - returnList = Tcl_NewObj(); - Tcl_IncrRefCount(returnList); - - files = ofn.lpstrFile; - /* * Get directory. */ @@ -1325,11 +1448,8 @@ GetFileNameA( Tcl_DStringValue(&ds), Tcl_DStringLength(&ds))); } Tcl_SetObjResult(interp, returnList); - Tcl_DecrRefCount(returnList); Tcl_DStringFree(&ds); } else { - Tcl_DString ds; - Tcl_AppendResult(interp, ConvertExternalFilename(NULL, (char *) ofn.lpstrFile, &ds), NULL); Tcl_DStringFree(&ds); @@ -1354,29 +1474,13 @@ GetFileNameA( result = TCL_ERROR; } } + } else if (cdlgerr == FNERR_INVALIDFILENAME) { + Tcl_SetResult(interp, "invalid filename \"", TCL_STATIC); + Tcl_AppendResult(interp, ConvertExternalFilename(NULL, + (char *) ofn.lpstrFile, &ds), "\"", NULL); + Tcl_DStringFree(&ds); } else { - /* - * Use the CommDlgExtendedError() function to retrieve the error code. - * This function can return one of about two dozen codes; most of - * these indicate some sort of gross system failure (insufficient - * memory, bad window handles, etc.) Most of the error codes will be - * ignored; as we find we want specific error messages for particular - * errors, we can extend the code as needed. - * - * We could also check for FNERR_BUFFERTOOSMALL, but we can't really - * do anything about it when it happens. - */ - - if (CommDlgExtendedError() == FNERR_INVALIDFILENAME) { - Tcl_DString ds; - - Tcl_SetResult(interp, "invalid filename \"", TCL_STATIC); - Tcl_AppendResult(interp, ConvertExternalFilename(NULL, - (char *) ofn.lpstrFile, &ds), "\"", NULL); - Tcl_DStringFree(&ds); - } else { - result = TCL_OK; - } + result = TCL_OK; } if (ofn.lpstrTitle != NULL) { @@ -1393,6 +1497,10 @@ GetFileNameA( end: Tcl_DStringFree(&utfDirString); Tcl_DStringFree(&utfFilterString); + if (ofnData.dynFileBuffer != NULL) { + ckfree(ofnData.dynFileBuffer); + ofnData.dynFileBuffer = NULL; + } return result; } @@ -1400,10 +1508,12 @@ GetFileNameA( /* *------------------------------------------------------------------------- * - * OFNHookProc -- + * OFNHookProcA -- * - * Hook function called only if debugging is turned on. Sets the - * "tk_dialog" variable when the dialog is ready to receive messages. + * Dialog box hook function. This is used to sets the "tk_dialog" + * variable for test/debugging when the dialog is ready to receive + * messages. When multiple file selection is enabled this function + * is used to process the list of names. * * Results: * Returns 0 to allow default processing of messages to occur. @@ -1415,7 +1525,7 @@ GetFileNameA( */ static UINT APIENTRY -OFNHookProc( +OFNHookProcA( HWND hdlg, /* handle to child dialog window */ UINT uMsg, /* message identifier */ WPARAM wParam, /* message parameter */ @@ -1424,9 +1534,88 @@ OFNHookProc( ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); OPENFILENAME *ofnPtr; + OFNData *ofnData; if (uMsg == WM_INITDIALOG) { TkWinSetUserData(hdlg, lParam); + } else if (uMsg == WM_NOTIFY) { + OFNOTIFY *notifyPtr = (OFNOTIFY *) lParam; + + if (notifyPtr->hdr.code == CDN_SELCHANGE) { + int dirsize, selsize; + char *buffer; + int buffersize; + + /* + * Change of selection. Unscramble the unholy mess that's in the + * selection buffer, resizing it if necessary. + */ + + ofnPtr = notifyPtr->lpOFN; + ofnData = (OFNData *) ofnPtr->lCustData; + buffer = ofnData->dynFileBuffer; + hdlg = GetParent(hdlg); + + selsize = SendMessage(hdlg, CDM_GETSPEC, 0, 0); + dirsize = SendMessage(hdlg, CDM_GETFOLDERPATH, 0, 0); + buffersize = selsize + dirsize + 1; + + if (selsize > 1) { + if (ofnData->dynFileBufferSize < buffersize) { + buffer = ckrealloc(buffer, buffersize); + ofnData->dynFileBufferSize = buffersize; + ofnData->dynFileBuffer = buffer; + } + + SendMessage(hdlg, CDM_GETFOLDERPATH, dirsize, (int) buffer); + buffer += dirsize; + SendMessage(hdlg, CDM_GETSPEC, selsize, (int) buffer); + + /* + * If there are multiple files, delete the quotes and change + * every second quote to NULL terminator. + */ + + if (buffer[0] == '"') { + BOOL findquote = TRUE; + char *tmp = buffer; + + while (*buffer != '\0') { + if (findquote) { + if (*buffer == '"') { + findquote = FALSE; + } + buffer++; + } else { + if (*buffer == '"') { + findquote = TRUE; + *buffer = '\0'; + } + *tmp++ = *buffer++; + } + } + *tmp = '\0'; /* Second NULL terminator. */ + } else { + buffer[selsize] = '\0'; /* Second NULL terminator. */ + + /* + * Replace directory terminating NULL with a backslash. + */ + + buffer--; + *buffer = '\\'; + } + + } else { + /* + * Nothing is selected, so just empty the string. + */ + + if (buffer != NULL) { + *buffer = '\0'; + } + } + } } else if (uMsg == WM_WINDOWPOSCHANGED) { /* * This message is delivered at the right time to both old-style and @@ -1437,11 +1626,14 @@ OFNHookProc( ofnPtr = (OPENFILENAME *) TkWinGetUserData(hdlg); if (ofnPtr != NULL) { - if (ofnPtr->Flags & OFN_EXPLORER) { - hdlg = GetParent(hdlg); + ofnData = (OFNData *) ofnPtr->lCustData; + if (ofnData->interp != NULL) { + if (ofnPtr->Flags & OFN_EXPLORER) { + hdlg = GetParent(hdlg); + } + tsdPtr->debugInterp = ofnData->interp; + Tcl_DoWhenIdle(SetTkDialog, hdlg); } - tsdPtr->debugInterp = (Tcl_Interp *) ofnPtr->lCustData; - Tcl_DoWhenIdle(SetTkDialog, (ClientData) hdlg); TkWinSetUserData(hdlg, NULL); } } |