1 // Written by Christopher E. Miller 2 // See the included license.txt for copyright and license details. 3 4 5 /// 6 module dfl.filedialog; 7 8 private import dfl.internal.dlib; 9 10 private import dfl.control, dfl.internal.winapi, dfl.base, dfl.drawing; 11 private import dfl.application, dfl.commondialog, dfl.event, dfl.internal.utf; 12 13 14 /// 15 abstract class FileDialog: CommonDialog // docmain 16 { 17 private this() 18 { 19 Application.ppin(cast(void*)this); 20 21 ofn.lStructSize = ofn.sizeof; 22 ofn.lCustData = cast(typeof(ofn.lCustData))cast(void*)this; 23 ofn.Flags = INIT_FLAGS; 24 ofn.nFilterIndex = INIT_FILTER_INDEX; 25 initInstance(); 26 ofn.lpfnHook = cast(typeof(ofn.lpfnHook))&ofnHookProc; 27 } 28 29 30 override DialogResult showDialog() 31 { 32 return runDialog(GetActiveWindow()) ? 33 DialogResult.OK : DialogResult.CANCEL; 34 } 35 36 override DialogResult showDialog(IWindow owner) 37 { 38 return runDialog(owner ? owner.handle : GetActiveWindow()) ? 39 DialogResult.OK : DialogResult.CANCEL; 40 } 41 42 43 override void reset() 44 { 45 ofn.Flags = INIT_FLAGS; 46 ofn.lpstrFilter = null; 47 ofn.nFilterIndex = INIT_FILTER_INDEX; 48 ofn.lpstrDefExt = null; 49 _defext = null; 50 _fileNames = null; 51 needRebuildFiles = false; 52 _filter = null; 53 ofn.lpstrInitialDir = null; 54 _initDir = null; 55 ofn.lpstrTitle = null; 56 _title = null; 57 initInstance(); 58 } 59 60 61 private void initInstance() 62 { 63 //ofn.hInstance = ?; // Should this be initialized? 64 } 65 66 67 /+ 68 final @property void addExtension(bool byes) // setter 69 { 70 addext = byes; 71 } 72 73 74 final @property bool addExtension() // getter 75 { 76 return addext; 77 } 78 +/ 79 80 81 /// 82 @property void checkFileExists(bool byes) // setter 83 { 84 if(byes) 85 ofn.Flags |= OFN_FILEMUSTEXIST; 86 else 87 ofn.Flags &= ~OFN_FILEMUSTEXIST; 88 } 89 90 /// ditto 91 @property bool checkFileExists() // getter 92 { 93 return (ofn.Flags & OFN_FILEMUSTEXIST) != 0; 94 } 95 96 97 /// 98 final @property void checkPathExists(bool byes) // setter 99 { 100 if(byes) 101 ofn.Flags |= OFN_PATHMUSTEXIST; 102 else 103 ofn.Flags &= ~OFN_PATHMUSTEXIST; 104 } 105 106 /// ditto 107 final @property bool checkPathExists() // getter 108 { 109 return (ofn.Flags & OFN_PATHMUSTEXIST) != 0; 110 } 111 112 113 /// 114 final @property void defaultExt(Dstring ext) // setter 115 { 116 if(!ext.length) 117 { 118 ofn.lpstrDefExt = null; 119 _defext = null; 120 } 121 else 122 { 123 if(ext.length && ext[0] == '.') 124 ext = ext[1 .. ext.length]; 125 126 if(dfl.internal.utf.useUnicode) 127 { 128 ofnw.lpstrDefExt = dfl.internal.utf.toUnicodez(ext); 129 } 130 else 131 { 132 ofna.lpstrDefExt = dfl.internal.utf.toAnsiz(ext); 133 } 134 _defext = ext; 135 } 136 } 137 138 /// ditto 139 final @property Dstring defaultExt() // getter 140 { 141 return _defext; 142 } 143 144 145 /// 146 final @property void dereferenceLinks(bool byes) // setter 147 { 148 if(byes) 149 ofn.Flags &= ~OFN_NODEREFERENCELINKS; 150 else 151 ofn.Flags |= OFN_NODEREFERENCELINKS; 152 } 153 154 /// ditto 155 final @property bool dereferenceLinks() // getter 156 { 157 return (ofn.Flags & OFN_NODEREFERENCELINKS) == 0; 158 } 159 160 161 /// 162 final @property void fileName(Dstring fn) // setter 163 { 164 // TODO: check if correct implementation. 165 166 if(fn.length > MAX_PATH) 167 throw new DflException("Invalid file name"); 168 169 if(fileNames.length) 170 { 171 _fileNames = (&fn)[0 .. 1] ~ _fileNames[1 .. _fileNames.length]; 172 } 173 else 174 { 175 _fileNames = new Dstring[1]; 176 _fileNames[0] = fn; 177 } 178 } 179 180 /// ditto 181 final @property Dstring fileName() // getter 182 { 183 if(fileNames.length) 184 return fileNames[0]; 185 return null; 186 } 187 188 189 /// 190 final @property Dstring[] fileNames() // getter 191 { 192 if(needRebuildFiles) 193 populateFiles(); 194 195 return _fileNames; 196 } 197 198 199 /// 200 // The format string is like "Text files (*.txt)|*.txt|All files (*.*)|*.*". 201 final @property void filter(Dstring filterString) // setter 202 { 203 if(!filterString.length) 204 { 205 ofn.lpstrFilter = null; 206 _filter = null; 207 } 208 else 209 { 210 struct _Str 211 { 212 union 213 { 214 wchar[] sw; 215 char[] sa; 216 } 217 } 218 _Str str; 219 220 size_t i, starti; 221 size_t nitems = 0; 222 223 if(dfl.internal.utf.useUnicode) 224 { 225 str.sw = new wchar[filterString.length + 2]; 226 str.sw = str.sw[0 .. 0]; 227 } 228 else 229 { 230 str.sa = new char[filterString.length + 2]; 231 str.sa = str.sa[0 .. 0]; 232 } 233 234 235 for(i = starti = 0; i != filterString.length; i++) 236 { 237 switch(filterString[i]) 238 { 239 case '|': 240 if(starti == i) 241 goto bad_filter; 242 243 if(dfl.internal.utf.useUnicode) 244 { 245 str.sw ~= dfl.internal.utf.toUnicode(filterString[starti .. i]); 246 str.sw ~= "\0"; 247 } 248 else 249 { 250 str.sa ~= dfl.internal.utf.unsafeAnsi(filterString[starti .. i]); 251 str.sa ~= "\0"; 252 } 253 254 starti = i + 1; 255 nitems++; 256 break; 257 258 case 0: 259 case '\r', '\n': 260 goto bad_filter; 261 262 default: 263 } 264 } 265 if(starti == i || !(nitems % 2)) 266 goto bad_filter; 267 if(dfl.internal.utf.useUnicode) 268 { 269 str.sw ~= dfl.internal.utf.toUnicode(filterString[starti .. i]); 270 str.sw ~= "\0\0"; 271 272 ofnw.lpstrFilter = str.sw.ptr; 273 } 274 else 275 { 276 str.sa ~= dfl.internal.utf.unsafeAnsi(filterString[starti .. i]); 277 str.sa ~= "\0\0"; 278 279 ofna.lpstrFilter = str.sa.ptr; 280 } 281 282 _filter = filterString; 283 return; 284 285 bad_filter: 286 throw new DflException("Invalid file filter string"); 287 } 288 } 289 290 /// ditto 291 final @property Dstring filter() // getter 292 { 293 return _filter; 294 } 295 296 297 /// 298 // Note: index is 1-based. 299 final @property void filterIndex(int index) // setter 300 { 301 ofn.nFilterIndex = (index > 0) ? index : 1; 302 } 303 304 /// ditto 305 final @property int filterIndex() // getter 306 { 307 return ofn.nFilterIndex; 308 } 309 310 311 /// 312 final @property void initialDirectory(Dstring dir) // setter 313 { 314 if(!dir.length) 315 { 316 ofn.lpstrInitialDir = null; 317 _initDir = null; 318 } 319 else 320 { 321 if(dfl.internal.utf.useUnicode) 322 { 323 ofnw.lpstrInitialDir = dfl.internal.utf.toUnicodez(dir); 324 } 325 else 326 { 327 ofna.lpstrInitialDir = dfl.internal.utf.toAnsiz(dir); 328 } 329 _initDir = dir; 330 } 331 } 332 333 /// ditto 334 final @property Dstring initialDirectory() // getter 335 { 336 return _initDir; 337 } 338 339 340 // Should be instance(), but conflicts with D's old keyword. 341 342 /// 343 protected @property void inst(HINSTANCE hinst) // setter 344 { 345 ofn.hInstance = hinst; 346 } 347 348 /// ditto 349 protected @property HINSTANCE inst() // getter 350 { 351 return ofn.hInstance; 352 } 353 354 355 /// 356 protected @property DWORD options() // getter 357 { 358 return ofn.Flags; 359 } 360 361 362 /// 363 final @property void restoreDirectory(bool byes) // setter 364 { 365 if(byes) 366 ofn.Flags |= OFN_NOCHANGEDIR; 367 else 368 ofn.Flags &= ~OFN_NOCHANGEDIR; 369 } 370 371 /// ditto 372 final @property bool restoreDirectory() // getter 373 { 374 return (ofn.Flags & OFN_NOCHANGEDIR) != 0; 375 } 376 377 378 /// 379 final @property void showHelp(bool byes) // setter 380 { 381 if(byes) 382 ofn.Flags |= OFN_SHOWHELP; 383 else 384 ofn.Flags &= ~OFN_SHOWHELP; 385 } 386 387 /// ditto 388 final @property bool showHelp() // getter 389 { 390 return (ofn.Flags & OFN_SHOWHELP) != 0; 391 } 392 393 394 /// 395 final @property void title(Dstring newTitle) // setter 396 { 397 if(!newTitle.length) 398 { 399 ofn.lpstrTitle = null; 400 _title = null; 401 } 402 else 403 { 404 if(dfl.internal.utf.useUnicode) 405 { 406 ofnw.lpstrTitle = dfl.internal.utf.toUnicodez(newTitle); 407 } 408 else 409 { 410 ofna.lpstrTitle = dfl.internal.utf.toAnsiz(newTitle); 411 } 412 _title = newTitle; 413 } 414 } 415 416 /// ditto 417 final @property Dstring title() // getter 418 { 419 return _title; 420 } 421 422 423 /// 424 final @property void validateNames(bool byes) // setter 425 { 426 if(byes) 427 ofn.Flags &= ~OFN_NOVALIDATE; 428 else 429 ofn.Flags |= OFN_NOVALIDATE; 430 } 431 432 /// ditto 433 final @property bool validateNames() // getter 434 { 435 return(ofn.Flags & OFN_NOVALIDATE) == 0; 436 } 437 438 439 /// 440 Event!(FileDialog, CancelEventArgs) fileOk; 441 442 443 protected: 444 445 override bool runDialog(HWND owner) 446 { 447 assert(0); 448 } 449 450 451 /// 452 void onFileOk(CancelEventArgs ea) 453 { 454 fileOk(this, ea); 455 } 456 457 458 override LRESULT hookProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) 459 { 460 switch(msg) 461 { 462 case WM_NOTIFY: 463 { 464 NMHDR* nmhdr; 465 nmhdr = cast(NMHDR*)lparam; 466 switch(nmhdr.code) 467 { 468 case CDN_FILEOK: 469 { 470 CancelEventArgs cea; 471 cea = new CancelEventArgs; 472 onFileOk(cea); 473 if(cea.cancel) 474 { 475 SetWindowLongA(hwnd, DWL_MSGRESULT, 1); 476 return 1; 477 } 478 } 479 break; 480 481 default: 482 //cprintf(" nmhdr.code = %d/0x%X\n", nmhdr.code, nmhdr.code); 483 } 484 } 485 break; 486 487 default: 488 } 489 490 return super.hookProc(hwnd, msg, wparam, lparam); 491 } 492 493 494 private: 495 union 496 { 497 OPENFILENAMEW ofnw; 498 OPENFILENAMEA ofna; 499 alias ofnw ofn; 500 501 static assert(OPENFILENAMEW.sizeof == OPENFILENAMEA.sizeof); 502 static assert(OPENFILENAMEW.Flags.offsetof == OPENFILENAMEA.Flags.offsetof); 503 } 504 Dstring[] _fileNames; 505 Dstring _filter; 506 Dstring _initDir; 507 Dstring _defext; 508 Dstring _title; 509 //bool addext = true; 510 bool needRebuildFiles = false; 511 512 enum DWORD INIT_FLAGS = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | 513 OFN_ENABLEHOOK | OFN_ENABLESIZING; 514 enum INIT_FILTER_INDEX = 0; 515 enum FILE_BUF_LEN = 4096; // ? 12288 ? 12800 ? 516 517 518 void beginOfn(HWND owner) 519 { 520 if(dfl.internal.utf.useUnicode) 521 { 522 auto buf = new wchar[(ofn.Flags & OFN_ALLOWMULTISELECT) ? FILE_BUF_LEN : MAX_PATH]; 523 buf[0] = 0; 524 525 if(fileNames.length) 526 { 527 Dwstring ts; 528 ts = dfl.internal.utf.toUnicode(_fileNames[0]); 529 buf[0 .. ts.length] = ts[]; 530 buf[ts.length] = 0; 531 } 532 533 ofnw.nMaxFile = buf.length; 534 ofnw.lpstrFile = buf.ptr; 535 } 536 else 537 { 538 auto buf = new char[(ofn.Flags & OFN_ALLOWMULTISELECT) ? FILE_BUF_LEN : MAX_PATH]; 539 buf[0] = 0; 540 541 if(fileNames.length) 542 { 543 Dstring ts; 544 ts = dfl.internal.utf.unsafeAnsi(_fileNames[0]); 545 buf[0 .. ts.length] = ts[]; 546 buf[ts.length] = 0; 547 } 548 549 ofna.nMaxFile = buf.length; 550 ofna.lpstrFile = buf.ptr; 551 } 552 553 ofn.hwndOwner = owner; 554 } 555 556 557 // Populate -_fileNames- from -ofn.lpstrFile-. 558 void populateFiles() 559 in 560 { 561 assert(ofn.lpstrFile !is null); 562 } 563 body 564 { 565 if(ofn.Flags & OFN_ALLOWMULTISELECT) 566 { 567 // Nonstandard reserve. 568 _fileNames = new Dstring[4]; 569 _fileNames = _fileNames[0 .. 0]; 570 571 if(dfl.internal.utf.useUnicode) 572 { 573 wchar* startp, p; 574 p = startp = ofnw.lpstrFile; 575 for(;;) 576 { 577 if(!*p) 578 { 579 _fileNames ~= dfl.internal.utf.fromUnicode(startp, p - startp); // dup later. 580 581 p++; 582 if(!*p) 583 break; 584 585 startp = p; 586 continue; 587 } 588 589 p++; 590 } 591 } 592 else 593 { 594 char* startp, p; 595 p = startp = ofna.lpstrFile; 596 for(;;) 597 { 598 if(!*p) 599 { 600 _fileNames ~= dfl.internal.utf.fromAnsi(startp, p - startp); // dup later. 601 602 p++; 603 if(!*p) 604 break; 605 606 startp = p; 607 continue; 608 } 609 610 p++; 611 } 612 } 613 614 assert(_fileNames.length); 615 if(_fileNames.length == 1) 616 { 617 //_fileNames[0] = _fileNames[0].dup; 618 //_fileNames[0] = _fileNames[0].idup; // Needed in D2. Doesn't work in D1. 619 _fileNames[0] = cast(Dstring)_fileNames[0].dup; // Needed in D2. 620 } 621 else 622 { 623 Dstring s; 624 size_t i; 625 s = _fileNames[0]; 626 627 // Not sure which of these 2 is better... 628 /+ 629 for(i = 1; i != _fileNames.length; i++) 630 { 631 _fileNames[i - 1] = pathJoin(s, _fileNames[i]); 632 } 633 _fileNames = _fileNames[0 .. _fileNames.length - 1]; 634 +/ 635 for(i = 1; i != _fileNames.length; i++) 636 { 637 _fileNames[i] = pathJoin(s, _fileNames[i]); 638 } 639 _fileNames = _fileNames[1 .. _fileNames.length]; 640 } 641 } 642 else 643 { 644 _fileNames = new Dstring[1]; 645 if(dfl.internal.utf.useUnicode) 646 { 647 _fileNames[0] = dfl.internal.utf.fromUnicodez(ofnw.lpstrFile); 648 } 649 else 650 { 651 _fileNames[0] = dfl.internal.utf.fromAnsiz(ofna.lpstrFile); 652 } 653 654 /+ 655 if(addext && checkFileExists() && ofn.nFilterIndex) 656 { 657 if(!ofn.nFileExtension || ofn.nFileExtension == _fileNames[0].length) 658 { 659 Dstring s; 660 typeof(ofn.nFilterIndex) onidx; 661 int i; 662 Dstring[] exts; 663 664 s = _filter; 665 onidx = ofn.nFilterIndex << 1; 666 do 667 { 668 i = charFindInString(s, '|'); 669 if(i == -1) 670 goto no_such_filter; 671 672 s = s[i + 1 .. s.length]; 673 674 onidx--; 675 } 676 while(onidx != 1); 677 678 i = charFindInString(s, '|'); 679 if(i != -1) 680 s = s[0 .. i]; 681 682 exts = stringSplit(s, ";"); 683 foreach(Dstring ext; exts) 684 { 685 cprintf("sel ext: %.*s\n", ext); 686 } 687 688 // ... 689 690 no_such_filter: ; 691 } 692 } 693 +/ 694 } 695 696 needRebuildFiles = false; 697 } 698 699 700 // Call only if the dialog succeeded. 701 void finishOfn() 702 { 703 if(needRebuildFiles) 704 populateFiles(); 705 706 ofn.lpstrFile = null; 707 } 708 709 710 // Call only if dialog fail or cancel. 711 void cancelOfn() 712 { 713 needRebuildFiles = false; 714 715 ofn.lpstrFile = null; 716 _fileNames = null; 717 } 718 } 719 720 721 private extern(Windows) nothrow 722 { 723 alias BOOL function(LPOPENFILENAMEW lpofn) GetOpenFileNameWProc; 724 alias BOOL function(LPOPENFILENAMEW lpofn) GetSaveFileNameWProc; 725 } 726 727 728 /// 729 class OpenFileDialog: FileDialog // docmain 730 { 731 this() 732 { 733 super(); 734 ofn.Flags |= OFN_FILEMUSTEXIST; 735 } 736 737 738 override void reset() 739 { 740 super.reset(); 741 ofn.Flags |= OFN_FILEMUSTEXIST; 742 } 743 744 745 /// 746 final @property void multiselect(bool byes) // setter 747 { 748 if(byes) 749 ofn.Flags |= OFN_ALLOWMULTISELECT; 750 else 751 ofn.Flags &= ~OFN_ALLOWMULTISELECT; 752 } 753 754 /// ditto 755 final @property bool multiselect() // getter 756 { 757 return (ofn.Flags & OFN_ALLOWMULTISELECT) != 0; 758 } 759 760 761 /// 762 final @property void readOnlyChecked(bool byes) // setter 763 { 764 if(byes) 765 ofn.Flags |= OFN_READONLY; 766 else 767 ofn.Flags &= ~OFN_READONLY; 768 } 769 770 /// ditto 771 final @property bool readOnlyChecked() // getter 772 { 773 return (ofn.Flags & OFN_READONLY) != 0; 774 } 775 776 777 /// 778 final @property void showReadOnly(bool byes) // setter 779 { 780 if(byes) 781 ofn.Flags &= ~OFN_HIDEREADONLY; 782 else 783 ofn.Flags |= OFN_HIDEREADONLY; 784 } 785 786 /// ditto 787 final @property bool showReadOnly() // getter 788 { 789 return (ofn.Flags & OFN_HIDEREADONLY) == 0; 790 } 791 792 793 private import std.stream; // TO-DO: remove this import; use dfl.internal.dlib. 794 795 /// 796 final Stream openFile() 797 { 798 return new File(fileName(), FileMode.In); 799 } 800 801 802 protected: 803 804 override bool runDialog(HWND owner) 805 { 806 if(!_runDialog(owner)) 807 { 808 if(!CommDlgExtendedError()) 809 return false; 810 _cantrun(); 811 } 812 return true; 813 } 814 815 816 private BOOL _runDialog(HWND owner) 817 { 818 BOOL result = 0; 819 820 beginOfn(owner); 821 822 //synchronized(typeid(dfl.internal.utf.CurDirLockType)) 823 { 824 if(dfl.internal.utf.useUnicode) 825 { 826 enum NAME = "GetOpenFileNameW"; 827 static GetOpenFileNameWProc proc = null; 828 829 if(!proc) 830 { 831 proc = cast(GetOpenFileNameWProc)GetProcAddress(GetModuleHandleA("comdlg32.dll"), NAME.ptr); 832 if(!proc) 833 throw new Exception("Unable to load procedure " ~ NAME ~ ""); 834 } 835 836 result = proc(&ofnw); 837 } 838 else 839 { 840 result = GetOpenFileNameA(&ofna); 841 } 842 } 843 844 if(result) 845 { 846 finishOfn(); 847 return result; 848 } 849 850 cancelOfn(); 851 return result; 852 } 853 } 854 855 856 /// 857 class SaveFileDialog: FileDialog // docmain 858 { 859 this() 860 { 861 super(); 862 ofn.Flags |= OFN_OVERWRITEPROMPT; 863 } 864 865 866 override void reset() 867 { 868 super.reset(); 869 ofn.Flags |= OFN_OVERWRITEPROMPT; 870 } 871 872 873 /// 874 final @property void createPrompt(bool byes) // setter 875 { 876 if(byes) 877 ofn.Flags |= OFN_CREATEPROMPT; 878 else 879 ofn.Flags &= ~OFN_CREATEPROMPT; 880 } 881 882 /// ditto 883 final @property bool createPrompt() // getter 884 { 885 return (ofn.Flags & OFN_CREATEPROMPT) != 0; 886 } 887 888 889 /// 890 final @property void overwritePrompt(bool byes) // setter 891 { 892 if(byes) 893 ofn.Flags |= OFN_OVERWRITEPROMPT; 894 else 895 ofn.Flags &= ~OFN_OVERWRITEPROMPT; 896 } 897 898 /// ditto 899 final @property bool overwritePrompt() // getter 900 { 901 return (ofn.Flags & OFN_OVERWRITEPROMPT) != 0; 902 } 903 904 905 private import std.stream; // TO-DO: remove this import; use dfl.internal.dlib. 906 907 /// 908 // Opens and creates with read and write access. 909 // Warning: if file exists, it's truncated. 910 final Stream openFile() 911 { 912 return new File(fileName(), FileMode.OutNew | FileMode.Out | FileMode.In); 913 } 914 915 916 protected: 917 918 override bool runDialog(HWND owner) 919 { 920 beginOfn(owner); 921 922 //synchronized(typeid(dfl.internal.utf.CurDirLockType)) 923 { 924 if(dfl.internal.utf.useUnicode) 925 { 926 enum NAME = "GetSaveFileNameW"; 927 static GetSaveFileNameWProc proc = null; 928 929 if(!proc) 930 { 931 proc = cast(GetSaveFileNameWProc)GetProcAddress(GetModuleHandleA("comdlg32.dll"), NAME.ptr); 932 if(!proc) 933 throw new Exception("Unable to load procedure " ~ NAME ~ ""); 934 } 935 936 if(proc(&ofnw)) 937 { 938 finishOfn(); 939 return true; 940 } 941 } 942 else 943 { 944 if(GetSaveFileNameA(&ofna)) 945 { 946 finishOfn(); 947 return true; 948 } 949 } 950 } 951 952 cancelOfn(); 953 return false; 954 } 955 } 956 957 958 private extern(Windows) LRESULT ofnHookProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) nothrow 959 { 960 alias dfl.internal.winapi.HANDLE HANDLE; // Otherwise, odd conflict with wine. 961 962 enum PROP_STR = "DFL_FileDialog"; 963 FileDialog fd; 964 LRESULT result = 0; 965 966 try 967 { 968 if(msg == WM_INITDIALOG) 969 { 970 OPENFILENAMEA* ofn; 971 ofn = cast(OPENFILENAMEA*)lparam; 972 SetPropA(hwnd, PROP_STR.ptr, cast(HANDLE)ofn.lCustData); 973 fd = cast(FileDialog)cast(void*)ofn.lCustData; 974 } 975 else 976 { 977 fd = cast(FileDialog)cast(void*)GetPropA(hwnd, PROP_STR.ptr); 978 } 979 980 //cprintf("hook msg(%d/0x%X) to obj %p\n", msg, msg, fd); 981 if(fd) 982 { 983 fd.needRebuildFiles = true; 984 result = fd.hookProc(hwnd, msg, wparam, lparam); 985 } 986 } 987 catch(DThrowable e) 988 { 989 Application.onThreadException(e); 990 } 991 992 return result; 993 } 994