// This file contains portions of code released by Microsoft under the MIT license as part // of an open-sourcing initiative in 2014 of the C# core libraries. // The original source was submitted to https://github.com/Microsoft/referencesource using System.Diagnostics; using System.Collections; using System.Threading; using System.Text; #if BF_PLATFORM_WINDOWS namespace System.IO { enum DialogResult { None = 0, OK = 1, Cancel = 2 } abstract class CommonDialog { public Windows.HWnd mHWnd; public Windows.HWnd mDefaultControlHwnd; public int mDefWndProc; private const int32 CDM_SETDEFAULTFOCUS = Windows.WM_USER + 0x51; public static Dictionary sHookMap = new Dictionary() ~ { Debug.Assert(sHookMap.Count == 0); delete _; }; public static Monitor sMonitor = new Monitor() ~ delete _; public Result ShowDialog(INativeWindow owner = null) { Windows.HWnd hwndOwner = 0; if (owner != null) hwndOwner = (.)owner.Handle; //Native.WndProc wndProc = scope => OwnerWndProc; //mDefWndProc = Native.SetWindowLong(mHWnd, Native.GWL_WNDPROC, (intptr)wndProc.GetFuncPtr().Value); var result = RunDialog(hwndOwner); return result; } public virtual int OwnerWndProc(Windows.HWnd hWnd, int32 msg, int wParam, int lParam) { return Windows.CallWindowProcW(mDefWndProc, hWnd, msg, wParam, lParam); } protected virtual int HookProc(Windows.HWnd hWnd, int32 msg, int wParam, int lparam) { if (msg == Windows.WM_INITDIALOG) { //TODO: MoveToScreenCenter(hWnd); // Under some circumstances, the dialog // does not initially focus on any control. We fix that by explicitly // setting focus ourselves. See ASURT 39435. // mDefaultControlHwnd = (Windows.HWnd)wParam; if (mDefaultControlHwnd != 0) Windows.SetFocus(mDefaultControlHwnd); } else if (msg == Windows.WM_SETFOCUS) { Windows.PostMessageW(hWnd, CDM_SETDEFAULTFOCUS, 0, 0); } else if (msg == CDM_SETDEFAULTFOCUS) { // If the dialog box gets focus, bounce it to the default control. // so we post a message back to ourselves to wait for the focus change then push it to the default // control. See ASURT 84016. // if (mDefaultControlHwnd != 0) Windows.SetFocus(mDefaultControlHwnd); } return 0; } protected abstract Result RunDialog(Windows.HWnd hWndOwner); } abstract class FileDialog : CommonDialog { protected abstract Result RunFileDialog(ref Windows.OpenFileName ofn); protected override Result RunDialog(Windows.HWnd hWndOwner) { return RunDialogOld(hWndOwner); } private const int32 FILEBUFSIZE = 8192; protected const int32 OPTION_ADDEXTENSION = (int32)0x80000000; protected int32 mOptions; private String mTitle ~ delete _; private String mInitialDir ~ delete _; private String mDefaultExt ~ delete _; protected String[] mFileNames ~ DeleteContainerAndItems!(_); private bool mSecurityCheckFileNames; private String mFilter ~ delete _; private String mFilterBuffer = new String() ~ delete _; private int32 mFilterIndex; private bool mSupportMultiDottedExtensions; private bool mIgnoreSecondFileOkNotification; // Used for VS Whidbey 95342 private int32 mOKNotificationCount; // Same //private String char8Buffer = new String(FILEBUFSIZE) ~ delete _; public this() { Reset(); } public virtual void Reset() { DeleteAndNullify!(mTitle); DeleteAndNullify!(mInitialDir); DeleteAndNullify!(mDefaultExt); DeleteAndNullify!(mFileNames); DeleteAndNullify!(mFilter); mFilterIndex = 1; mSupportMultiDottedExtensions = false; mOptions = Windows.OFN_HIDEREADONLY | Windows.OFN_PATHMUSTEXIST | OPTION_ADDEXTENSION; } protected int32 Options { get { return mOptions & ( Windows.OFN_READONLY | Windows.OFN_HIDEREADONLY | Windows.OFN_NOCHANGEDIR | Windows.OFN_SHOWHELP | Windows.OFN_NOVALIDATE | Windows.OFN_ALLOWMULTISELECT | Windows.OFN_PATHMUSTEXIST | Windows.OFN_FILEMUSTEXIST | Windows.OFN_NODEREFERENCELINKS | Windows.OFN_OVERWRITEPROMPT); //return mOptions; } } public StringView Title { set { String.NewOrSet!(mTitle, value); } get { return mTitle; } } public StringView InitialDirectory { set { String.NewOrSet!(mInitialDir, value); } get { return mInitialDir; } } public String[] FileNames { get { return mFileNames; } } public StringView FileName { set { if (mFileNames == null) { mFileNames = new String[](new String(value)); } } } public bool AddExtension { get { return GetOption(OPTION_ADDEXTENSION); } set { SetOption(OPTION_ADDEXTENSION, value); } } public virtual bool CheckFileExists { get { return GetOption(Windows.OFN_FILEMUSTEXIST); } set { SetOption(Windows.OFN_FILEMUSTEXIST, value); } } public bool DereferenceLinks { get { return !GetOption(Windows.OFN_NODEREFERENCELINKS); } set { SetOption(Windows.OFN_NODEREFERENCELINKS, !value); } } public bool CheckPathExists { get { return GetOption(Windows.OFN_PATHMUSTEXIST); } set { SetOption(Windows.OFN_PATHMUSTEXIST, value); } } public bool Multiselect { get { return GetOption(Windows.OFN_ALLOWMULTISELECT); } set { SetOption(Windows.OFN_ALLOWMULTISELECT, value); } } public bool ValidateNames { get { return !GetOption(Windows.OFN_NOVALIDATE); } set { SetOption(Windows.OFN_NOVALIDATE, !value); } } public StringView DefaultExt { get { return mDefaultExt == null ? "" : mDefaultExt; } set { delete mDefaultExt; mDefaultExt = null; //if (!String.IsNullOrEmpty(value)) if (value.Length > 0) { mDefaultExt = new String(value); if (mDefaultExt.StartsWith(".")) mDefaultExt.Remove(0, 1); } } } public void GetFilter(String outFilter) { if (mFilter != null) outFilter.Append(mFilter); } public Result SetFilter(StringView value) { String useValue = scope String(value); if (useValue != null && useValue.Length > 0) { var formats = String.StackSplit!(useValue, '|'); if (formats == null || formats.Count % 2 != 0) { return .Err; } /// /*String[] formats = value.Split('|'); if (formats == null || formats.Length % 2 != 0) { throw new ArgumentException(SR.GetString(SR.FileDialogInvalidFilter)); }*/ String.NewOrSet!(mFilter, useValue); } else { useValue = null; DeleteAndNullify!(mFilter); } return .Ok; } protected bool GetOption(int32 option) { return (mOptions & option) != 0; } protected void SetOption(int32 option, bool value) { if (value) { mOptions |= option; } else { mOptions &= ~option; } } private static Result MakeFilterString(String s, bool dereferenceLinks, String filterBuffer) { String useStr = s; if (useStr == null || useStr.Length == 0) { // Workaround for Whidbey bug #5165 // Apply the workaround only when DereferenceLinks is true and OS is at least WinXP. if (dereferenceLinks && System.Environment.OSVersion.Version.Major >= 5) { useStr = " |*.*"; } else if (useStr == null) { return .Err; } } filterBuffer.Set(s); for (int32 i = 0; i < filterBuffer.Length; i++) if (filterBuffer[i] == '|') filterBuffer[i] = (char8)0; filterBuffer.Append((char8)0); return .Ok; } public static mixin Testie() { int a = 123; char16* buf; if (a == 0) { buf = null; } else { buf = new char16[123]* { ? }; defer:mixin delete buf; } buf } private Result RunDialogOld(Windows.HWnd hWndOwner) { //RunDialogTest(hWndOwner); Windows.WndProc hookProcPtr = => StaticHookProc; Windows.OpenFileName ofn = Windows.OpenFileName(); char16[FILEBUFSIZE] char16Buffer = .(0, ?); if (mFileNames != null) { //int len = UTF16.GetEncodedLen(fileNames[0]); //char16Buffer = scope:: char16[len + 1]*; UTF16.Encode(mFileNames[0], (char16*)&char16Buffer, FILEBUFSIZE); } // Degrade to the older style dialog if we're not on Win2K. // We do this by setting the struct size to a different value // if (Environment.OSVersion.Platform != System.PlatformID.Win32NT || Environment.OSVersion.Version.Major < 5) { ofn.mStructSize = 0x4C; } ofn.mHwndOwner = hWndOwner; ofn.mHInstance = (Windows.HInstance)Windows.GetModuleHandleW(null); if (mFilter != null) { Try!(MakeFilterString(mFilter, this.DereferenceLinks, mFilterBuffer)); ofn.mFilter = mFilterBuffer.ToScopedNativeWChar!::(); } ofn.nFilterIndex = mFilterIndex; ofn.mFile = (char16*)&char16Buffer; ofn.nMaxFile = FILEBUFSIZE; if (mInitialDir != null) ofn.mInitialDir = mInitialDir.ToScopedNativeWChar!::(); if (mTitle != null) ofn.mTitle = mTitle.ToScopedNativeWChar!(); ofn.mFlags = Options | (Windows.OFN_EXPLORER | Windows.OFN_ENABLEHOOK | Windows.OFN_ENABLESIZING); ofn.mHook = hookProcPtr; ofn.mCustData = (int)(void*)this; ofn.mFlagsEx = Windows.OFN_USESHELLITEM; if (mDefaultExt != null && AddExtension) ofn.mDefExt = mDefaultExt; DeleteContainerAndItems!(mFileNames); mFileNames = null; //Security checks happen here return RunFileDialog(ref ofn); } static int StaticHookProc(Windows.HWnd hWnd, int32 msg, int wParam, int lparam) { if (msg == Windows.WM_INITDIALOG) { using (sMonitor.Enter()) { var ofn = (Windows.OpenFileName*)(void*)lparam; sHookMap[(int)hWnd] = (CommonDialog)Internal.UnsafeCastToObject((void*)ofn.mCustData); } } CommonDialog dlg; using (sMonitor.Enter()) { sHookMap.TryGetValue((int)hWnd, out dlg); } if (dlg == null) return 0; dlg.[Friend]HookProc(hWnd, msg, wParam, lparam); if (msg == Windows.WM_DESTROY) { using (sMonitor.Enter()) { sHookMap.Remove((int)hWnd); } } return 0; } //TODO: Add ProcessFileNames for validation } class OpenFileDialog : FileDialog { public override void Reset() { base.Reset(); SetOption(Windows.OFN_FILEMUSTEXIST, true); } public bool ReadOnlyChecked { get { return GetOption(Windows.OFN_READONLY); } set { SetOption(Windows.OFN_READONLY, value); } } public bool ShowReadOnly { get { return !GetOption(Windows.OFN_HIDEREADONLY); } set { SetOption(Windows.OFN_HIDEREADONLY, !value); } } protected override Result RunFileDialog(ref Windows.OpenFileName ofn) { bool result = Windows.GetOpenFileNameW(ref ofn); if (!result) return .Err; if (!Multiselect) { let pathName = new String(); UTF16.Decode(ofn.mFile, pathName); mFileNames = new String[](pathName); return DialogResult.OK; } int32 entryCount = 0; int32 prevNull = -1; for (int32 i = 0; true; i++) { if (ofn.mFile[i] == (char8)0) { if (prevNull == i - 1) break; prevNull = i; entryCount++; } } String pathName = null; prevNull = -1; mFileNames = new String[Math.Max(1, entryCount - 1)]; entryCount = 0; for (int32 i = 0; true; i++) { if (ofn.mFile[i] == (char8)0) { if (prevNull == i - 1) break; if (prevNull == -1) { pathName = scope:: String(); UTF16.Decode(ofn.mFile, pathName); } else { var str = new String(pathName.Length + 1 + i - prevNull - 1); str.Append(pathName); str.Append(Path.DirectorySeparatorChar); UTF16.Decode(ofn.mFile + prevNull + 1, str); mFileNames[entryCount++] = str; } prevNull = i; } } if ((entryCount == 0) && (pathName != null)) mFileNames[0] = new String(pathName); return DialogResult.OK; } } } #endif