1/* 2 * tkWinDialog.c -- 3 * 4 * Contains the Windows implementation of the common dialog boxes. 5 * 6 * Copyright (c) 1996-1997 Sun Microsystems, Inc. 7 * 8 * See the file "license.terms" for information on usage and redistribution of 9 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 10 * 11 * RCS: @(#) $Id$ 12 * 13 */ 14 15#define WINVER 0x0500 /* Requires Windows 2K definitions */ 16#define _WIN32_WINNT 0x0500 17#include "tkWinInt.h" 18#include "tkFileFilter.h" 19 20#include <commdlg.h> /* includes common dialog functionality */ 21#ifdef _MSC_VER 22# pragma comment (lib, "comdlg32.lib") 23#endif 24#include <dlgs.h> /* includes common dialog template defines */ 25#include <cderr.h> /* includes the common dialog error codes */ 26 27#include <shlobj.h> /* includes SHBrowseForFolder */ 28#ifdef _MSC_VER 29# pragma comment (lib, "shell32.lib") 30#endif 31 32/* These needed for compilation with VC++ 5.2 */ 33#ifndef BIF_EDITBOX 34#define BIF_EDITBOX 0x10 35#endif 36 37#ifndef BIF_VALIDATE 38#define BIF_VALIDATE 0x0020 39#endif 40 41#ifndef BIF_NEWDIALOGSTYLE 42#define BIF_NEWDIALOGSTYLE 0x0040 43#endif 44 45#ifndef BFFM_VALIDATEFAILED 46#ifdef UNICODE 47#define BFFM_VALIDATEFAILED 4 48#else 49#define BFFM_VALIDATEFAILED 3 50#endif 51#endif /* BFFM_VALIDATEFAILED */ 52 53#ifndef OPENFILENAME_SIZE_VERSION_400 54#define OPENFILENAME_SIZE_VERSION_400 76 55#endif 56 57/* 58 * The following structure is used by the new Tk_ChooseDirectoryObjCmd to pass 59 * data between it and its callback. Unqiue to Winodws platform. 60 */ 61 62typedef struct ChooseDirData { 63 TCHAR utfInitDir[MAX_PATH]; /* Initial folder to use */ 64 TCHAR utfRetDir[MAX_PATH]; /* Returned folder to use */ 65 Tcl_Interp *interp; 66 int mustExist; /* True if file must exist to return from 67 * callback */ 68} CHOOSEDIRDATA; 69 70typedef struct ThreadSpecificData { 71 int debugFlag; /* Flags whether we should output debugging 72 * information while displaying a builtin 73 * dialog. */ 74 Tcl_Interp *debugInterp; /* Interpreter to used for debugging. */ 75 UINT WM_LBSELCHANGED; /* Holds a registered windows event used for 76 * communicating between the Directory Chooser 77 * dialog and its hook proc. */ 78 HHOOK hMsgBoxHook; /* Hook proc for tk_messageBox and the */ 79 HICON hSmallIcon; /* icons used by a parent to be used in */ 80 HICON hBigIcon; /* the message box */ 81} ThreadSpecificData; 82static Tcl_ThreadDataKey dataKey; 83 84/* 85 * The following structures are used by Tk_MessageBoxCmd() to parse arguments 86 * and return results. 87 */ 88 89static const TkStateMap iconMap[] = { 90 {MB_ICONERROR, "error"}, 91 {MB_ICONINFORMATION, "info"}, 92 {MB_ICONQUESTION, "question"}, 93 {MB_ICONWARNING, "warning"}, 94 {-1, NULL} 95}; 96 97static const TkStateMap typeMap[] = { 98 {MB_ABORTRETRYIGNORE, "abortretryignore"}, 99 {MB_OK, "ok"}, 100 {MB_OKCANCEL, "okcancel"}, 101 {MB_RETRYCANCEL, "retrycancel"}, 102 {MB_YESNO, "yesno"}, 103 {MB_YESNOCANCEL, "yesnocancel"}, 104 {-1, NULL} 105}; 106 107static const TkStateMap buttonMap[] = { 108 {IDABORT, "abort"}, 109 {IDRETRY, "retry"}, 110 {IDIGNORE, "ignore"}, 111 {IDOK, "ok"}, 112 {IDCANCEL, "cancel"}, 113 {IDNO, "no"}, 114 {IDYES, "yes"}, 115 {-1, NULL} 116}; 117 118static const int buttonFlagMap[] = { 119 MB_DEFBUTTON1, MB_DEFBUTTON2, MB_DEFBUTTON3, MB_DEFBUTTON4 120}; 121 122static const struct {int type; int btnIds[3];} allowedTypes[] = { 123 {MB_ABORTRETRYIGNORE, {IDABORT, IDRETRY, IDIGNORE}}, 124 {MB_OK, {IDOK, -1, -1 }}, 125 {MB_OKCANCEL, {IDOK, IDCANCEL, -1 }}, 126 {MB_RETRYCANCEL, {IDRETRY, IDCANCEL, -1 }}, 127 {MB_YESNO, {IDYES, IDNO, -1 }}, 128 {MB_YESNOCANCEL, {IDYES, IDNO, IDCANCEL}} 129}; 130 131#define NUM_TYPES (sizeof(allowedTypes) / sizeof(allowedTypes[0])) 132 133/* 134 * Abstract trivial differences between Win32 and Win64. 135 */ 136 137#define TkWinGetHInstance(from) \ 138 ((HINSTANCE) GetWindowLongPtr((from), GWLP_HINSTANCE)) 139#define TkWinGetUserData(from) \ 140 GetWindowLongPtr((from), GWLP_USERDATA) 141#define TkWinSetUserData(to,what) \ 142 SetWindowLongPtr((to), GWLP_USERDATA, (LPARAM)(what)) 143 144/* 145 * The value of TK_MULTI_MAX_PATH dictactes how many files can be retrieved 146 * with tk_get*File -multiple 1. It must be allocated on the stack, so make it 147 * large enough but not too large. - hobbs 148 * 149 * The data is stored as <dir>\0<file1>\0<file2>\0...<fileN>\0\0. Since 150 * MAX_PATH == 260 on Win2K/NT, *40 is ~10Kbytes. 151 */ 152 153#define TK_MULTI_MAX_PATH (MAX_PATH*40) 154 155/* 156 * The following structure is used to pass information between the directory 157 * chooser function, Tk_ChooseDirectoryObjCmd(), and its dialog hook proc. 158 */ 159 160typedef struct ChooseDir { 161 Tcl_Interp *interp; /* Interp, used only if debug is turned on, 162 * for setting the "tk_dialog" variable. */ 163 int lastCtrl; /* Used by hook proc to keep track of last 164 * control that had input focus, so when OK is 165 * pressed we know whether to browse a new 166 * directory or return. */ 167 int lastIdx; /* Last item that was selected in directory 168 * browser listbox. */ 169 TCHAR path[MAX_PATH]; /* On return from choose directory dialog, 170 * holds the selected path. Cannot return 171 * selected path in ofnPtr->lpstrFile because 172 * the default dialog proc stores a '\0' in 173 * it, since, of course, no _file_ was 174 * selected. */ 175 OPENFILENAME *ofnPtr; /* pointer to the OFN structure */ 176} ChooseDir; 177 178/* 179 * The following structure is used to pass information between GetFileName/W 180 * functions and OFN dialog hook procedures. [Bug 2896501, Patch 2898255] 181 */ 182 183typedef struct OFNData { 184 Tcl_Interp *interp; /* Interp, used only if debug is turned on, 185 * for setting the "tk_dialog" variable. */ 186 int dynFileBufferSize; /* Dynamic filename buffer size, stored to 187 * avoid shrinking and expanding the buffer 188 * when selection changes */ 189 char *dynFileBuffer; /* Dynamic filename buffer, cast to WCHAR* in 190 * UNICODE procedures */ 191} OFNData; 192 193/* 194 * Definitions of functions used only in this file. 195 */ 196 197static UINT APIENTRY ChooseDirectoryValidateProc(HWND hdlg, UINT uMsg, 198 LPARAM wParam, LPARAM lParam); 199static UINT CALLBACK ColorDlgHookProc(HWND hDlg, UINT uMsg, WPARAM wParam, 200 LPARAM lParam); 201static int GetFileNameA(ClientData clientData, 202 Tcl_Interp *interp, int objc, 203 Tcl_Obj *CONST objv[], int isOpen); 204static int GetFileNameW(ClientData clientData, 205 Tcl_Interp *interp, int objc, 206 Tcl_Obj *CONST objv[], int isOpen); 207static int MakeFilter(Tcl_Interp *interp, Tcl_Obj *valuePtr, 208 Tcl_DString *dsPtr, Tcl_Obj *initialPtr, 209 int *index); 210static UINT APIENTRY OFNHookProcA(HWND hdlg, UINT uMsg, WPARAM wParam, 211 LPARAM lParam); 212static UINT APIENTRY OFNHookProcW(HWND hdlg, UINT uMsg, WPARAM wParam, 213 LPARAM lParam); 214static LRESULT CALLBACK MsgBoxCBTProc(int nCode, WPARAM wParam, LPARAM lParam); 215static void SetTkDialog(ClientData clientData); 216static char * ConvertExternalFilename(Tcl_Encoding encoding, 217 char *filename, Tcl_DString *dsPtr); 218 219/* 220 *------------------------------------------------------------------------- 221 * 222 * EatSpuriousMessageBugFix -- 223 * 224 * In the file open/save dialog, double clicking on a list item causes 225 * the dialog box to close, but an unwanted WM_LBUTTONUP message is sent 226 * to the window underneath. If the window underneath happens to be a 227 * windows control (eg a button) then it will be activated by accident. 228 * 229 * This problem does not occur in dialog boxes, because windows must do 230 * some special processing to solve the problem. (separate message 231 * processing functions are used to cope with keyboard navigation of 232 * controls.) 233 * 234 * Here is one solution. After returning, we poll the message queue for 235 * 1/4s looking for WM_LBUTTON up messages. If we see one it's consumed. 236 * If we get a WM_LBUTTONDOWN message, then we exit early, since the user 237 * must be doing something new. This fix only works for the current 238 * application, so the problem will still occur if the open dialog 239 * happens to be over another applications button. However this is a 240 * fairly rare occurrance. 241 * 242 * Results: 243 * None. 244 * 245 * Side effects: 246 * Consumes an unwanted BUTTON messages. 247 * 248 *------------------------------------------------------------------------- 249 */ 250 251static void 252EatSpuriousMessageBugFix(void) 253{ 254 MSG msg; 255 DWORD nTime = GetTickCount() + 250; 256 257 while (GetTickCount() < nTime) { 258 if (PeekMessage(&msg, 0, WM_LBUTTONDOWN, WM_LBUTTONDOWN, PM_NOREMOVE)){ 259 break; 260 } 261 PeekMessage(&msg, 0, WM_LBUTTONUP, WM_LBUTTONUP, PM_REMOVE); 262 } 263} 264 265/* 266 *------------------------------------------------------------------------- 267 * 268 * TkWinDialogDebug -- 269 * 270 * Function to turn on/off debugging support for common dialogs under 271 * windows. The variable "tk_debug" is set to the identifier of the 272 * dialog window when the modal dialog window pops up and it is safe to 273 * send messages to the dialog. 274 * 275 * Results: 276 * None. 277 * 278 * Side effects: 279 * This variable only makes sense if just one dialog is up at a time. 280 * 281 *------------------------------------------------------------------------- 282 */ 283 284void 285TkWinDialogDebug( 286 int debug) 287{ 288 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 289 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 290 291 tsdPtr->debugFlag = debug; 292} 293 294/* 295 *------------------------------------------------------------------------- 296 * 297 * Tk_ChooseColorObjCmd -- 298 * 299 * This function implements the color dialog box for the Windows 300 * platform. See the user documentation for details on what it does. 301 * 302 * Results: 303 * See user documentation. 304 * 305 * Side effects: 306 * A dialog window is created the first time this function is called. 307 * This window is not destroyed and will be reused the next time the 308 * application invokes the "tk_chooseColor" command. 309 * 310 *------------------------------------------------------------------------- 311 */ 312 313int 314Tk_ChooseColorObjCmd( 315 ClientData clientData, /* Main window associated with interpreter. */ 316 Tcl_Interp *interp, /* Current interpreter. */ 317 int objc, /* Number of arguments. */ 318 Tcl_Obj *CONST objv[]) /* Argument objects. */ 319{ 320 Tk_Window tkwin = (Tk_Window) clientData, parent; 321 HWND hWnd; 322 int i, oldMode, winCode, result; 323 CHOOSECOLOR chooseColor; 324 static int inited = 0; 325 static COLORREF dwCustColors[16]; 326 static long oldColor; /* the color selected last time */ 327 static CONST char *optionStrings[] = { 328 "-initialcolor", "-parent", "-title", NULL 329 }; 330 enum options { 331 COLOR_INITIAL, COLOR_PARENT, COLOR_TITLE 332 }; 333 334 result = TCL_OK; 335 if (inited == 0) { 336 /* 337 * dwCustColors stores the custom color which the user can modify. We 338 * store these colors in a static array so that the next time the 339 * color dialog pops up, the same set of custom colors remain in the 340 * dialog. 341 */ 342 343 for (i = 0; i < 16; i++) { 344 dwCustColors[i] = RGB(255-i * 10, i, i * 10); 345 } 346 oldColor = RGB(0xa0, 0xa0, 0xa0); 347 inited = 1; 348 } 349 350 parent = tkwin; 351 chooseColor.lStructSize = sizeof(CHOOSECOLOR); 352 chooseColor.hwndOwner = NULL; 353 chooseColor.hInstance = NULL; 354 chooseColor.rgbResult = oldColor; 355 chooseColor.lpCustColors = dwCustColors; 356 chooseColor.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ENABLEHOOK; 357 chooseColor.lCustData = (LPARAM) NULL; 358 chooseColor.lpfnHook = (LPOFNHOOKPROC) ColorDlgHookProc; 359 chooseColor.lpTemplateName = (LPTSTR) interp; 360 361 for (i = 1; i < objc; i += 2) { 362 int index; 363 char *string; 364 Tcl_Obj *optionPtr, *valuePtr; 365 366 optionPtr = objv[i]; 367 valuePtr = objv[i + 1]; 368 369 if (Tcl_GetIndexFromObj(interp, optionPtr, optionStrings, "option", 370 TCL_EXACT, &index) != TCL_OK) { 371 return TCL_ERROR; 372 } 373 if (i + 1 == objc) { 374 string = Tcl_GetString(optionPtr); 375 Tcl_AppendResult(interp, "value for \"", string, "\" missing", 376 NULL); 377 return TCL_ERROR; 378 } 379 380 string = Tcl_GetString(valuePtr); 381 switch ((enum options) index) { 382 case COLOR_INITIAL: { 383 XColor *colorPtr; 384 385 colorPtr = Tk_GetColor(interp, tkwin, string); 386 if (colorPtr == NULL) { 387 return TCL_ERROR; 388 } 389 chooseColor.rgbResult = RGB(colorPtr->red / 0x100, 390 colorPtr->green / 0x100, colorPtr->blue / 0x100); 391 break; 392 } 393 case COLOR_PARENT: 394 parent = Tk_NameToWindow(interp, string, tkwin); 395 if (parent == NULL) { 396 return TCL_ERROR; 397 } 398 break; 399 case COLOR_TITLE: 400 chooseColor.lCustData = (LPARAM) string; 401 break; 402 } 403 } 404 405 Tk_MakeWindowExist(parent); 406 chooseColor.hwndOwner = NULL; 407 hWnd = Tk_GetHWND(Tk_WindowId(parent)); 408 chooseColor.hwndOwner = hWnd; 409 410 oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); 411 winCode = ChooseColor(&chooseColor); 412 (void) Tcl_SetServiceMode(oldMode); 413 414 /* 415 * Ensure that hWnd is enabled, because it can happen that we have updated 416 * the wrapper of the parent, which causes us to leave this child disabled 417 * (Windows loses sync). 418 */ 419 420 EnableWindow(hWnd, 1); 421 422 /* 423 * Clear the interp result since anything may have happened during the 424 * modal loop. 425 */ 426 427 Tcl_ResetResult(interp); 428 429 /* 430 * 3. Process the result of the dialog 431 */ 432 433 if (winCode) { 434 /* 435 * User has selected a color 436 */ 437 char color[100]; 438 439 sprintf(color, "#%02x%02x%02x", 440 GetRValue(chooseColor.rgbResult), 441 GetGValue(chooseColor.rgbResult), 442 GetBValue(chooseColor.rgbResult)); 443 Tcl_AppendResult(interp, color, NULL); 444 oldColor = chooseColor.rgbResult; 445 result = TCL_OK; 446 } 447 448 return result; 449} 450 451/* 452 *------------------------------------------------------------------------- 453 * 454 * ColorDlgHookProc -- 455 * 456 * Provides special handling of messages for the Color common dialog box. 457 * Used to set the title when the dialog first appears. 458 * 459 * Results: 460 * The return value is 0 if the default dialog box function should handle 461 * the message, non-zero otherwise. 462 * 463 * Side effects: 464 * Changes the title of the dialog window. 465 * 466 *---------------------------------------------------------------------- 467 */ 468 469static UINT CALLBACK 470ColorDlgHookProc( 471 HWND hDlg, /* Handle to the color dialog. */ 472 UINT uMsg, /* Type of message. */ 473 WPARAM wParam, /* First message parameter. */ 474 LPARAM lParam) /* Second message parameter. */ 475{ 476 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 477 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 478 const char *title; 479 CHOOSECOLOR *ccPtr; 480 481 if (WM_INITDIALOG == uMsg) { 482 483 /* 484 * Set the title string of the dialog. 485 */ 486 487 ccPtr = (CHOOSECOLOR *) lParam; 488 title = (const char *) ccPtr->lCustData; 489 490 if ((title != NULL) && (title[0] != '\0')) { 491 Tcl_DString ds; 492 493 (*tkWinProcs->setWindowText)(hDlg, 494 Tcl_WinUtfToTChar(title, -1, &ds)); 495 Tcl_DStringFree(&ds); 496 } 497 if (tsdPtr->debugFlag) { 498 tsdPtr->debugInterp = (Tcl_Interp *) ccPtr->lpTemplateName; 499 Tcl_DoWhenIdle(SetTkDialog, (ClientData) hDlg); 500 } 501 return TRUE; 502 } 503 return FALSE; 504} 505 506/* 507 *---------------------------------------------------------------------- 508 * 509 * Tk_GetOpenFileCmd -- 510 * 511 * This function implements the "open file" dialog box for the Windows 512 * platform. See the user documentation for details on what it does. 513 * 514 * Results: 515 * See user documentation. 516 * 517 * Side effects: 518 * A dialog window is created the first this function is called. 519 * 520 *---------------------------------------------------------------------- 521 */ 522 523int 524Tk_GetOpenFileObjCmd( 525 ClientData clientData, /* Main window associated with interpreter. */ 526 Tcl_Interp *interp, /* Current interpreter. */ 527 int objc, /* Number of arguments. */ 528 Tcl_Obj *CONST objv[]) /* Argument objects. */ 529{ 530 if (TkWinGetPlatformId() == VER_PLATFORM_WIN32_NT) { 531 return GetFileNameW(clientData, interp, objc, objv, 1); 532 } else { 533 return GetFileNameA(clientData, interp, objc, objv, 1); 534 } 535} 536 537/* 538 *---------------------------------------------------------------------- 539 * 540 * Tk_GetSaveFileCmd -- 541 * 542 * Same as Tk_GetOpenFileCmd but opens a "save file" dialog box 543 * instead 544 * 545 * Results: 546 * Same as Tk_GetOpenFileCmd. 547 * 548 * Side effects: 549 * Same as Tk_GetOpenFileCmd. 550 * 551 *---------------------------------------------------------------------- 552 */ 553 554int 555Tk_GetSaveFileObjCmd( 556 ClientData clientData, /* Main window associated with interpreter. */ 557 Tcl_Interp *interp, /* Current interpreter. */ 558 int objc, /* Number of arguments. */ 559 Tcl_Obj *CONST objv[]) /* Argument objects. */ 560{ 561 if (TkWinGetPlatformId() == VER_PLATFORM_WIN32_NT) { 562 return GetFileNameW(clientData, interp, objc, objv, 0); 563 } else { 564 return GetFileNameA(clientData, interp, objc, objv, 0); 565 } 566} 567 568/* 569 *---------------------------------------------------------------------- 570 * 571 * GetFileNameW -- 572 * 573 * Calls GetOpenFileName() or GetSaveFileName(). 574 * 575 * Results: 576 * See user documentation. 577 * 578 * Side effects: 579 * See user documentation. 580 * 581 *---------------------------------------------------------------------- 582 */ 583 584static int 585GetFileNameW( 586 ClientData clientData, /* Main window associated with interpreter. */ 587 Tcl_Interp *interp, /* Current interpreter. */ 588 int objc, /* Number of arguments. */ 589 Tcl_Obj *CONST objv[], /* Argument objects. */ 590 int open) /* 1 to call GetOpenFileName(), 0 to call 591 * GetSaveFileName(). */ 592{ 593 OPENFILENAMEW ofn; 594 WCHAR file[TK_MULTI_MAX_PATH]; 595 OFNData ofnData; 596 int cdlgerr; 597 int filterIndex = 0, result = TCL_ERROR, winCode, oldMode, i, multi = 0; 598 char *extension = NULL, *filter = NULL, *title = NULL; 599 Tk_Window tkwin = (Tk_Window) clientData; 600 HWND hWnd; 601 Tcl_Obj *filterObj = NULL, *initialTypeObj = NULL, *typeVariableObj = NULL; 602 Tcl_DString utfFilterString, utfDirString, ds; 603 Tcl_DString extString, filterString, dirString, titleString; 604 Tcl_Encoding unicodeEncoding = TkWinGetUnicodeEncoding(); 605 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 606 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 607 static CONST char *saveOptionStrings[] = { 608 "-defaultextension", "-filetypes", "-initialdir", "-initialfile", 609 "-parent", "-title", "-typevariable", NULL 610 }; 611 static CONST char *openOptionStrings[] = { 612 "-defaultextension", "-filetypes", "-initialdir", "-initialfile", 613 "-multiple", "-parent", "-title", "-typevariable", NULL 614 }; 615 CONST char **optionStrings; 616 617 enum options { 618 FILE_DEFAULT, FILE_TYPES, FILE_INITDIR, FILE_INITFILE, 619 FILE_MULTIPLE, FILE_PARENT, FILE_TITLE, FILE_TYPEVARIABLE 620 }; 621 622 file[0] = '\0'; 623 ZeroMemory(&ofnData, sizeof(OFNData)); 624 Tcl_DStringInit(&utfFilterString); 625 Tcl_DStringInit(&utfDirString); 626 627 /* 628 * Parse the arguments. 629 */ 630 631 if (open) { 632 optionStrings = openOptionStrings; 633 } else { 634 optionStrings = saveOptionStrings; 635 } 636 637 for (i = 1; i < objc; i += 2) { 638 int index; 639 char *string; 640 Tcl_Obj *optionPtr, *valuePtr; 641 642 optionPtr = objv[i]; 643 valuePtr = objv[i + 1]; 644 645 if (Tcl_GetIndexFromObj(interp, optionPtr, optionStrings, 646 "option", 0, &index) != TCL_OK) { 647 goto end; 648 } 649 650 /* 651 * We want to maximize code sharing between the open and save file 652 * dialog implementations; in particular, the switch statement below. 653 * We use different sets of option strings from the GetIndexFromObj 654 * call above, but a single enumeration for both. The save file dialog 655 * doesn't support -multiple, but it falls in the middle of the 656 * enumeration. Ultimately, this means that when the index found by 657 * GetIndexFromObj is >= FILE_MULTIPLE, when doing a save file dialog, 658 * we have to increment the index, so that it matches the open file 659 * dialog enumeration. 660 */ 661 662 if (!open && index >= FILE_MULTIPLE) { 663 index++; 664 } 665 if (i + 1 == objc) { 666 string = Tcl_GetString(optionPtr); 667 Tcl_AppendResult(interp, "value for \"", string, "\" missing", 668 NULL); 669 goto end; 670 } 671 672 string = Tcl_GetString(valuePtr); 673 switch ((enum options) index) { 674 case FILE_DEFAULT: 675 if (string[0] == '.') { 676 string++; 677 } 678 extension = string; 679 break; 680 case FILE_TYPES: 681 filterObj = valuePtr; 682 break; 683 case FILE_INITDIR: 684 Tcl_DStringFree(&utfDirString); 685 if (Tcl_TranslateFileName(interp, string, 686 &utfDirString) == NULL) { 687 goto end; 688 } 689 break; 690 case FILE_INITFILE: 691 if (Tcl_TranslateFileName(interp, string, &ds) == NULL) { 692 goto end; 693 } 694 Tcl_UtfToExternal(NULL, unicodeEncoding, Tcl_DStringValue(&ds), 695 Tcl_DStringLength(&ds), 0, NULL, (char *) file, 696 sizeof(file), NULL, NULL, NULL); 697 Tcl_DStringFree(&ds); 698 break; 699 case FILE_MULTIPLE: 700 if (Tcl_GetBooleanFromObj(interp, valuePtr, &multi) != TCL_OK) { 701 return TCL_ERROR; 702 } 703 break; 704 case FILE_PARENT: 705 tkwin = Tk_NameToWindow(interp, string, tkwin); 706 if (tkwin == NULL) { 707 goto end; 708 } 709 break; 710 case FILE_TITLE: 711 title = string; 712 break; 713 case FILE_TYPEVARIABLE: 714 typeVariableObj = valuePtr; 715 initialTypeObj = Tcl_ObjGetVar2(interp, typeVariableObj, NULL, 716 TCL_GLOBAL_ONLY); 717 break; 718 } 719 } 720 721 if (MakeFilter(interp, filterObj, &utfFilterString, initialTypeObj, 722 &filterIndex) != TCL_OK) { 723 goto end; 724 } 725 filter = Tcl_DStringValue(&utfFilterString); 726 727 Tk_MakeWindowExist(tkwin); 728 hWnd = Tk_GetHWND(Tk_WindowId(tkwin)); 729 730 ZeroMemory(&ofn, sizeof(OPENFILENAMEW)); 731 if (LOBYTE(LOWORD(GetVersion())) < 5) { 732 ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; 733 } else { 734 ofn.lStructSize = sizeof(OPENFILENAMEW); 735 } 736 ofn.hwndOwner = hWnd; 737 ofn.hInstance = TkWinGetHInstance(ofn.hwndOwner); 738 ofn.lpstrFile = (WCHAR *) file; 739 ofn.nMaxFile = TK_MULTI_MAX_PATH; 740 ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR 741 | OFN_EXPLORER | OFN_ENABLEHOOK; 742 ofn.lpfnHook = (LPOFNHOOKPROC) OFNHookProcW; 743 ofn.lCustData = (LPARAM) &ofnData; 744 745 if (open != 0) { 746 ofn.Flags |= OFN_FILEMUSTEXIST; 747 } else { 748 ofn.Flags |= OFN_OVERWRITEPROMPT; 749 } 750 if (tsdPtr->debugFlag != 0) { 751 ofnData.interp = interp; 752 } 753 if (multi != 0) { 754 ofn.Flags |= OFN_ALLOWMULTISELECT; 755 756 /* 757 * Starting buffer size. The buffer will be expanded by the OFN dialog 758 * procedure when necessary 759 */ 760 761 ofnData.dynFileBufferSize = 1024; 762 ofnData.dynFileBuffer = ckalloc(1024); 763 } 764 765 if (extension != NULL) { 766 Tcl_UtfToExternalDString(unicodeEncoding, extension, -1, &extString); 767 ofn.lpstrDefExt = (WCHAR *) Tcl_DStringValue(&extString); 768 } 769 770 Tcl_UtfToExternalDString(unicodeEncoding, 771 Tcl_DStringValue(&utfFilterString), 772 Tcl_DStringLength(&utfFilterString), &filterString); 773 ofn.lpstrFilter = (WCHAR *) Tcl_DStringValue(&filterString); 774 ofn.nFilterIndex = filterIndex; 775 776 if (Tcl_DStringValue(&utfDirString)[0] != '\0') { 777 Tcl_UtfToExternalDString(unicodeEncoding, 778 Tcl_DStringValue(&utfDirString), 779 Tcl_DStringLength(&utfDirString), &dirString); 780 } else { 781 /* 782 * NT 5.0 changed the meaning of lpstrInitialDir, so we have to ensure 783 * that we set the [pwd] if the user didn't specify anything else. 784 */ 785 786 Tcl_DString cwd; 787 788 Tcl_DStringFree(&utfDirString); 789 if ((Tcl_GetCwd(interp, &utfDirString) == NULL) || 790 (Tcl_TranslateFileName(interp, 791 Tcl_DStringValue(&utfDirString), &cwd) == NULL)) { 792 Tcl_ResetResult(interp); 793 } else { 794 Tcl_UtfToExternalDString(unicodeEncoding, Tcl_DStringValue(&cwd), 795 Tcl_DStringLength(&cwd), &dirString); 796 } 797 Tcl_DStringFree(&cwd); 798 } 799 ofn.lpstrInitialDir = (WCHAR *) Tcl_DStringValue(&dirString); 800 801 if (title != NULL) { 802 Tcl_UtfToExternalDString(unicodeEncoding, title, -1, &titleString); 803 ofn.lpstrTitle = (WCHAR *) Tcl_DStringValue(&titleString); 804 } 805 806 /* 807 * Popup the dialog. 808 */ 809 810 oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); 811 if (open != 0) { 812 winCode = GetOpenFileNameW(&ofn); 813 } else { 814 winCode = GetSaveFileNameW(&ofn); 815 } 816 Tcl_SetServiceMode(oldMode); 817 EatSpuriousMessageBugFix(); 818 819 /* 820 * Ensure that hWnd is enabled, because it can happen that we have updated 821 * the wrapper of the parent, which causes us to leave this child disabled 822 * (Windows loses sync). 823 */ 824 825 EnableWindow(hWnd, 1); 826 827 /* 828 * Clear the interp result since anything may have happened during the 829 * modal loop. 830 */ 831 832 Tcl_ResetResult(interp); 833 834 /* 835 * Process the results. 836 * 837 * Use the CommDlgExtendedError() function to retrieve the error code. 838 * This function can return one of about two dozen codes; most of these 839 * indicate some sort of gross system failure (insufficient memory, bad 840 * window handles, etc.). Most of the error codes will be ignored; as we 841 * find we want more specific error messages for particular errors, we can 842 * extend the code as needed. 843 */ 844 845 cdlgerr = CommDlgExtendedError(); 846 847 /* 848 * We now allow FNERR_BUFFERTOOSMALL when multiselection is enabled. The 849 * filename buffer has been dynamically allocated by the OFN dialog 850 * procedure to accomodate all selected files. 851 */ 852 853 if ((winCode != 0) 854 || ((cdlgerr == FNERR_BUFFERTOOSMALL) 855 && (ofn.Flags & OFN_ALLOWMULTISELECT))) { 856 if (ofn.Flags & OFN_ALLOWMULTISELECT) { 857 /* 858 * The result in dynFileBuffer contains many items, separated by 859 * NUL characters. It is terminated with two nulls in a row. The 860 * first element is the directory path. 861 */ 862 863 WCHAR *files = (WCHAR *) ofnData.dynFileBuffer; 864 Tcl_Obj *returnList = Tcl_NewObj(); 865 int count = 0; 866 867 /* 868 * Get directory. 869 */ 870 871 (void) ConvertExternalFilename(unicodeEncoding, (char *) files, 872 &ds); 873 874 while (*files != '\0') { 875 while (*files != '\0') { 876 files++; 877 } 878 files++; 879 if (*files != '\0') { 880 Tcl_Obj *fullnameObj; 881 Tcl_DString filenameBuf; 882 883 count++; 884 (void) ConvertExternalFilename(unicodeEncoding, 885 (char *) files, &filenameBuf); 886 887 fullnameObj = Tcl_NewStringObj(Tcl_DStringValue(&ds), 888 Tcl_DStringLength(&ds)); 889 Tcl_AppendToObj(fullnameObj, "/", -1); 890 Tcl_AppendToObj(fullnameObj, Tcl_DStringValue(&filenameBuf), 891 Tcl_DStringLength(&filenameBuf)); 892 Tcl_DStringFree(&filenameBuf); 893 Tcl_ListObjAppendElement(NULL, returnList, fullnameObj); 894 } 895 } 896 897 if (count == 0) { 898 /* 899 * Only one file was returned. 900 */ 901 902 Tcl_ListObjAppendElement(NULL, returnList, 903 Tcl_NewStringObj(Tcl_DStringValue(&ds), 904 Tcl_DStringLength(&ds))); 905 } 906 Tcl_SetObjResult(interp, returnList); 907 Tcl_DStringFree(&ds); 908 } else { 909 Tcl_AppendResult(interp, ConvertExternalFilename(unicodeEncoding, 910 (char *) ofn.lpstrFile, &ds), NULL); 911 Tcl_DStringFree(&ds); 912 } 913 result = TCL_OK; 914 if ((ofn.nFilterIndex > 0) && 915 Tcl_GetCharLength(Tcl_GetObjResult(interp)) > 0 && 916 typeVariableObj && filterObj) { 917 int listObjc, count; 918 Tcl_Obj **listObjv = NULL; 919 Tcl_Obj **typeInfo = NULL; 920 921 if (Tcl_ListObjGetElements(interp, filterObj, 922 &listObjc, &listObjv) != TCL_OK) { 923 result = TCL_ERROR; 924 } else if (Tcl_ListObjGetElements(interp, 925 listObjv[ofn.nFilterIndex - 1], &count, 926 &typeInfo) != TCL_OK) { 927 result = TCL_ERROR; 928 } else if (Tcl_ObjSetVar2(interp, typeVariableObj, NULL, 929 typeInfo[0], TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { 930 result = TCL_ERROR; 931 } 932 } 933 } else if (cdlgerr == FNERR_INVALIDFILENAME) { 934 Tcl_SetResult(interp, "invalid filename \"", TCL_STATIC); 935 Tcl_AppendResult(interp, ConvertExternalFilename(unicodeEncoding, 936 (char *) ofn.lpstrFile, &ds), "\"", NULL); 937 Tcl_DStringFree(&ds); 938 } else { 939 result = TCL_OK; 940 } 941 942 if (ofn.lpstrTitle != NULL) { 943 Tcl_DStringFree(&titleString); 944 } 945 if (ofn.lpstrInitialDir != NULL) { 946 Tcl_DStringFree(&dirString); 947 } 948 Tcl_DStringFree(&filterString); 949 if (ofn.lpstrDefExt != NULL) { 950 Tcl_DStringFree(&extString); 951 } 952 953 end: 954 Tcl_DStringFree(&utfDirString); 955 Tcl_DStringFree(&utfFilterString); 956 if (ofnData.dynFileBuffer != NULL) { 957 ckfree(ofnData.dynFileBuffer); 958 ofnData.dynFileBuffer = NULL; 959 } 960 961 return result; 962} 963 964/* 965 *------------------------------------------------------------------------- 966 * 967 * OFNHookProcW -- 968 * 969 * Dialog box hook function. This is used to sets the "tk_dialog" 970 * variable for test/debugging when the dialog is ready to receive 971 * messages. When multiple file selection is enabled this function 972 * is used to process the list of names. 973 * 974 * Results: 975 * Returns 0 to allow default processing of messages to occur. 976 * 977 * Side effects: 978 * None. 979 * 980 *------------------------------------------------------------------------- 981 */ 982 983static UINT APIENTRY 984OFNHookProcW( 985 HWND hdlg, /* Handle to child dialog window. */ 986 UINT uMsg, /* Message identifier */ 987 WPARAM wParam, /* Message parameter */ 988 LPARAM lParam) /* Message parameter */ 989{ 990 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 991 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 992 OPENFILENAMEW *ofnPtr; 993 OFNData *ofnData; 994 995 if (uMsg == WM_INITDIALOG) { 996 TkWinSetUserData(hdlg, lParam); 997 } else if (uMsg == WM_NOTIFY) { 998 OFNOTIFYW *notifyPtr = (OFNOTIFYW *) lParam; 999 1000 /* 1001 * This is weird... or not. The CDN_FILEOK is NOT sent when the selection 1002 * exceeds declared buffer size (the nMaxFile member of the OPENFILENAMEW 1003 * struct passed to GetOpenFileNameW function). So, we have to rely on 1004 * the most recent CDN_SELCHANGE then. Unfortunately this means, that 1005 * gathering the selected filenames happens twice when they fit into the 1006 * declared buffer. Luckily, it's not frequent operation so it should 1007 * not incur any noticeable delay. See [tktoolkit-Bugs-2987995] 1008 */ 1009 if (notifyPtr->hdr.code == CDN_FILEOK || 1010 notifyPtr->hdr.code == CDN_SELCHANGE) { 1011 int dirsize, selsize; 1012 WCHAR *buffer; 1013 int buffersize; 1014 1015 /* 1016 * Change of selection. Unscramble the unholy mess that's in the 1017 * selection buffer, resizing it if necessary. 1018 */ 1019 1020 ofnPtr = notifyPtr->lpOFN; 1021 ofnData = (OFNData *) ofnPtr->lCustData; 1022 buffer = (WCHAR *) ofnData->dynFileBuffer; 1023 hdlg = GetParent(hdlg); 1024 1025 selsize = SendMessageW(hdlg, CDM_GETSPEC, 0, 0); 1026 dirsize = SendMessageW(hdlg, CDM_GETFOLDERPATH, 0, 0); 1027 buffersize = (selsize + dirsize + 1) * 2; 1028 1029 if (selsize > 1) { 1030 if (ofnData->dynFileBufferSize < buffersize) { 1031 buffer = (WCHAR *) ckrealloc((char *) buffer, buffersize); 1032 ofnData->dynFileBufferSize = buffersize; 1033 ofnData->dynFileBuffer = (char *) buffer; 1034 } 1035 1036 SendMessageW(hdlg, CDM_GETFOLDERPATH, dirsize, (int) buffer); 1037 buffer += dirsize; 1038 1039 SendMessageW(hdlg, CDM_GETSPEC, selsize, (int) buffer); 1040 1041 /* 1042 * If there are multiple files, delete the quotes and change 1043 * every second quote to NULL terminator 1044 */ 1045 1046 if (buffer[0] == '"') { 1047 BOOL findquote = TRUE; 1048 WCHAR *tmp = buffer; 1049 1050 while(*buffer != '\0') { 1051 if (findquote) { 1052 if (*buffer == '"') { 1053 findquote = FALSE; 1054 } 1055 buffer++; 1056 } else { 1057 if (*buffer == '"') { 1058 findquote = TRUE; 1059 *buffer = '\0'; 1060 } 1061 *tmp++ = *buffer++; 1062 } 1063 } 1064 *tmp = '\0'; /* Second NULL terminator. */ 1065 } else { 1066 buffer[selsize] = '\0'; /* Second NULL terminator. */ 1067 1068 /* 1069 * Replace directory terminating NULL with a backslash. 1070 */ 1071 1072 buffer--; 1073 *buffer = '\\'; 1074 } 1075 } else { 1076 /* 1077 * Nothing is selected, so just empty the string. 1078 */ 1079 1080 if (buffer != NULL) { 1081 *buffer = '\0'; 1082 } 1083 } 1084 } 1085 } else if (uMsg == WM_WINDOWPOSCHANGED) { 1086 /* 1087 * This message is delivered at the right time to enable Tk to set the 1088 * debug information. Unhooks itself so it won't set the debug 1089 * information every time it gets a WM_WINDOWPOSCHANGED message. 1090 */ 1091 1092 ofnPtr = (OPENFILENAMEW *) TkWinGetUserData(hdlg); 1093 if (ofnPtr != NULL) { 1094 ofnData = (OFNData *) ofnPtr->lCustData; 1095 if (ofnData->interp != NULL) { 1096 hdlg = GetParent(hdlg); 1097 tsdPtr->debugInterp = ofnData->interp; 1098 Tcl_DoWhenIdle(SetTkDialog, hdlg); 1099 } 1100 TkWinSetUserData(hdlg, NULL); 1101 } 1102 } 1103 return 0; 1104} 1105 1106/* 1107 *---------------------------------------------------------------------- 1108 * 1109 * GetFileNameA -- 1110 * 1111 * Calls GetOpenFileName() or GetSaveFileName(). 1112 * 1113 * Results: 1114 * See user documentation. 1115 * 1116 * Side effects: 1117 * See user documentation. 1118 * 1119 *---------------------------------------------------------------------- 1120 */ 1121 1122static int 1123GetFileNameA( 1124 ClientData clientData, /* Main window associated with interpreter. */ 1125 Tcl_Interp *interp, /* Current interpreter. */ 1126 int objc, /* Number of arguments. */ 1127 Tcl_Obj *CONST objv[], /* Argument objects. */ 1128 int open) /* 1 to call GetOpenFileName(), 0 to call 1129 * GetSaveFileName(). */ 1130{ 1131 OPENFILENAME ofn; 1132 TCHAR file[TK_MULTI_MAX_PATH], savePath[MAX_PATH]; 1133 OFNData ofnData; 1134 int cdlgerr; 1135 int filterIndex = 0, result = TCL_ERROR, winCode, oldMode, i, multi = 0; 1136 char *extension = NULL, *filter = NULL, *title = NULL; 1137 Tk_Window tkwin = (Tk_Window) clientData; 1138 HWND hWnd; 1139 Tcl_Obj *filterObj = NULL, *initialTypeObj = NULL, *typeVariableObj = NULL; 1140 Tcl_DString utfFilterString, utfDirString, ds; 1141 Tcl_DString extString, filterString, dirString, titleString; 1142 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 1143 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 1144 static CONST char *saveOptionStrings[] = { 1145 "-defaultextension", "-filetypes", "-initialdir", "-initialfile", 1146 "-parent", "-title", "-typevariable", NULL 1147 }; 1148 static CONST char *openOptionStrings[] = { 1149 "-defaultextension", "-filetypes", "-initialdir", "-initialfile", 1150 "-multiple", "-parent", "-title", "-typevariable", NULL 1151 }; 1152 CONST char **optionStrings; 1153 1154 enum options { 1155 FILE_DEFAULT, FILE_TYPES, FILE_INITDIR, FILE_INITFILE, 1156 FILE_MULTIPLE, FILE_PARENT, FILE_TITLE, FILE_TYPEVARIABLE 1157 }; 1158 1159 file[0] = '\0'; 1160 ZeroMemory(&ofnData, sizeof(OFNData)); 1161 Tcl_DStringInit(&utfFilterString); 1162 Tcl_DStringInit(&utfDirString); 1163 1164 /* 1165 * Parse the arguments. 1166 */ 1167 1168 if (open) { 1169 optionStrings = openOptionStrings; 1170 } else { 1171 optionStrings = saveOptionStrings; 1172 } 1173 1174 for (i = 1; i < objc; i += 2) { 1175 int index; 1176 char *string; 1177 Tcl_Obj *optionPtr, *valuePtr; 1178 1179 optionPtr = objv[i]; 1180 valuePtr = objv[i + 1]; 1181 1182 if (Tcl_GetIndexFromObj(interp, optionPtr, optionStrings, "option", 0, 1183 &index) != TCL_OK) { 1184 goto end; 1185 } 1186 1187 /* 1188 * We want to maximize code sharing between the open and save file 1189 * dialog implementations; in particular, the switch statement below. 1190 * We use different sets of option strings from the GetIndexFromObj 1191 * call above, but a single enumeration for both. The save file dialog 1192 * doesn't support -multiple, but it falls in the middle of the 1193 * enumeration. Ultimately, this means that when the index found by 1194 * GetIndexFromObj is >= FILE_MULTIPLE, when doing a save file dialog, 1195 * we have to increment the index, so that it matches the open file 1196 * dialog enumeration. 1197 */ 1198 1199 if (!open && index >= FILE_MULTIPLE) { 1200 index++; 1201 } 1202 if (i + 1 == objc) { 1203 string = Tcl_GetString(optionPtr); 1204 Tcl_AppendResult(interp, "value for \"", string, "\" missing", 1205 NULL); 1206 goto end; 1207 } 1208 1209 string = Tcl_GetString(valuePtr); 1210 switch ((enum options) index) { 1211 case FILE_DEFAULT: 1212 if (string[0] == '.') { 1213 string++; 1214 } 1215 extension = string; 1216 break; 1217 case FILE_TYPES: 1218 filterObj = valuePtr; 1219 break; 1220 case FILE_INITDIR: 1221 Tcl_DStringFree(&utfDirString); 1222 if (Tcl_TranslateFileName(interp, string, &utfDirString) == NULL) { 1223 goto end; 1224 } 1225 break; 1226 case FILE_INITFILE: 1227 if (Tcl_TranslateFileName(interp, string, &ds) == NULL) { 1228 goto end; 1229 } 1230 Tcl_UtfToExternal(NULL, NULL, Tcl_DStringValue(&ds), 1231 Tcl_DStringLength(&ds), 0, NULL, (char *) file, 1232 sizeof(file), NULL, NULL, NULL); 1233 Tcl_DStringFree(&ds); 1234 break; 1235 case FILE_MULTIPLE: 1236 if (Tcl_GetBooleanFromObj(interp, valuePtr, &multi) != TCL_OK) { 1237 return TCL_ERROR; 1238 } 1239 break; 1240 case FILE_PARENT: 1241 tkwin = Tk_NameToWindow(interp, string, tkwin); 1242 if (tkwin == NULL) { 1243 goto end; 1244 } 1245 break; 1246 case FILE_TITLE: 1247 title = string; 1248 break; 1249 case FILE_TYPEVARIABLE: 1250 typeVariableObj = valuePtr; 1251 initialTypeObj = Tcl_ObjGetVar2(interp, typeVariableObj, NULL, 1252 TCL_GLOBAL_ONLY); 1253 break; 1254 } 1255 } 1256 1257 if (MakeFilter(interp, filterObj, &utfFilterString, initialTypeObj, 1258 &filterIndex) != TCL_OK) { 1259 goto end; 1260 } 1261 filter = Tcl_DStringValue(&utfFilterString); 1262 1263 Tk_MakeWindowExist(tkwin); 1264 hWnd = Tk_GetHWND(Tk_WindowId(tkwin)); 1265 1266 ZeroMemory(&ofn, sizeof(OPENFILENAMEA)); 1267 if (LOBYTE(LOWORD(GetVersion())) < 5) { 1268 ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; 1269 } else { 1270 ofn.lStructSize = sizeof(ofn); 1271 } 1272 ofn.hwndOwner = hWnd; 1273 ofn.hInstance = TkWinGetHInstance(ofn.hwndOwner); 1274 ofn.lpstrFilter = NULL; 1275 ofn.lpstrCustomFilter = NULL; 1276 ofn.nMaxCustFilter = 0; 1277 ofn.lpstrFile = (LPTSTR) file; 1278 ofn.nMaxFile = TK_MULTI_MAX_PATH; 1279 ofn.lpstrFileTitle = NULL; 1280 ofn.nMaxFileTitle = 0; 1281 ofn.lpstrInitialDir = NULL; 1282 ofn.lpstrTitle = NULL; 1283 ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR 1284 | OFN_EXPLORER | OFN_ENABLEHOOK; 1285 ofn.nFileOffset = 0; 1286 ofn.nFileExtension = 0; 1287 ofn.lpstrDefExt = NULL; 1288 ofn.lpfnHook = (LPOFNHOOKPROC) OFNHookProcA; 1289 ofn.lCustData = (LPARAM) &ofnData; 1290 ofn.lpTemplateName = NULL; 1291 1292 if (open != 0) { 1293 ofn.Flags |= OFN_FILEMUSTEXIST; 1294 } else { 1295 ofn.Flags |= OFN_OVERWRITEPROMPT; 1296 } 1297 1298 if (tsdPtr->debugFlag != 0) { 1299 ofnData.interp = interp; 1300 } 1301 1302 if (multi != 0) { 1303 ofn.Flags |= OFN_ALLOWMULTISELECT; 1304 1305 /* 1306 * Starting buffer size. The buffer will be expanded by the OFN dialog 1307 * procedure when necessary 1308 */ 1309 1310 ofnData.dynFileBufferSize = 1024; 1311 ofnData.dynFileBuffer = ckalloc(1024); 1312 } 1313 1314 if (extension != NULL) { 1315 Tcl_UtfToExternalDString(NULL, extension, -1, &extString); 1316 ofn.lpstrDefExt = (LPTSTR) Tcl_DStringValue(&extString); 1317 } 1318 Tcl_UtfToExternalDString(NULL, Tcl_DStringValue(&utfFilterString), 1319 Tcl_DStringLength(&utfFilterString), &filterString); 1320 ofn.lpstrFilter = (LPTSTR) Tcl_DStringValue(&filterString); 1321 ofn.nFilterIndex = filterIndex; 1322 1323 if (Tcl_DStringValue(&utfDirString)[0] != '\0') { 1324 Tcl_UtfToExternalDString(NULL, Tcl_DStringValue(&utfDirString), 1325 Tcl_DStringLength(&utfDirString), &dirString); 1326 } else { 1327 /* 1328 * NT 5.0 changed the meaning of lpstrInitialDir, so we have to ensure 1329 * that we set the [pwd] if the user didn't specify anything else. 1330 */ 1331 1332 Tcl_DString cwd; 1333 1334 Tcl_DStringFree(&utfDirString); 1335 if ((Tcl_GetCwd(interp, &utfDirString) == NULL) || 1336 (Tcl_TranslateFileName(interp, 1337 Tcl_DStringValue(&utfDirString), &cwd) == NULL)) { 1338 Tcl_ResetResult(interp); 1339 } else { 1340 Tcl_UtfToExternalDString(NULL, Tcl_DStringValue(&cwd), 1341 Tcl_DStringLength(&cwd), &dirString); 1342 } 1343 Tcl_DStringFree(&cwd); 1344 } 1345 ofn.lpstrInitialDir = (LPTSTR) Tcl_DStringValue(&dirString); 1346 1347 if (title != NULL) { 1348 Tcl_UtfToExternalDString(NULL, title, -1, &titleString); 1349 ofn.lpstrTitle = (LPTSTR) Tcl_DStringValue(&titleString); 1350 } 1351 1352 /* 1353 * Popup the dialog. 1354 */ 1355 1356 GetCurrentDirectory(MAX_PATH, savePath); 1357 oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); 1358 if (open != 0) { 1359 winCode = GetOpenFileName(&ofn); 1360 } else { 1361 winCode = GetSaveFileName(&ofn); 1362 } 1363 Tcl_SetServiceMode(oldMode); 1364 EatSpuriousMessageBugFix(); 1365 SetCurrentDirectory(savePath); 1366 1367 /* 1368 * Ensure that hWnd is enabled, because it can happen that we have updated 1369 * the wrapper of the parent, which causes us to leave this child disabled 1370 * (Windows loses sync). 1371 */ 1372 1373 EnableWindow(hWnd, 1); 1374 1375 /* 1376 * Clear the interp result since anything may have happened during the 1377 * modal loop. 1378 */ 1379 1380 Tcl_ResetResult(interp); 1381 1382 /* 1383 * Process the results. 1384 * 1385 * Use the CommDlgExtendedError() function to retrieve the error code. 1386 * This function can return one of about two dozen codes; most of these 1387 * indicate some sort of gross system failure (insufficient memory, bad 1388 * window handles, etc.) Most of the error codes will be ignored; as we 1389 * find we want specific error messages for particular errors, we can 1390 * extend the code as needed. 1391 */ 1392 1393 cdlgerr = CommDlgExtendedError(); 1394 1395 /* 1396 * We now allow FNERR_BUFFERTOOSMALL when multiselection is enabled. The 1397 * filename buffer has been dynamically allocated by the OFN dialog 1398 * procedure to accomodate all selected files. 1399 */ 1400 1401 if ((winCode != 0) 1402 || ((cdlgerr == FNERR_BUFFERTOOSMALL) 1403 && (ofn.Flags & OFN_ALLOWMULTISELECT))) { 1404 if (ofn.Flags & OFN_ALLOWMULTISELECT) { 1405 /* 1406 * The result in dynFileBuffer contains many items, separated by 1407 * NUL characters. It is terminated with two nulls in a row. The 1408 * first element is the directory path (if multiple files are 1409 * selected) or the only returned file (if only a single file has 1410 * been chosen). 1411 */ 1412 1413 char *files = ofnData.dynFileBuffer; 1414 Tcl_Obj *returnList = Tcl_NewObj(); 1415 int count = 0; 1416 1417 /* 1418 * Get directory. 1419 */ 1420 1421 (void) ConvertExternalFilename(NULL, (char *) files, &ds); 1422 1423 while (*files != '\0') { 1424 while (*files != '\0') { 1425 files++; 1426 } 1427 files++; 1428 if (*files != '\0') { 1429 Tcl_Obj *fullnameObj; 1430 Tcl_DString filename; 1431 1432 count++; 1433 (void) ConvertExternalFilename(NULL, (char *) files, 1434 &filename); 1435 fullnameObj = Tcl_NewStringObj(Tcl_DStringValue(&ds), 1436 Tcl_DStringLength(&ds)); 1437 Tcl_AppendToObj(fullnameObj, "/", -1); 1438 Tcl_AppendToObj(fullnameObj, Tcl_DStringValue(&filename), 1439 Tcl_DStringLength(&filename)); 1440 Tcl_DStringFree(&filename); 1441 Tcl_ListObjAppendElement(NULL, returnList, fullnameObj); 1442 } 1443 } 1444 if (count == 0) { 1445 /* 1446 * Only one file was returned. 1447 */ 1448 1449 Tcl_ListObjAppendElement(NULL, returnList, Tcl_NewStringObj( 1450 Tcl_DStringValue(&ds), Tcl_DStringLength(&ds))); 1451 } 1452 Tcl_SetObjResult(interp, returnList); 1453 Tcl_DStringFree(&ds); 1454 } else { 1455 Tcl_AppendResult(interp, ConvertExternalFilename(NULL, 1456 (char *) ofn.lpstrFile, &ds), NULL); 1457 Tcl_DStringFree(&ds); 1458 } 1459 result = TCL_OK; 1460 if ((ofn.nFilterIndex > 0) && 1461 (Tcl_GetCharLength(Tcl_GetObjResult(interp)) > 0) && 1462 typeVariableObj && filterObj) { 1463 int listObjc, count; 1464 Tcl_Obj **listObjv = NULL; 1465 Tcl_Obj **typeInfo = NULL; 1466 1467 if (Tcl_ListObjGetElements(interp, filterObj, &listObjc, 1468 &listObjv) != TCL_OK) { 1469 result = TCL_ERROR; 1470 } else if (Tcl_ListObjGetElements(interp, 1471 listObjv[ofn.nFilterIndex - 1], &count, 1472 &typeInfo) != TCL_OK) { 1473 result = TCL_ERROR; 1474 } else if (Tcl_ObjSetVar2(interp, typeVariableObj, NULL, 1475 typeInfo[0], TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) == NULL) { 1476 result = TCL_ERROR; 1477 } 1478 } 1479 } else if (cdlgerr == FNERR_INVALIDFILENAME) { 1480 Tcl_SetResult(interp, "invalid filename \"", TCL_STATIC); 1481 Tcl_AppendResult(interp, ConvertExternalFilename(NULL, 1482 (char *) ofn.lpstrFile, &ds), "\"", NULL); 1483 Tcl_DStringFree(&ds); 1484 } else { 1485 result = TCL_OK; 1486 } 1487 1488 if (ofn.lpstrTitle != NULL) { 1489 Tcl_DStringFree(&titleString); 1490 } 1491 if (ofn.lpstrInitialDir != NULL) { 1492 Tcl_DStringFree(&dirString); 1493 } 1494 Tcl_DStringFree(&filterString); 1495 if (ofn.lpstrDefExt != NULL) { 1496 Tcl_DStringFree(&extString); 1497 } 1498 1499 end: 1500 Tcl_DStringFree(&utfDirString); 1501 Tcl_DStringFree(&utfFilterString); 1502 if (ofnData.dynFileBuffer != NULL) { 1503 ckfree(ofnData.dynFileBuffer); 1504 ofnData.dynFileBuffer = NULL; 1505 } 1506 1507 return result; 1508} 1509 1510/* 1511 *------------------------------------------------------------------------- 1512 * 1513 * OFNHookProcA -- 1514 * 1515 * Dialog box hook function. This is used to sets the "tk_dialog" 1516 * variable for test/debugging when the dialog is ready to receive 1517 * messages. When multiple file selection is enabled this function 1518 * is used to process the list of names. 1519 * 1520 * Results: 1521 * Returns 0 to allow default processing of messages to occur. 1522 * 1523 * Side effects: 1524 * None. 1525 * 1526 *------------------------------------------------------------------------- 1527 */ 1528 1529static UINT APIENTRY 1530OFNHookProcA( 1531 HWND hdlg, /* handle to child dialog window */ 1532 UINT uMsg, /* message identifier */ 1533 WPARAM wParam, /* message parameter */ 1534 LPARAM lParam) /* message parameter */ 1535{ 1536 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 1537 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 1538 OPENFILENAME *ofnPtr; 1539 OFNData *ofnData; 1540 1541 if (uMsg == WM_INITDIALOG) { 1542 TkWinSetUserData(hdlg, lParam); 1543 } else if (uMsg == WM_NOTIFY) { 1544 OFNOTIFY *notifyPtr = (OFNOTIFY *) lParam; 1545 1546 /* 1547 * This is weird... or not. The CDN_FILEOK is NOT sent when the selection 1548 * exceeds declared buffer size (the nMaxFile member of the OPENFILENAMEW 1549 * struct passed to GetOpenFileNameW function). So, we have to rely on 1550 * the most recent CDN_SELCHANGE then. Unfortunately this means, that 1551 * gathering the selected filenames happens twice when they fit into the 1552 * declared buffer. Luckily, it's not frequent operation so it should 1553 * not incur any noticeable delay. See [tktoolkit-Bugs-2987995] 1554 */ 1555 if (notifyPtr->hdr.code == CDN_FILEOK || 1556 notifyPtr->hdr.code == CDN_SELCHANGE) { 1557 int dirsize, selsize; 1558 char *buffer; 1559 int buffersize; 1560 1561 /* 1562 * Change of selection. Unscramble the unholy mess that's in the 1563 * selection buffer, resizing it if necessary. 1564 */ 1565 1566 ofnPtr = notifyPtr->lpOFN; 1567 ofnData = (OFNData *) ofnPtr->lCustData; 1568 buffer = ofnData->dynFileBuffer; 1569 hdlg = GetParent(hdlg); 1570 1571 selsize = SendMessage(hdlg, CDM_GETSPEC, 0, 0); 1572 dirsize = SendMessage(hdlg, CDM_GETFOLDERPATH, 0, 0); 1573 buffersize = selsize + dirsize + 1; 1574 1575 if (selsize > 1) { 1576 if (ofnData->dynFileBufferSize < buffersize) { 1577 buffer = ckrealloc(buffer, buffersize); 1578 ofnData->dynFileBufferSize = buffersize; 1579 ofnData->dynFileBuffer = buffer; 1580 } 1581 1582 SendMessage(hdlg, CDM_GETFOLDERPATH, dirsize, (int) buffer); 1583 buffer += dirsize; 1584 SendMessage(hdlg, CDM_GETSPEC, selsize, (int) buffer); 1585 1586 /* 1587 * If there are multiple files, delete the quotes and change 1588 * every second quote to NULL terminator. 1589 */ 1590 1591 if (buffer[0] == '"') { 1592 BOOL findquote = TRUE; 1593 char *tmp = buffer; 1594 1595 while (*buffer != '\0') { 1596 if (findquote) { 1597 if (*buffer == '"') { 1598 findquote = FALSE; 1599 } 1600 buffer++; 1601 } else { 1602 if (*buffer == '"') { 1603 findquote = TRUE; 1604 *buffer = '\0'; 1605 } 1606 *tmp++ = *buffer++; 1607 } 1608 } 1609 *tmp = '\0'; /* Second NULL terminator. */ 1610 } else { 1611 buffer[selsize] = '\0'; /* Second NULL terminator. */ 1612 1613 /* 1614 * Replace directory terminating NULL with a backslash. 1615 */ 1616 1617 buffer--; 1618 *buffer = '\\'; 1619 } 1620 1621 } else { 1622 /* 1623 * Nothing is selected, so just empty the string. 1624 */ 1625 1626 if (buffer != NULL) { 1627 *buffer = '\0'; 1628 } 1629 } 1630 } 1631 } else if (uMsg == WM_WINDOWPOSCHANGED) { 1632 /* 1633 * This message is delivered at the right time to both old-style and 1634 * explorer-style hook procs to enable Tk to set the debug 1635 * information. Unhooks itself so it won't set the debug information 1636 * every time it gets a WM_WINDOWPOSCHANGED message. 1637 */ 1638 1639 ofnPtr = (OPENFILENAME *) TkWinGetUserData(hdlg); 1640 if (ofnPtr != NULL) { 1641 ofnData = (OFNData *) ofnPtr->lCustData; 1642 if (ofnData->interp != NULL) { 1643 if (ofnPtr->Flags & OFN_EXPLORER) { 1644 hdlg = GetParent(hdlg); 1645 } 1646 tsdPtr->debugInterp = ofnData->interp; 1647 Tcl_DoWhenIdle(SetTkDialog, hdlg); 1648 } 1649 TkWinSetUserData(hdlg, NULL); 1650 } 1651 } 1652 return 0; 1653} 1654 1655/* 1656 *---------------------------------------------------------------------- 1657 * 1658 * MakeFilter -- 1659 * 1660 * Allocate a buffer to store the filters in a format understood by 1661 * Windows. 1662 * 1663 * Results: 1664 * A standard TCL return value. 1665 * 1666 * Side effects: 1667 * ofnPtr->lpstrFilter is modified. 1668 * 1669 *---------------------------------------------------------------------- 1670 */ 1671 1672static int 1673MakeFilter( 1674 Tcl_Interp *interp, /* Current interpreter. */ 1675 Tcl_Obj *valuePtr, /* Value of the -filetypes option */ 1676 Tcl_DString *dsPtr, /* Filled with windows filter string. */ 1677 Tcl_Obj *initialPtr, /* Initial type name */ 1678 int *index) /* Index of initial type in filter string */ 1679{ 1680 char *filterStr; 1681 char *p; 1682 char *initial = NULL; 1683 int pass; 1684 int ix = 0; /* index counter */ 1685 FileFilterList flist; 1686 FileFilter *filterPtr; 1687 1688 if (initialPtr) { 1689 initial = Tcl_GetStringFromObj(initialPtr, NULL); 1690 } 1691 TkInitFileFilters(&flist); 1692 if (TkGetFileFilters(interp, &flist, valuePtr, 1) != TCL_OK) { 1693 return TCL_ERROR; 1694 } 1695 1696 if (flist.filters == NULL) { 1697 /* 1698 * Use "All Files (*.*) as the default filter if none is specified 1699 */ 1700 char *defaultFilter = "All Files (*.*)"; 1701 1702 p = filterStr = (char*)ckalloc(30 * sizeof(char)); 1703 1704 strcpy(p, defaultFilter); 1705 p+= strlen(defaultFilter); 1706 1707 *p++ = '\0'; 1708 *p++ = '*'; 1709 *p++ = '.'; 1710 *p++ = '*'; 1711 *p++ = '\0'; 1712 *p++ = '\0'; 1713 *p = '\0'; 1714 1715 } else { 1716 int len; 1717 1718 if (valuePtr == NULL) { 1719 len = 0; 1720 } else { 1721 (void) Tcl_GetStringFromObj(valuePtr, &len); 1722 } 1723 1724 /* 1725 * We format the filetype into a string understood by Windows: {"Text 1726 * Documents" {.doc .txt} {TEXT}} becomes "Text Documents 1727 * (*.doc,*.txt)\0*.doc;*.txt\0" 1728 * 1729 * See the Windows OPENFILENAME manual page for details on the filter 1730 * string format. 1731 */ 1732 1733 /* 1734 * Since we may only add asterisks (*) to the filter, we need at most 1735 * twice the size of the string to format the filter 1736 */ 1737 1738 filterStr = ckalloc((unsigned int) len * 3); 1739 1740 for (filterPtr = flist.filters, p = filterStr; filterPtr; 1741 filterPtr = filterPtr->next) { 1742 char *sep; 1743 FileFilterClause *clausePtr; 1744 1745 /* 1746 * Check initial index for match, set index. Filter index is 1 1747 * based so increment first 1748 */ 1749 ix++; 1750 if (index && initial && (strcmp(initial, filterPtr->name) == 0)) { 1751 *index = ix; 1752 } 1753 1754 /* 1755 * First, put in the name of the file type. 1756 */ 1757 1758 strcpy(p, filterPtr->name); 1759 p+= strlen(filterPtr->name); 1760 *p++ = ' '; 1761 *p++ = '('; 1762 1763 for (pass = 1; pass <= 2; pass++) { 1764 /* 1765 * In the first pass, we format the extensions in the name 1766 * field. In the second pass, we format the extensions in the 1767 * filter pattern field 1768 */ 1769 1770 sep = ""; 1771 for (clausePtr=filterPtr->clauses;clausePtr; 1772 clausePtr=clausePtr->next) { 1773 GlobPattern *globPtr; 1774 1775 for (globPtr = clausePtr->patterns; globPtr; 1776 globPtr = globPtr->next) { 1777 strcpy(p, sep); 1778 p += strlen(sep); 1779 strcpy(p, globPtr->pattern); 1780 p += strlen(globPtr->pattern); 1781 1782 if (pass == 1) { 1783 sep = ","; 1784 } else { 1785 sep = ";"; 1786 } 1787 } 1788 } 1789 if (pass == 1) { 1790 *p ++ = ')'; 1791 } 1792 *p++ = '\0'; 1793 } 1794 } 1795 1796 /* 1797 * Windows requires the filter string to be ended by two NULL 1798 * characters. 1799 */ 1800 1801 *p++ = '\0'; 1802 *p = '\0'; 1803 } 1804 1805 Tcl_DStringAppend(dsPtr, filterStr, (int) (p - filterStr)); 1806 ckfree((char *) filterStr); 1807 1808 TkFreeFileFilters(&flist); 1809 return TCL_OK; 1810} 1811 1812/* 1813 *---------------------------------------------------------------------- 1814 * 1815 * Tk_ChooseDirectoryObjCmd -- 1816 * 1817 * This function implements the "tk_chooseDirectory" dialog box for the 1818 * Windows platform. See the user documentation for details on what it 1819 * does. Uses the newer SHBrowseForFolder explorer type interface. 1820 * 1821 * Results: 1822 * See user documentation. 1823 * 1824 * Side effects: 1825 * A modal dialog window is created. Tcl_SetServiceMode() is called to 1826 * allow background events to be processed 1827 * 1828 *---------------------------------------------------------------------- 1829 * 1830 * The function tk_chooseDirectory pops up a dialog box for the user to select 1831 * a directory. The following option-value pairs are possible as command line 1832 * arguments: 1833 * 1834 * -initialdir dirname 1835 * 1836 * Specifies that the directories in directory should be displayed when the 1837 * dialog pops up. If this parameter is not specified, then the directories in 1838 * the current working directory are displayed. If the parameter specifies a 1839 * relative path, the return value will convert the relative path to an 1840 * absolute path. This option may not always work on the Macintosh. This is 1841 * not a bug. Rather, the General Controls control panel on the Mac allows the 1842 * end user to override the application default directory. 1843 * 1844 * -parent window 1845 * 1846 * Makes window the logical parent of the dialog. The dialog is displayed on 1847 * top of its parent window. 1848 * 1849 * -title titleString 1850 * 1851 * Specifies a string to display as the title of the dialog box. If this 1852 * option is not specified, then a default title will be displayed. 1853 * 1854 * -mustexist boolean 1855 * 1856 * Specifies whether the user may specify non-existant directories. If this 1857 * parameter is true, then the user may only select directories that already 1858 * exist. The default value is false. 1859 * 1860 * New Behaviour: 1861 * 1862 * - If mustexist = 0 and a user entered folder does not exist, a prompt will 1863 * pop-up asking if the user wants another chance to change it. The old 1864 * dialog just returned the bogus entry. On mustexist = 1, the entries MUST 1865 * exist before exiting the box with OK. 1866 * 1867 * Bugs: 1868 * 1869 * - If valid abs directory name is entered into the entry box and Enter 1870 * pressed, the box will close returning the name. This is inconsistent when 1871 * entering relative names or names with forward slashes, which are 1872 * invalidated then corrected in the callback. After correction, the box is 1873 * held open to allow further modification by the user. 1874 * 1875 * - Not sure how to implement localization of message prompts. 1876 * 1877 * - -title is really -message. 1878 * 1879 *---------------------------------------------------------------------- 1880 */ 1881 1882int 1883Tk_ChooseDirectoryObjCmd( 1884 ClientData clientData, /* Main window associated with interpreter. */ 1885 Tcl_Interp *interp, /* Current interpreter. */ 1886 int objc, /* Number of arguments. */ 1887 Tcl_Obj *CONST objv[]) /* Argument objects. */ 1888{ 1889 char path[MAX_PATH]; 1890 int oldMode, result = TCL_ERROR, i; 1891 LPCITEMIDLIST pidl; /* Returned by browser */ 1892 BROWSEINFO bInfo; /* Used by browser */ 1893 CHOOSEDIRDATA cdCBData; /* Structure to pass back and forth */ 1894 LPMALLOC pMalloc; /* Used by shell */ 1895 Tk_Window tkwin = (Tk_Window) clientData; 1896 HWND hWnd; 1897 char *utfTitle = NULL;/* Title for window */ 1898 TCHAR saveDir[MAX_PATH]; 1899 Tcl_DString titleString; /* UTF Title */ 1900 Tcl_DString initDirString; /* Initial directory */ 1901 Tcl_Obj *objPtr; 1902 static CONST char *optionStrings[] = { 1903 "-initialdir", "-mustexist", "-parent", "-title", NULL 1904 }; 1905 enum options { 1906 DIR_INITIAL, DIR_EXIST, DIR_PARENT, FILE_TITLE 1907 }; 1908 1909 /* 1910 * Initialize 1911 */ 1912 1913 path[0] = '\0'; 1914 ZeroMemory(&cdCBData, sizeof(CHOOSEDIRDATA)); 1915 cdCBData.interp = interp; 1916 1917 /* 1918 * Process the command line options 1919 */ 1920 1921 for (i = 1; i < objc; i += 2) { 1922 int index; 1923 char *string; 1924 Tcl_Obj *optionPtr, *valuePtr; 1925 1926 optionPtr = objv[i]; 1927 valuePtr = objv[i + 1]; 1928 1929 if (Tcl_GetIndexFromObj(interp, optionPtr, optionStrings, "option", 0, 1930 &index) != TCL_OK) { 1931 goto cleanup; 1932 } 1933 if (i + 1 == objc) { 1934 string = Tcl_GetString(optionPtr); 1935 Tcl_AppendResult(interp, "value for \"", string, "\" missing", 1936 NULL); 1937 goto cleanup; 1938 } 1939 1940 string = Tcl_GetString(valuePtr); 1941 switch ((enum options) index) { 1942 case DIR_INITIAL: 1943 if (Tcl_TranslateFileName(interp,string,&initDirString) == NULL) { 1944 goto cleanup; 1945 } 1946 string = Tcl_DStringValue(&initDirString); 1947 1948 /* 1949 * Convert possible relative path to full path to keep dialog 1950 * happy. 1951 */ 1952 1953 GetFullPathName(string, MAX_PATH, saveDir, NULL); 1954 lstrcpyn(cdCBData.utfInitDir, saveDir, MAX_PATH); 1955 Tcl_DStringFree(&initDirString); 1956 break; 1957 case DIR_EXIST: 1958 if (Tcl_GetBooleanFromObj(interp, valuePtr, 1959 &cdCBData.mustExist) != TCL_OK) { 1960 goto cleanup; 1961 } 1962 break; 1963 case DIR_PARENT: 1964 tkwin = Tk_NameToWindow(interp, string, tkwin); 1965 if (tkwin == NULL) { 1966 goto cleanup; 1967 } 1968 break; 1969 case FILE_TITLE: 1970 utfTitle = string; 1971 break; 1972 } 1973 } 1974 1975 /* 1976 * Get ready to call the browser 1977 */ 1978 1979 Tk_MakeWindowExist(tkwin); 1980 hWnd = Tk_GetHWND(Tk_WindowId(tkwin)); 1981 1982 /* 1983 * Setup the parameters used by SHBrowseForFolder 1984 */ 1985 1986 bInfo.hwndOwner = hWnd; 1987 bInfo.pszDisplayName = path; 1988 bInfo.pidlRoot = NULL; 1989 if (lstrlen(cdCBData.utfInitDir) == 0) { 1990 GetCurrentDirectory(MAX_PATH, cdCBData.utfInitDir); 1991 } 1992 bInfo.lParam = (LPARAM) &cdCBData; 1993 1994 if (utfTitle != NULL) { 1995 Tcl_UtfToExternalDString(NULL, utfTitle, -1, &titleString); 1996 bInfo.lpszTitle = (LPTSTR) Tcl_DStringValue(&titleString); 1997 } else { 1998 bInfo.lpszTitle = "Please choose a directory, then select OK."; 1999 } 2000 2001 /* 2002 * Set flags to add edit box, status text line and use the new ui. Allow 2003 * override with magic variable (ignore errors in retrieval). See 2004 * http://msdn.microsoft.com/en-us/library/bb773205(VS.85).aspx for 2005 * possible flag values. 2006 */ 2007 2008 bInfo.ulFlags = BIF_EDITBOX | BIF_STATUSTEXT | BIF_RETURNFSANCESTORS 2009 | BIF_VALIDATE | BIF_NEWDIALOGSTYLE; 2010 objPtr = Tcl_GetVar2Ex(interp, "::tk::winChooseDirFlags", NULL, 2011 TCL_GLOBAL_ONLY); 2012 if (objPtr != NULL) { 2013 int flags; 2014 Tcl_GetIntFromObj(NULL, objPtr, &flags); 2015 bInfo.ulFlags = flags; 2016 } 2017 2018 /* 2019 * Callback to handle events 2020 */ 2021 2022 bInfo.lpfn = (BFFCALLBACK) ChooseDirectoryValidateProc; 2023 2024 /* 2025 * Display dialog in background and process result. We look to give the 2026 * user a chance to change their mind on an invalid folder if mustexist is 2027 * 0. 2028 */ 2029 2030 oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); 2031 GetCurrentDirectory(MAX_PATH, saveDir); 2032 if (SHGetMalloc(&pMalloc) == NOERROR) { 2033 pidl = SHBrowseForFolder(&bInfo); 2034 2035 /* 2036 * This is a fix for Windows 2000, which seems to modify the folder name 2037 * buffer even when the dialog is canceled (in this case the buffer 2038 * contains garbage). See [Bug #3002230] 2039 */ 2040 path[0] = '\0'; 2041 2042 /* 2043 * Null for cancel button or invalid dir, otherwise valid. 2044 */ 2045 2046 if (pidl != NULL) { 2047 if (!SHGetPathFromIDList(pidl, path)) { 2048 Tcl_SetResult(interp, "Error: Not a file system folder\n", 2049 TCL_VOLATILE); 2050 }; 2051 pMalloc->lpVtbl->Free(pMalloc, (void *) pidl); 2052 } else if (lstrlen(cdCBData.utfRetDir) > 0) { 2053 lstrcpy(path, cdCBData.utfRetDir); 2054 } 2055 pMalloc->lpVtbl->Release(pMalloc); 2056 } 2057 SetCurrentDirectory(saveDir); 2058 Tcl_SetServiceMode(oldMode); 2059 2060 /* 2061 * Ensure that hWnd is enabled, because it can happen that we have updated 2062 * the wrapper of the parent, which causes us to leave this child disabled 2063 * (Windows loses sync). 2064 */ 2065 2066 EnableWindow(hWnd, 1); 2067 2068 /* 2069 * Change the pathname to the Tcl "normalized" pathname, where back 2070 * slashes are used instead of forward slashes 2071 */ 2072 2073 Tcl_ResetResult(interp); 2074 if (*path) { 2075 Tcl_DString ds; 2076 2077 Tcl_AppendResult(interp, ConvertExternalFilename(NULL, (char *) path, 2078 &ds), NULL); 2079 Tcl_DStringFree(&ds); 2080 } 2081 2082 result = TCL_OK; 2083 2084 if (utfTitle != NULL) { 2085 Tcl_DStringFree(&titleString); 2086 } 2087 2088 cleanup: 2089 return result; 2090} 2091 2092/* 2093 *---------------------------------------------------------------------- 2094 * 2095 * ChooseDirectoryValidateProc -- 2096 * 2097 * Hook function called by the explorer ChooseDirectory dialog when 2098 * events occur. It is used to validate the text entry the user may have 2099 * entered. 2100 * 2101 * Results: 2102 * Returns 0 to allow default processing of message, or 1 to tell default 2103 * dialog function not to close. 2104 * 2105 *---------------------------------------------------------------------- 2106 */ 2107 2108static UINT APIENTRY 2109ChooseDirectoryValidateProc( 2110 HWND hwnd, 2111 UINT message, 2112 LPARAM lParam, 2113 LPARAM lpData) 2114{ 2115 TCHAR selDir[MAX_PATH]; 2116 CHOOSEDIRDATA *chooseDirSharedData = (CHOOSEDIRDATA *) lpData; 2117 Tcl_DString initDirString; 2118 char string[MAX_PATH]; 2119 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 2120 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 2121 2122 if (tsdPtr->debugFlag) { 2123 tsdPtr->debugInterp = (Tcl_Interp *) chooseDirSharedData->interp; 2124 Tcl_DoWhenIdle(SetTkDialog, (ClientData) hwnd); 2125 } 2126 chooseDirSharedData->utfRetDir[0] = '\0'; 2127 switch (message) { 2128 case BFFM_VALIDATEFAILED: 2129 /* 2130 * First save and check to see if it is a valid path name, if so then 2131 * make that path the one shown in the window. Otherwise, it failed 2132 * the check and should be treated as such. Use 2133 * Set/GetCurrentDirectory which allows relative path names and names 2134 * with forward slashes. Use Tcl_TranslateFileName to make sure names 2135 * like ~ are converted correctly. 2136 */ 2137 2138 if (Tcl_TranslateFileName(chooseDirSharedData->interp, 2139 (char *) lParam, &initDirString) == NULL) { 2140 /* 2141 * Should we expose the error (in the interp result) to the user 2142 * at this point? 2143 */ 2144 2145 chooseDirSharedData->utfRetDir[0] = '\0'; 2146 return 1; 2147 } 2148 lstrcpyn(string, Tcl_DStringValue(&initDirString), MAX_PATH); 2149 Tcl_DStringFree(&initDirString); 2150 2151 if (SetCurrentDirectory((char *)string) == 0) { 2152 LPTSTR lpFilePart[MAX_PATH]; 2153 2154 /* 2155 * Get the full path name to the user entry, at this point it does 2156 * not exist so see if it is supposed to. Otherwise just return 2157 * it. 2158 */ 2159 2160 GetFullPathName(string, MAX_PATH, 2161 chooseDirSharedData->utfRetDir, /*unused*/ lpFilePart); 2162 if (chooseDirSharedData->mustExist) { 2163 /* 2164 * User HAS to select a valid directory. 2165 */ 2166 2167 wsprintf(selDir, TEXT("Directory '%.200s' does not exist,\nplease select or enter an existing directory."), chooseDirSharedData->utfRetDir); 2168 MessageBox(NULL, selDir, NULL, MB_ICONEXCLAMATION|MB_OK); 2169 chooseDirSharedData->utfRetDir[0] = '\0'; 2170 return 1; 2171 } 2172 } else { 2173 /* 2174 * Changed to new folder OK, return immediatly with the current 2175 * directory in utfRetDir. 2176 */ 2177 2178 GetCurrentDirectory(MAX_PATH, chooseDirSharedData->utfRetDir); 2179 return 0; 2180 } 2181 return 0; 2182 2183 case BFFM_SELCHANGED: 2184 /* 2185 * Set the status window to the currently selected path and enable the 2186 * OK button if a file system folder, otherwise disable the OK button 2187 * for things like server names. Perhaps a new switch 2188 * -enablenonfolders can be used to allow non folders to be selected. 2189 * 2190 * Not called when user changes edit box directly. 2191 */ 2192 2193 if (SHGetPathFromIDList((LPITEMIDLIST) lParam, selDir)) { 2194 SendMessage(hwnd, BFFM_SETSTATUSTEXT, 0, (LPARAM) selDir); 2195 // enable the OK button 2196 SendMessage(hwnd, BFFM_ENABLEOK, 0, (LPARAM) 1); 2197 } else { 2198 // disable the OK button 2199 SendMessage(hwnd, BFFM_ENABLEOK, 0, (LPARAM) 0); 2200 } 2201 UpdateWindow(hwnd); 2202 return 1; 2203 2204 case BFFM_INITIALIZED: { 2205 /* 2206 * Directory browser intializing - tell it where to start from, user 2207 * specified parameter. 2208 */ 2209 2210 char *initDir = chooseDirSharedData->utfInitDir; 2211 2212 SetCurrentDirectory(initDir); 2213 if (*initDir == '\\') { 2214 /* 2215 * BFFM_SETSELECTION only understands UNC paths as pidls, so 2216 * convert path to pidl using IShellFolder interface. 2217 */ 2218 2219 LPMALLOC pMalloc; 2220 LPSHELLFOLDER psfFolder; 2221 2222 if (SUCCEEDED(SHGetMalloc(&pMalloc))) { 2223 if (SUCCEEDED(SHGetDesktopFolder(&psfFolder))) { 2224 LPITEMIDLIST pidlMain; 2225 ULONG ulCount, ulAttr; 2226 Tcl_DString ds; 2227 2228 Tcl_UtfToExternalDString(TkWinGetUnicodeEncoding(), 2229 initDir, -1, &ds); 2230 if (SUCCEEDED(psfFolder->lpVtbl->ParseDisplayName( 2231 psfFolder, hwnd, NULL, (WCHAR *) 2232 Tcl_DStringValue(&ds), &ulCount,&pidlMain,&ulAttr)) 2233 && (pidlMain != NULL)) { 2234 SendMessage(hwnd, BFFM_SETSELECTION, FALSE, 2235 (LPARAM) pidlMain); 2236 pMalloc->lpVtbl->Free(pMalloc, pidlMain); 2237 } 2238 psfFolder->lpVtbl->Release(psfFolder); 2239 Tcl_DStringFree(&ds); 2240 } 2241 pMalloc->lpVtbl->Release(pMalloc); 2242 } 2243 } else { 2244 SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM) initDir); 2245 } 2246 SendMessage(hwnd, BFFM_ENABLEOK, 0, (LPARAM) 1); 2247 break; 2248 } 2249 2250 } 2251 return 0; 2252} 2253 2254/* 2255 *---------------------------------------------------------------------- 2256 * 2257 * Tk_MessageBoxObjCmd -- 2258 * 2259 * This function implements the MessageBox window for the Windows 2260 * platform. See the user documentation for details on what it does. 2261 * 2262 * Results: 2263 * See user documentation. 2264 * 2265 * Side effects: 2266 * None. The MessageBox window will be destroy before this function 2267 * returns. 2268 * 2269 *---------------------------------------------------------------------- 2270 */ 2271 2272int 2273Tk_MessageBoxObjCmd( 2274 ClientData clientData, /* Main window associated with interpreter. */ 2275 Tcl_Interp *interp, /* Current interpreter. */ 2276 int objc, /* Number of arguments. */ 2277 Tcl_Obj *CONST objv[]) /* Argument objects. */ 2278{ 2279 Tk_Window tkwin = (Tk_Window) clientData, parent; 2280 HWND hWnd; 2281 Tcl_Obj *messageObj, *titleObj, *detailObj, *tmpObj; 2282 int defaultBtn, icon, type; 2283 int i, oldMode, winCode; 2284 UINT flags; 2285 static CONST char *optionStrings[] = { 2286 "-default", "-detail", "-icon", "-message", 2287 "-parent", "-title", "-type", NULL 2288 }; 2289 enum options { 2290 MSG_DEFAULT, MSG_DETAIL, MSG_ICON, MSG_MESSAGE, 2291 MSG_PARENT, MSG_TITLE, MSG_TYPE 2292 }; 2293 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 2294 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 2295 2296 (void) TkWinGetUnicodeEncoding(); 2297 defaultBtn = -1; 2298 detailObj = NULL; 2299 icon = MB_ICONINFORMATION; 2300 messageObj = NULL; 2301 parent = tkwin; 2302 titleObj = NULL; 2303 type = MB_OK; 2304 2305 for (i = 1; i < objc; i += 2) { 2306 int index; 2307 char *string; 2308 Tcl_Obj *optionPtr, *valuePtr; 2309 2310 optionPtr = objv[i]; 2311 valuePtr = objv[i + 1]; 2312 2313 if (Tcl_GetIndexFromObj(interp, optionPtr, optionStrings, "option", 2314 TCL_EXACT, &index) != TCL_OK) { 2315 return TCL_ERROR; 2316 } 2317 if (i + 1 == objc) { 2318 string = Tcl_GetString(optionPtr); 2319 Tcl_AppendResult(interp, "value for \"", string, "\" missing", 2320 NULL); 2321 return TCL_ERROR; 2322 } 2323 2324 switch ((enum options) index) { 2325 case MSG_DEFAULT: 2326 defaultBtn = TkFindStateNumObj(interp, optionPtr, buttonMap, 2327 valuePtr); 2328 if (defaultBtn < 0) { 2329 return TCL_ERROR; 2330 } 2331 break; 2332 2333 case MSG_DETAIL: 2334 detailObj = valuePtr; 2335 break; 2336 2337 case MSG_ICON: 2338 icon = TkFindStateNumObj(interp, optionPtr, iconMap, valuePtr); 2339 if (icon < 0) { 2340 return TCL_ERROR; 2341 } 2342 break; 2343 2344 case MSG_MESSAGE: 2345 messageObj = valuePtr; 2346 break; 2347 2348 case MSG_PARENT: 2349 parent = Tk_NameToWindow(interp, Tcl_GetString(valuePtr), tkwin); 2350 if (parent == NULL) { 2351 return TCL_ERROR; 2352 } 2353 break; 2354 2355 case MSG_TITLE: 2356 titleObj = valuePtr; 2357 break; 2358 2359 case MSG_TYPE: 2360 type = TkFindStateNumObj(interp, optionPtr, typeMap, valuePtr); 2361 if (type < 0) { 2362 return TCL_ERROR; 2363 } 2364 break; 2365 } 2366 } 2367 2368 Tk_MakeWindowExist(parent); 2369 hWnd = Tk_GetHWND(Tk_WindowId(parent)); 2370 2371 flags = 0; 2372 if (defaultBtn >= 0) { 2373 int defaultBtnIdx = -1; 2374 2375 for (i = 0; i < (int) NUM_TYPES; i++) { 2376 if (type == allowedTypes[i].type) { 2377 int j; 2378 2379 for (j = 0; j < 3; j++) { 2380 if (allowedTypes[i].btnIds[j] == defaultBtn) { 2381 defaultBtnIdx = j; 2382 break; 2383 } 2384 } 2385 if (defaultBtnIdx < 0) { 2386 Tcl_AppendResult(interp, "invalid default button \"", 2387 TkFindStateString(buttonMap, defaultBtn), 2388 "\"", NULL); 2389 return TCL_ERROR; 2390 } 2391 break; 2392 } 2393 } 2394 flags = buttonFlagMap[defaultBtnIdx]; 2395 } 2396 2397 flags |= icon | type | MB_SYSTEMMODAL; 2398 2399 tmpObj = messageObj ? Tcl_DuplicateObj(messageObj) 2400 : Tcl_NewUnicodeObj(NULL, 0); 2401 Tcl_IncrRefCount(tmpObj); 2402 if (detailObj) { 2403 Tcl_AppendUnicodeToObj(tmpObj, L"\n\n", 2); 2404 Tcl_AppendObjToObj(tmpObj, detailObj); 2405 } 2406 2407 oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); 2408 2409 /* 2410 * MessageBoxW exists for all platforms. Use it to allow unicode error 2411 * message to be displayed correctly where possible by the OS. 2412 * 2413 * In order to have the parent window icon reflected in a MessageBox, we 2414 * have to create a hook that will trigger when the MessageBox is being 2415 * created. 2416 */ 2417 2418 tsdPtr->hSmallIcon = TkWinGetIcon(parent, ICON_SMALL); 2419 tsdPtr->hBigIcon = TkWinGetIcon(parent, ICON_BIG); 2420 tsdPtr->hMsgBoxHook = SetWindowsHookEx(WH_CBT, MsgBoxCBTProc, NULL, 2421 GetCurrentThreadId()); 2422 winCode = MessageBoxW(hWnd, Tcl_GetUnicode(tmpObj), 2423 titleObj ? Tcl_GetUnicode(titleObj) : L"", flags); 2424 UnhookWindowsHookEx(tsdPtr->hMsgBoxHook); 2425 (void) Tcl_SetServiceMode(oldMode); 2426 2427 /* 2428 * Ensure that hWnd is enabled, because it can happen that we have updated 2429 * the wrapper of the parent, which causes us to leave this child disabled 2430 * (Windows loses sync). 2431 */ 2432 2433 EnableWindow(hWnd, 1); 2434 2435 Tcl_DecrRefCount(tmpObj); 2436 2437 Tcl_SetResult(interp, TkFindStateString(buttonMap, winCode), TCL_STATIC); 2438 return TCL_OK; 2439} 2440 2441static LRESULT CALLBACK 2442MsgBoxCBTProc( 2443 int nCode, 2444 WPARAM wParam, 2445 LPARAM lParam) 2446{ 2447 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 2448 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 2449 2450 if (nCode == HCBT_CREATEWND) { 2451 /* 2452 * Window owned by our task is being created. Since the hook is 2453 * installed just before the MessageBox call and removed after the 2454 * MessageBox call, the window being created is either the message box 2455 * or one of its controls. Check that the class is WC_DIALOG to ensure 2456 * that it's the one we want. 2457 */ 2458 2459 LPCBT_CREATEWND lpcbtcreate = (LPCBT_CREATEWND) lParam; 2460 2461 if (WC_DIALOG == lpcbtcreate->lpcs->lpszClass) { 2462 HWND hwnd = (HWND) wParam; 2463 2464 SendMessage(hwnd, WM_SETICON, ICON_SMALL, 2465 (LPARAM) tsdPtr->hSmallIcon); 2466 SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM) tsdPtr->hBigIcon); 2467 } 2468 } 2469 2470 /* 2471 * Call the next hook proc, if there is one 2472 */ 2473 2474 return CallNextHookEx(tsdPtr->hMsgBoxHook, nCode, wParam, lParam); 2475} 2476 2477/* 2478 * ---------------------------------------------------------------------- 2479 * 2480 * SetTkDialog -- 2481 * 2482 * Records the HWND for a native dialog in the 'tk_dialog' variable so 2483 * that the test-suite can operate on the correct dialog window. Use of 2484 * this is enabled when a test program calls TkWinDialogDebug by calling 2485 * the test command 'tkwinevent debug 1'. 2486 * 2487 * ---------------------------------------------------------------------- 2488 */ 2489 2490static void 2491SetTkDialog( 2492 ClientData clientData) 2493{ 2494 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 2495 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 2496 char buf[32]; 2497 2498 sprintf(buf, "0x%p", (HWND) clientData); 2499 Tcl_SetVar(tsdPtr->debugInterp, "tk_dialog", buf, TCL_GLOBAL_ONLY); 2500} 2501 2502/* 2503 * Factored out a common pattern in use in this file. 2504 */ 2505static char * 2506ConvertExternalFilename( 2507 Tcl_Encoding encoding, 2508 char *filename, 2509 Tcl_DString *dsPtr) 2510{ 2511 char *p; 2512 2513 Tcl_ExternalToUtfDString(encoding, filename, -1, dsPtr); 2514 for (p = Tcl_DStringValue(dsPtr); *p != '\0'; p++) { 2515 /* 2516 * Change the pathname to the Tcl "normalized" pathname, where back 2517 * slashes are used instead of forward slashes 2518 */ 2519 2520 if (*p == '\\') { 2521 *p = '/'; 2522 } 2523 } 2524 return Tcl_DStringValue(dsPtr); 2525} 2526 2527/* 2528 * Local Variables: 2529 * mode: c 2530 * c-basic-offset: 4 2531 * fill-column: 78 2532 * End: 2533 */ 2534