// BrowseForFolder_function.js
// http://akelpad.sourceforge.net/forum/viewtopic.php?p=13321#13321
// Version: 2015-01-05
// Author: KDJ
//
// Call in script:
//   if (! AkelPad.Include("BrowseForFolder_function.js")) WScript.Quit();
//   sDir = BrowseForFolder(hWndOwn, sText, sIniDir, sTitle, bFiles, nPosX, nPosY);
//
// All arguments are optional:
//   hWndOwn - handle to the owner window for the dialog box, default is desktop window
//   sText   - text that is displayed above the tree view control in the dialog box, default is ""
//   sIniDir - folder/file to be selected, after calling the function, default is ""
//   sTitle  - dialog box title, default is standard title
//   bFiles  - if true, dialog box displays also files
//   nPosX   - horizontal position of the dialog in screen coordinates
//   nPosY   - vertical position of the dialog in screen coordinates
//
// Function returns string - selected folder/file name or "" if you press Cancel button

function BrowseForFolder(hWndOwn, sText, sIniDir, sTitle, bFiles, nPosX, nPosY)
{
  var sDir     = "";
  var oSys     = AkelPad.SystemFunction();
  var hWndDesk = oSys.Call("User32::GetDesktopWindow");

  try
  {
    oSys.RegisterCallback(BFFCallback);
  }
  catch (oError)
  {
    WScript.Echo("Unable to register callback function.");
    return sDir;
  }

  if (! oSys.Call("User32::IsWindow", hWndOwn))
    hWndOwn = hWndDesk;
  if (typeof sText != "string")
    sText = "";
  if (typeof sIniDir != "string")
    sIniDir = "";

  var lpText   = AkelPad.MemAlloc((sText.length + 1) * 2);
  var lpDir    = AkelPad.MemAlloc(260 * 2); //sizeof(MAX_PATH)
  var lpBrowse = AkelPad.MemAlloc(_X64 ? 64 : 32); //sizeof(BROWSEINFO)
  var nFlags   = 0x00000041 /*BIF_NEWDIALOGSTYLE|BIF_RETURNONLYFSDIRS*/ | (bFiles ? 0x00004000 /*BIF_BROWSEINCLUDEFILES*/ : 0);
  var lpIDL;

  AkelPad.MemCopy(lpText, sText, 1 /*DT_UNICODE*/);
  AkelPad.MemCopy(lpDir, sIniDir, 1 /*DT_UNICODE*/);

  AkelPad.MemCopy(_PtrAdd(lpBrowse,              0),     hWndOwn, 2 /*DT_QWORD*/); //hWndOwner
  AkelPad.MemCopy(_PtrAdd(lpBrowse, _X64 ?  8 :  4),           0, 2 /*DT_QWORD*/); //pidlRoot
  AkelPad.MemCopy(_PtrAdd(lpBrowse, _X64 ? 16 :  8),       lpDir, 2 /*DT_QWORD*/); //pszDisplayName
  AkelPad.MemCopy(_PtrAdd(lpBrowse, _X64 ? 24 : 12),      lpText, 2 /*DT_QWORD*/); //lpszTitle
  AkelPad.MemCopy(_PtrAdd(lpBrowse, _X64 ? 32 : 16),      nFlags, 3 /*DT_DWORD*/); //ulFlags
  AkelPad.MemCopy(_PtrAdd(lpBrowse, _X64 ? 40 : 20), BFFCallback, 2 /*DT_QWORD*/); //lpfn
  AkelPad.MemCopy(_PtrAdd(lpBrowse, _X64 ? 48 : 24),           0, 2 /*DT_QWORD*/); //lParam = lpData in callback function
  AkelPad.MemCopy(_PtrAdd(lpBrowse, _X64 ? 56 : 28),           0, 3 /*DT_DWORD*/); //iImage

  if (lpIDL = oSys.Call("Shell32::SHBrowseForFolderW", lpBrowse))
  {
    oSys.Call("Shell32::SHGetPathFromIDListW", lpIDL, lpDir);
    oSys.Call("Ole32::CoTaskMemFree", lpIDL);
    sDir = AkelPad.MemRead(lpDir, 1 /*DT_UNICODE*/);
  }

  oSys.UnregisterCallback(BFFCallback);
  AkelPad.MemFree(lpText);
  AkelPad.MemFree(lpDir);
  AkelPad.MemFree(lpBrowse);

  return sDir;

  function BFFCallback(hWnd, uMsg, lParam, lpData)
  {
    if (uMsg == 1 /*BFFM_INITIALIZED*/)
    {
      var lpRect = AkelPad.MemAlloc(16); //sizeof(RECT)
      var hWnd1;
      var hWnd2;
      var nPosW, nPosH;
      var nOwnX, nOwnY, nOwnW, nOwnH;
      var nDeskW, nDeskH;

      if (typeof sTitle == "string")
        oSys.Call("User32::SetWindowTextW", hWnd, sTitle);

      AkelPad.SendMessage(hWnd, 0x0467 /*BFFM_SETSELECTIONW*/, 1, lpDir);

      //set focus to TreeView
      if (hWnd1 = oSys.Call("User32::FindWindowExW", hWnd, 0, "SHBrowseForFolder ShellNameSpace Control", 0))
      {
        if (hWnd2 = oSys.Call("User32::FindWindowExW", hWnd1, 0, "SysTreeView32", 0))
        {
          WScript.Sleep(50);
          oSys.Call("User32::PostMessageW", hWnd, 0x0028 /*WM_NEXTDLGCTL*/, hWnd2, 1);
          oSys.Call("User32::PostMessageW", hWnd2, 0x1114 /*TVM_ENSUREVISIBLE*/, 0, AkelPad.SendMessage(hWnd2, 0x110A /*TVM_GETNEXTITEM*/, 0x0009 /*TVGN_CARET*/, 0));
        }
      }

      if ((typeof nPosX != "number") || (typeof nPosY != "number"))
      {
        //center dialog
        oSys.Call("User32::GetWindowRect", hWnd, lpRect);
        nPosX = AkelPad.MemRead(_PtrAdd(lpRect,  0), 3 /*DT_DWORD*/);
        nPosY = AkelPad.MemRead(_PtrAdd(lpRect,  4), 3 /*DT_DWORD*/);
        nPosW = AkelPad.MemRead(_PtrAdd(lpRect,  8), 3 /*DT_DWORD*/) - nPosX;
        nPosH = AkelPad.MemRead(_PtrAdd(lpRect, 12), 3 /*DT_DWORD*/) - nPosY;

        oSys.Call("User32::GetWindowRect", hWndOwn, lpRect);
        nOwnX = AkelPad.MemRead(_PtrAdd(lpRect,  0), 3 /*DT_DWORD*/);
        nOwnY = AkelPad.MemRead(_PtrAdd(lpRect,  4), 3 /*DT_DWORD*/);
        nOwnW = AkelPad.MemRead(_PtrAdd(lpRect,  8), 3 /*DT_DWORD*/) - nOwnX;
        nOwnH = AkelPad.MemRead(_PtrAdd(lpRect, 12), 3 /*DT_DWORD*/) - nOwnY;

        oSys.Call("User32::GetWindowRect", hWndDesk, lpRect);
        nDeskW = AkelPad.MemRead(_PtrAdd(lpRect,  8), 3 /*DT_DWORD*/);
        nDeskH = AkelPad.MemRead(_PtrAdd(lpRect, 12), 3 /*DT_DWORD*/);
        AkelPad.MemFree(lpRect);

        nPosX = nOwnX + (nOwnW - nPosW) / 2;
        nPosY = nOwnY + (nOwnH - nPosH) / 2;

        if ((nPosX + nPosW) > nDeskW)
          nPosX = nDeskW - nPosW;
        if (nPosX < 0)
          nPosX = 0;
        if ((nPosY + nPosH) > nDeskH)
          nPosY = nDeskH - nPosH;
        if (nPosY < 0)
          nPosY = 0;
      }

      oSys.Call("User32::SetWindowPos", hWnd, 0, nPosX, nPosY, 0, 0, 0x15 /*SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOSIZE*/);
    }

    return 0;
  }
}
