--- /dev/null
- lptvid = (TV_ITEMDATA *)SHAlloc( sizeof(TV_ITEMDATA) );
- if (!lptvid)
+/*
+ * Copyright 1999 Juergen Schmied
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * FIXME:
+ * - many memory leaks
+ * - many flags unimplemented
+ * - implement new dialog style "make new folder" button
+ * - implement editbox
+ * - implement new dialog style resizing
+ */
+
+#include "precomp.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(shell);
+
+/* original margins and control size */
+typedef struct tagLAYOUT_DATA
+{
+ LONG left, width, right;
+ LONG top, height, bottom;
+} LAYOUT_DATA;
+
+typedef struct tagbrowse_info
+{
+ HWND hWnd;
+ HWND hwndTreeView;
+ LPBROWSEINFOW lpBrowseInfo;
+ LPITEMIDLIST pidlRet;
+ LAYOUT_DATA *layout; /* filled by LayoutInit, used by LayoutUpdate */
+ SIZE szMin;
+} browse_info;
+
+typedef struct tagTV_ITEMDATA
+{
+ IShellFolder* lpsfParent; /* IShellFolder of the parent */
+ LPITEMIDLIST lpi; /* PIDL relative to parent */
+ LPITEMIDLIST lpifq; /* Fully qualified PIDL */
+ IEnumIDList* pEnumIL; /* Children iterator */
+} TV_ITEMDATA, *LPTV_ITEMDATA;
+
+typedef struct tagLAYOUT_INFO
+{
+ int iItemId; /* control id */
+ DWORD dwAnchor; /* BF_* flags specifying which margins should remain constant */
+} LAYOUT_INFO;
+
+static const LAYOUT_INFO g_layout_info[] =
+{
+ {IDC_BROWSE_FOR_FOLDER_TITLE, BF_TOP|BF_LEFT|BF_RIGHT},
+ {IDC_BROWSE_FOR_FOLDER_STATUS, BF_TOP|BF_LEFT|BF_RIGHT},
+ {IDC_BROWSE_FOR_FOLDER_FOLDER, BF_TOP|BF_LEFT|BF_RIGHT},
+ {IDC_BROWSE_FOR_FOLDER_TREEVIEW, BF_TOP|BF_BOTTOM|BF_LEFT|BF_RIGHT},
+ {IDC_BROWSE_FOR_FOLDER_FOLDER, BF_BOTTOM|BF_LEFT},
+ {IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT, BF_BOTTOM|BF_LEFT|BF_RIGHT},
+ {IDC_BROWSE_FOR_FOLDER_NEW_FOLDER, BF_BOTTOM|BF_LEFT},
+ {IDOK, BF_BOTTOM|BF_RIGHT},
+ {IDCANCEL, BF_BOTTOM|BF_RIGHT}
+};
+
+#define LAYOUT_INFO_COUNT (sizeof(g_layout_info)/sizeof(g_layout_info[0]))
+
+#define SUPPORTEDFLAGS (BIF_STATUSTEXT | \
+ BIF_BROWSEFORCOMPUTER | \
+ BIF_RETURNFSANCESTORS | \
+ BIF_RETURNONLYFSDIRS | \
+ BIF_NONEWFOLDERBUTTON | \
+ BIF_NEWDIALOGSTYLE | \
+ BIF_BROWSEINCLUDEFILES)
+
+static void FillTreeView(browse_info*, IShellFolder*,
+ LPITEMIDLIST, HTREEITEM, IEnumIDList*);
+static HTREEITEM InsertTreeViewItem( browse_info*, IShellFolder*,
+ LPCITEMIDLIST, LPCITEMIDLIST, IEnumIDList*, HTREEITEM);
+
+static const WCHAR szBrowseFolderInfo[] = L"__WINE_BRSFOLDERDLG_INFO";
+
+static DWORD __inline BrowseFlagsToSHCONTF(UINT ulFlags)
+{
+ return SHCONTF_FOLDERS | (ulFlags & BIF_BROWSEINCLUDEFILES ? SHCONTF_NONFOLDERS : 0);
+}
+
+static void browsefolder_callback( LPBROWSEINFOW lpBrowseInfo, HWND hWnd,
+ UINT msg, LPARAM param )
+{
+ if (!lpBrowseInfo->lpfn)
+ return;
+ lpBrowseInfo->lpfn( hWnd, msg, param, lpBrowseInfo->lParam );
+}
+
+static LAYOUT_DATA *LayoutInit(HWND hwnd, const LAYOUT_INFO *layout_info, int layout_count)
+{
+ LAYOUT_DATA *data;
+ RECT rcWnd;
+ int i;
+
+ GetClientRect(hwnd, &rcWnd);
+ data = (LAYOUT_DATA *)SHAlloc(sizeof(LAYOUT_DATA)*layout_count);
+ for (i = 0; i < layout_count; i++)
+ {
+ RECT r;
+ HWND hItem = GetDlgItem(hwnd, layout_info[i].iItemId);
+
+ if (hItem == NULL)
+ ERR("Item %d not found\n", i);
+ GetWindowRect(hItem, &r);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&r, 2);
+
+ data[i].left = r.left;
+ data[i].right = rcWnd.right - r.right;
+ data[i].width = r.right - r.left;
+
+ data[i].top = r.top;
+ data[i].bottom = rcWnd.bottom - r.bottom;
+ data[i].height = r.bottom - r.top;
+ }
+ return data;
+}
+
+static void LayoutUpdate(HWND hwnd, LAYOUT_DATA *data, const LAYOUT_INFO *layout_info, int layout_count)
+{
+ RECT rcWnd;
+ int i;
+
+ GetClientRect(hwnd, &rcWnd);
+ for (i = 0; i < layout_count; i++)
+ {
+ RECT r;
+ HWND hItem = GetDlgItem(hwnd, layout_info[i].iItemId);
+
+ GetWindowRect(hItem, &r);
+ MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT)&r, 2);
+
+ if (layout_info[i].dwAnchor & BF_RIGHT)
+ {
+ r.right = rcWnd.right - data[i].right;
+ if (!(layout_info[i].dwAnchor & BF_LEFT))
+ r.left = r.right - data[i].width;
+ }
+
+ if (layout_info[i].dwAnchor & BF_BOTTOM)
+ {
+ r.bottom = rcWnd.bottom - data[i].bottom;
+ if (!(layout_info[i].dwAnchor & BF_TOP))
+ r.top = r.bottom - data[i].height;
+ }
+
+ SetWindowPos(hItem, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOZORDER);
+ }
+}
+
+
+/******************************************************************************
+ * InitializeTreeView [Internal]
+ *
+ * Called from WM_INITDIALOG handler.
+ *
+ * PARAMS
+ * hwndParent [I] The BrowseForFolder dialog
+ * root [I] ITEMIDLIST of the root shell folder
+ */
+static void InitializeTreeView( browse_info *info )
+{
+ LPITEMIDLIST pidlParent, pidlChild;
+ HIMAGELIST hImageList;
+ HRESULT hr;
+ CComPtr<IShellFolder> lpsfParent;
+ CComPtr<IShellFolder> lpsfRoot;
+ CComPtr<IEnumIDList> pEnumChildren;
+ HTREEITEM item;
+ DWORD flags;
+ LPCITEMIDLIST root = info->lpBrowseInfo->pidlRoot;
+
+ TRACE("%p\n", info );
+
+ Shell_GetImageLists(NULL, &hImageList);
+
+ if (hImageList)
+ SendMessageW( info->hwndTreeView, TVM_SETIMAGELIST, 0, (LPARAM)hImageList );
+
+ /* We want to call InsertTreeViewItem down the code, in order to insert
+ * the root item of the treeview. Due to InsertTreeViewItem's signature,
+ * we need the following to do this:
+ *
+ * + An ITEMIDLIST corresponding to _the parent_ of root.
+ * + An ITEMIDLIST, which is a relative path from root's parent to root
+ * (containing a single SHITEMID).
+ * + An IShellFolder interface pointer of root's parent folder.
+ *
+ * If root is 'Desktop', then root's parent is also 'Desktop'.
+ */
+
+ pidlParent = ILClone(root);
+ ILRemoveLastID(pidlParent);
+ pidlChild = ILClone(ILFindLastID(root));
+
+ if (_ILIsDesktop(pidlParent))
+ {
+ hr = SHGetDesktopFolder(&lpsfParent);
+ } else {
+ CComPtr<IShellFolder> lpsfDesktop;
+ hr = SHGetDesktopFolder(&lpsfDesktop);
+ if (FAILED(hr)) {
+ WARN("SHGetDesktopFolder failed! hr = %08x\n", hr);
+ ILFree(pidlChild);
+ ILFree(pidlParent);
+ return;
+ }
+ hr = lpsfDesktop->BindToObject(pidlParent, 0, IID_PPV_ARG(IShellFolder, &lpsfParent));
+ }
+
+ if (FAILED(hr)) {
+ WARN("Could not bind to parent shell folder! hr = %08x\n", hr);
+ ILFree(pidlChild);
+ ILFree(pidlParent);
+ return;
+ }
+
+ if (pidlChild && pidlChild->mkid.cb) {
+ hr = lpsfParent->BindToObject(pidlChild, 0, IID_PPV_ARG(IShellFolder, &lpsfRoot));
+ } else {
+ lpsfRoot = lpsfParent;
+ }
+
+ if (FAILED(hr)) {
+ WARN("Could not bind to root shell folder! hr = %08x\n", hr);
+ ILFree(pidlChild);
+ ILFree(pidlParent);
+ return;
+ }
+
+ flags = BrowseFlagsToSHCONTF( info->lpBrowseInfo->ulFlags );
+ hr = lpsfRoot->EnumObjects(info->hWnd, flags, &pEnumChildren );
+ if (FAILED(hr)) {
+ WARN("Could not get child iterator! hr = %08x\n", hr);
+ ILFree(pidlChild);
+ ILFree(pidlParent);
+ return;
+ }
+
+ SendMessageW( info->hwndTreeView, TVM_DELETEITEM, 0, (LPARAM)TVI_ROOT );
+ item = InsertTreeViewItem( info, lpsfParent, pidlChild,
+ pidlParent, pEnumChildren, TVI_ROOT );
+ SendMessageW( info->hwndTreeView, TVM_EXPAND, TVE_EXPAND, (LPARAM)item );
+
+ ILFree(pidlChild);
+ ILFree(pidlParent);
+}
+
+static int GetIcon(LPCITEMIDLIST lpi, UINT uFlags)
+{
+ SHFILEINFOW sfi;
+ SHGetFileInfoW((LPCWSTR)lpi, 0 ,&sfi, sizeof(SHFILEINFOW), uFlags);
+ return sfi.iIcon;
+}
+
+static void GetNormalAndSelectedIcons(LPITEMIDLIST lpifq, LPTVITEMW lpTV_ITEM)
+{
+ LPITEMIDLIST pidlDesktop = NULL;
+ DWORD flags;
+
+ TRACE("%p %p\n",lpifq, lpTV_ITEM);
+
+ if (!lpifq)
+ {
+ pidlDesktop = _ILCreateDesktop();
+ lpifq = pidlDesktop;
+ }
+
+ flags = SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON;
+ lpTV_ITEM->iImage = GetIcon( lpifq, flags );
+
+ flags = SHGFI_PIDL | SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_OPENICON;
+ lpTV_ITEM->iSelectedImage = GetIcon( lpifq, flags );
+
+ if (pidlDesktop)
+ ILFree( pidlDesktop );
+}
+
+/******************************************************************************
+ * GetName [Internal]
+ *
+ * Query a shell folder for the display name of one of it's children
+ *
+ * PARAMS
+ * lpsf [I] IShellFolder interface of the folder to be queried.
+ * lpi [I] ITEMIDLIST of the child, relative to parent
+ * dwFlags [I] as in IShellFolder::GetDisplayNameOf
+ * lpFriendlyName [O] The desired display name in unicode
+ *
+ * RETURNS
+ * Success: TRUE
+ * Failure: FALSE
+ */
+static BOOL GetName(IShellFolder * lpsf, LPCITEMIDLIST lpi, DWORD dwFlags, LPWSTR lpFriendlyName)
+{
+ BOOL bSuccess=TRUE;
+ STRRET str;
+
+ TRACE("%p %p %x %p\n", lpsf, lpi, dwFlags, lpFriendlyName);
+ if (SUCCEEDED(lpsf->GetDisplayNameOf(lpi, dwFlags, &str)))
+ bSuccess = StrRetToStrNW(lpFriendlyName, MAX_PATH, &str, lpi);
+ else
+ bSuccess = FALSE;
+
+ TRACE("-- %s\n", debugstr_w(lpFriendlyName));
+ return bSuccess;
+}
+
+/******************************************************************************
+ * InsertTreeViewItem [Internal]
+ *
+ * PARAMS
+ * info [I] data for the dialog
+ * lpsf [I] IShellFolder interface of the item's parent shell folder
+ * pidl [I] ITEMIDLIST of the child to insert, relative to parent
+ * pidlParent [I] ITEMIDLIST of the parent shell folder
+ * pEnumIL [I] Iterator for the children of the item to be inserted
+ * hParent [I] The treeview-item that represents the parent shell folder
+ *
+ * RETURNS
+ * Success: Handle to the created and inserted treeview-item
+ * Failure: NULL
+ */
+static HTREEITEM InsertTreeViewItem( browse_info *info, IShellFolder * lpsf,
+ LPCITEMIDLIST pidl, LPCITEMIDLIST pidlParent, IEnumIDList* pEnumIL,
+ HTREEITEM hParent)
+{
+ TVITEMW tvi;
+ TVINSERTSTRUCTW tvins;
+ WCHAR szBuff[MAX_PATH];
+ LPTV_ITEMDATA lptvid=0;
+
+ tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;
+
+ tvi.cChildren= pEnumIL ? 1 : 0;
+ tvi.mask |= TVIF_CHILDREN;
+
- if (!GetName(lpsf, pidl, SHGDN_NORMAL, szBuff))
++ if (!GetName(lpsf, pidl, SHGDN_NORMAL, szBuff))
+ return NULL;
+
++ lptvid = (TV_ITEMDATA *)SHAlloc( sizeof(TV_ITEMDATA) );
++ if (!lptvid)
+ return NULL;
+
+ tvi.pszText = szBuff;
+ tvi.cchTextMax = MAX_PATH;
+ tvi.lParam = (LPARAM)lptvid;
+
+ lpsf->AddRef();
+ pEnumIL->AddRef();
+ lptvid->lpsfParent = lpsf;
+ lptvid->lpi = ILClone(pidl);
+ lptvid->lpifq = pidlParent ? ILCombine(pidlParent, pidl) : ILClone(pidl);
+ lptvid->pEnumIL = pEnumIL;
+ GetNormalAndSelectedIcons(lptvid->lpifq, &tvi);
+
+ tvins.item = tvi;
+ tvins.hInsertAfter = NULL;
+ tvins.hParent = hParent;
+
+ return TreeView_InsertItem( info->hwndTreeView, &tvins );
+}
+
+/******************************************************************************
+ * FillTreeView [Internal]
+ *
+ * For each child (given by lpe) of the parent shell folder, which is given by
+ * lpsf and whose PIDL is pidl, insert a treeview-item right under hParent
+ *
+ * PARAMS
+ * info [I] data for the dialog
+ * lpsf [I] IShellFolder interface of the parent shell folder
+ * pidl [I] ITEMIDLIST of the parent shell folder
+ * hParent [I] The treeview item that represents the parent shell folder
+ * lpe [I] An iterator for the children of the parent shell folder
+ */
+static void FillTreeView( browse_info *info, IShellFolder * lpsf,
+ LPITEMIDLIST pidl, HTREEITEM hParent, IEnumIDList* lpe)
+{
+ LPITEMIDLIST pidlTemp = 0;
+ ULONG ulFetched;
+ HRESULT hr;
+ HWND hwnd = GetParent( info->hwndTreeView );
+
+ TRACE("%p %p %p %p\n",lpsf, pidl, hParent, lpe);
+
+ /* No IEnumIDList -> No children */
+ if (!lpe) return;
+
+ SetCapture( hwnd );
+ SetCursor( LoadCursorA( 0, (LPSTR)IDC_WAIT ) );
+
+ while (S_OK == lpe->Next(1,&pidlTemp,&ulFetched))
+ {
+ ULONG ulAttrs = SFGAO_HASSUBFOLDER | SFGAO_FOLDER;
+ CComPtr<IEnumIDList> pEnumIL;
+ CComPtr<IShellFolder> pSFChild;
+ lpsf->GetAttributesOf(1, (LPCITEMIDLIST*)&pidlTemp, &ulAttrs);
+ if (ulAttrs & SFGAO_FOLDER)
+ {
+ hr = lpsf->BindToObject(pidlTemp, NULL, IID_PPV_ARG(IShellFolder, &pSFChild));
+ if (SUCCEEDED(hr))
+ {
+ DWORD flags = BrowseFlagsToSHCONTF(info->lpBrowseInfo->ulFlags);
+ hr = pSFChild->EnumObjects(hwnd, flags, &pEnumIL);
+ if (hr == S_OK)
+ {
+ if ((pEnumIL->Skip(1) != S_OK) ||
+ FAILED(pEnumIL->Reset()))
+ {
+ pEnumIL = NULL;
+ }
+ }
+ }
+ }
+
+ if (!InsertTreeViewItem(info, lpsf, pidlTemp, pidl, pEnumIL, hParent))
+ goto done;
+ SHFree(pidlTemp); /* Finally, free the pidl that the shell gave us... */
+ pidlTemp=NULL;
+ }
+
+done:
+ ReleaseCapture();
+ SetCursor(LoadCursorW(0, (LPWSTR)IDC_ARROW));
+ SHFree(pidlTemp);
+}
+
+static BOOL __inline PIDLIsType(LPCITEMIDLIST pidl, PIDLTYPE type)
+{
+ LPPIDLDATA data = _ILGetDataPointer(pidl);
+ if (!data)
+ return FALSE;
+ return (data->type == type);
+}
+
+static void BrsFolder_CheckValidSelection( browse_info *info, LPTV_ITEMDATA lptvid )
+{
+ LPBROWSEINFOW lpBrowseInfo = info->lpBrowseInfo;
+ LPCITEMIDLIST pidl = lptvid->lpi;
+ BOOL bEnabled = TRUE;
+ DWORD dwAttributes;
+ HRESULT r;
+
+ if ((lpBrowseInfo->ulFlags & BIF_BROWSEFORCOMPUTER) &&
+ !PIDLIsType(pidl, PT_COMP))
+ bEnabled = FALSE;
+ if (lpBrowseInfo->ulFlags & BIF_RETURNFSANCESTORS)
+ {
+ dwAttributes = SFGAO_FILESYSANCESTOR | SFGAO_FILESYSTEM;
+ r = lptvid->lpsfParent->GetAttributesOf(1,
+ (LPCITEMIDLIST*)&lptvid->lpi, &dwAttributes);
+ if (FAILED(r) || !(dwAttributes & (SFGAO_FILESYSANCESTOR|SFGAO_FILESYSTEM)))
+ bEnabled = FALSE;
+ }
+
+ dwAttributes = SFGAO_FOLDER | SFGAO_FILESYSTEM;
+ r = lptvid->lpsfParent->GetAttributesOf(1,
+ (LPCITEMIDLIST*)&lptvid->lpi,
+ &dwAttributes);
+ if (FAILED(r) ||
+ ((dwAttributes & (SFGAO_FOLDER|SFGAO_FILESYSTEM)) != (SFGAO_FOLDER|SFGAO_FILESYSTEM)))
+ {
+ if (lpBrowseInfo->ulFlags & BIF_RETURNONLYFSDIRS)
+ bEnabled = FALSE;
+ EnableWindow(GetDlgItem(info->hWnd, IDC_BROWSE_FOR_FOLDER_NEW_FOLDER), FALSE);
+ }
+ else
+ EnableWindow(GetDlgItem(info->hWnd, IDC_BROWSE_FOR_FOLDER_NEW_FOLDER), TRUE);
+
+ SendMessageW(info->hWnd, BFFM_ENABLEOK, 0, bEnabled);
+}
+
+static LRESULT BrsFolder_Treeview_Delete( browse_info *info, NMTREEVIEWW *pnmtv )
+{
+ LPTV_ITEMDATA lptvid = (LPTV_ITEMDATA)pnmtv->itemOld.lParam;
+
+ TRACE("TVN_DELETEITEMA/W %p\n", lptvid);
+
+ lptvid->lpsfParent->Release();
+ if (lptvid->pEnumIL)
+ lptvid->pEnumIL->Release();
+ SHFree(lptvid->lpi);
+ SHFree(lptvid->lpifq);
+ SHFree(lptvid);
+ return 0;
+}
+
+static LRESULT BrsFolder_Treeview_Expand( browse_info *info, NMTREEVIEWW *pnmtv )
+{
+ CComPtr<IShellFolder> lpsf2;
+ LPTV_ITEMDATA lptvid = (LPTV_ITEMDATA) pnmtv->itemNew.lParam;
+ HRESULT r;
+
+ TRACE("TVN_ITEMEXPANDINGA/W\n");
+
+ if ((pnmtv->itemNew.state & TVIS_EXPANDEDONCE))
+ return 0;
+
+ if (lptvid->lpi && lptvid->lpi->mkid.cb) {
+ r = lptvid->lpsfParent->BindToObject(lptvid->lpi, 0, IID_PPV_ARG(IShellFolder, &lpsf2));
+ } else {
+ lpsf2 = lptvid->lpsfParent;
+ r = lpsf2->AddRef();
+ }
+
+ if (SUCCEEDED(r))
+ {
+ FillTreeView( info, lpsf2, lptvid->lpifq, pnmtv->itemNew.hItem, lptvid->pEnumIL);
+ lpsf2->Release();
+ }
+
+ /* My Computer is already sorted and trying to do a simple text
+ * sort will only mess things up */
+ if (!_ILIsMyComputer(lptvid->lpi))
+ SendMessageW( info->hwndTreeView, TVM_SORTCHILDREN,
+ FALSE, (LPARAM)pnmtv->itemNew.hItem );
+
+ return 0;
+}
+
+static HRESULT BrsFolder_Treeview_Changed( browse_info *info, NMTREEVIEWW *pnmtv )
+{
+ LPTV_ITEMDATA lptvid = (LPTV_ITEMDATA) pnmtv->itemNew.lParam;
+ WCHAR name[MAX_PATH];
+
+ ILFree(info->pidlRet);
+ info->pidlRet = ILClone(lptvid->lpifq);
+
+ if (GetName(lptvid->lpsfParent, lptvid->lpi, SHGDN_NORMAL, name))
+ SetWindowTextW( GetDlgItem(info->hWnd, IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT), name );
+
+ browsefolder_callback( info->lpBrowseInfo, info->hWnd, BFFM_SELCHANGED,
+ (LPARAM)info->pidlRet );
+ BrsFolder_CheckValidSelection( info, lptvid );
+ return 0;
+}
+
+static LRESULT BrsFolder_Treeview_Rename(browse_info *info, NMTVDISPINFOW *pnmtv)
+{
+ LPTV_ITEMDATA item_data;
+ WCHAR old_path[MAX_PATH], new_path[MAX_PATH], *p;
+ NMTREEVIEWW nmtv;
+ TVITEMW item;
+
+ if(!pnmtv->item.pszText)
+ return 0;
+
+ item.mask = TVIF_HANDLE|TVIF_PARAM;
+ item.hItem = (HTREEITEM)SendMessageW(info->hwndTreeView, TVM_GETNEXTITEM, TVGN_CARET, 0);
+ SendMessageW(info->hwndTreeView, TVM_GETITEMW, 0, (LPARAM)&item);
+ item_data = (LPTV_ITEMDATA)item.lParam;
+
+ SHGetPathFromIDListW(item_data->lpifq, old_path);
+ if(!(p = strrchrW(old_path, '\\')))
+ return 0;
+ p = new_path+(p-old_path+1);
+ memcpy(new_path, old_path, (p-new_path)*sizeof(WCHAR));
+ strcpyW(p, pnmtv->item.pszText);
+
+ if(!MoveFileW(old_path, new_path))
+ return 0;
+
+ SHFree(item_data->lpifq);
+ SHFree(item_data->lpi);
+ item_data->lpifq = SHSimpleIDListFromPathW(new_path);
+ item_data->lpsfParent->ParseDisplayName(NULL, NULL, pnmtv->item.pszText,
+ NULL, &item_data->lpi, NULL);
+
+ item.mask = TVIF_HANDLE|TVIF_TEXT;
+ item.pszText = pnmtv->item.pszText;
+ SendMessageW(info->hwndTreeView, TVM_SETITEMW, 0, (LPARAM)&item);
+
+ nmtv.itemNew.lParam = item.lParam;
+ BrsFolder_Treeview_Changed(info, &nmtv);
+ return 0;
+}
+
+static LRESULT BrsFolder_OnNotify( browse_info *info, UINT CtlID, LPNMHDR lpnmh )
+{
+ NMTREEVIEWW *pnmtv = (NMTREEVIEWW *)lpnmh;
+
+ TRACE("%p %x %p msg=%x\n", info, CtlID, lpnmh, pnmtv->hdr.code);
+
+ if (pnmtv->hdr.idFrom != IDC_BROWSE_FOR_FOLDER_TREEVIEW)
+ return 0;
+
+ switch (pnmtv->hdr.code)
+ {
+ case TVN_DELETEITEMA:
+ case TVN_DELETEITEMW:
+ return BrsFolder_Treeview_Delete( info, pnmtv );
+
+ case TVN_ITEMEXPANDINGA:
+ case TVN_ITEMEXPANDINGW:
+ return BrsFolder_Treeview_Expand( info, pnmtv );
+
+ case TVN_SELCHANGEDA:
+ case TVN_SELCHANGEDW:
+ return BrsFolder_Treeview_Changed( info, pnmtv );
+
+ case TVN_ENDLABELEDITA:
+ case TVN_ENDLABELEDITW:
+ return BrsFolder_Treeview_Rename( info, (LPNMTVDISPINFOW)pnmtv );
+
+ default:
+ WARN("unhandled (%d)\n", pnmtv->hdr.code);
+ break;
+ }
+
+ return 0;
+}
+
+
+static BOOL BrsFolder_OnCreate( HWND hWnd, browse_info *info )
+{
+ LPBROWSEINFOW lpBrowseInfo = info->lpBrowseInfo;
+
+ info->hWnd = hWnd;
+ SetPropW( hWnd, szBrowseFolderInfo, info );
+
+ if (lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE)
+ FIXME("flags BIF_NEWDIALOGSTYLE partially implemented\n");
+ if (lpBrowseInfo->ulFlags & ~SUPPORTEDFLAGS)
+ FIXME("flags %x not implemented\n", lpBrowseInfo->ulFlags & ~SUPPORTEDFLAGS);
+
+ if (lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE)
+ {
+ RECT rcWnd;
+
+ info->layout = LayoutInit(hWnd, g_layout_info, LAYOUT_INFO_COUNT);
+
+ /* TODO: Windows allows shrinking the windows a bit */
+ GetWindowRect(hWnd, &rcWnd);
+ info->szMin.cx = rcWnd.right - rcWnd.left;
+ info->szMin.cy = rcWnd.bottom - rcWnd.top;
+ }
+ else
+ {
+ info->layout = NULL;
+ }
+
+ if (lpBrowseInfo->lpszTitle)
+ SetWindowTextW( GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_TITLE), lpBrowseInfo->lpszTitle );
+ else
+ ShowWindow( GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_TITLE), SW_HIDE );
+
+ if (!(lpBrowseInfo->ulFlags & BIF_STATUSTEXT)
+ || (lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE))
+ ShowWindow( GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_STATUS), SW_HIDE );
+
+ /* Hide "Make New Folder" Button? */
+ if ((lpBrowseInfo->ulFlags & BIF_NONEWFOLDERBUTTON)
+ || !(lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE))
+ ShowWindow( GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_NEW_FOLDER), SW_HIDE );
+
+ /* Hide the editbox? */
+ if (!(lpBrowseInfo->ulFlags & BIF_EDITBOX))
+ {
+ ShowWindow( GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_FOLDER), SW_HIDE );
+ ShowWindow( GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_FOLDER_TEXT), SW_HIDE );
+ }
+
+ info->hwndTreeView = GetDlgItem( hWnd, IDC_BROWSE_FOR_FOLDER_TREEVIEW );
+ if (info->hwndTreeView)
+ {
+ InitializeTreeView( info );
+
+ /* Resize the treeview if there's not editbox */
+ if ((lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE)
+ && !(lpBrowseInfo->ulFlags & BIF_EDITBOX))
+ {
+ RECT rc;
+ GetClientRect(info->hwndTreeView, &rc);
+ SetWindowPos(info->hwndTreeView, HWND_TOP, 0, 0,
+ rc.right, rc.bottom + 40, SWP_NOMOVE);
+ }
+ }
+ else
+ ERR("treeview control missing!\n");
+
+ browsefolder_callback( info->lpBrowseInfo, hWnd, BFFM_INITIALIZED, 0 );
+
+ return TRUE;
+}
+
+static HRESULT BrsFolder_Rename(browse_info *info, HTREEITEM rename)
+{
+ SendMessageW(info->hwndTreeView, TVM_SELECTITEM, TVGN_CARET, (LPARAM)rename);
+ SendMessageW(info->hwndTreeView, TVM_EDITLABELW, 0, (LPARAM)rename);
+ return S_OK;
+}
+
+static HRESULT BrsFolder_NewFolder(browse_info *info)
+{
+ DWORD flags = BrowseFlagsToSHCONTF(info->lpBrowseInfo->ulFlags);
+ CComPtr<IShellFolder> desktop;
+ CComPtr<IShellFolder> cur;
+ CComPtr<ISFHelper> sfhelper;
+ WCHAR name[MAX_PATH];
+ HTREEITEM parent, added;
+ LPTV_ITEMDATA item_data;
+ LPITEMIDLIST new_item;
+ TVITEMW item;
+ HRESULT hr;
+ int len;
+
+ if(!info->pidlRet) {
+ ERR("Make new folder button should be disabled\n");
+ return E_FAIL;
+ }
+
+ /* Create new directory */
+ hr = SHGetDesktopFolder(&desktop);
+ if(FAILED(hr))
+ return hr;
+ hr = desktop->BindToObject(info->pidlRet, 0, IID_PPV_ARG(IShellFolder, &cur));
+ if(FAILED(hr))
+ return hr;
+
+ hr = cur->QueryInterface(IID_PPV_ARG(ISFHelper, &sfhelper));
+ if(FAILED(hr))
+ return hr;
+
+ hr = SHGetPathFromIDListW(info->pidlRet, name);
+ if(FAILED(hr))
+ goto cleanup;
+
+ len = strlenW(name);
+ if(len<MAX_PATH)
+ name[len++] = '\\';
+ hr = sfhelper->GetUniqueName(&name[len], MAX_PATH-len);
+ if(FAILED(hr))
+ goto cleanup;
+
+ hr = E_FAIL;
+ if(!CreateDirectoryW(name, NULL))
+ goto cleanup;
+
+ /* Update parent of newly created directory */
+ parent = (HTREEITEM)SendMessageW(info->hwndTreeView, TVM_GETNEXTITEM, TVGN_CARET, 0);
+ if(!parent)
+ goto cleanup;
+
+ SendMessageW(info->hwndTreeView, TVM_EXPAND, TVE_EXPAND, (LPARAM)parent);
+
+ memset(&item, 0, sizeof(TVITEMW));
+ item.mask = TVIF_PARAM|TVIF_STATE;
+ item.hItem = parent;
+ SendMessageW(info->hwndTreeView, TVM_GETITEMW, 0, (LPARAM)&item);
+ item_data = (LPTV_ITEMDATA)item.lParam;
+ if(!item_data)
+ goto cleanup;
+
+ if(item_data->pEnumIL)
+ item_data->pEnumIL->Release();
+ hr = cur->EnumObjects(info->hwndTreeView, flags, &item_data->pEnumIL);
+ if(FAILED(hr))
+ goto cleanup;
+
+ /* Update treeview */
+ if(!(item.state&TVIS_EXPANDEDONCE)) {
+ item.mask = TVIF_STATE;
+ item.state = TVIS_EXPANDEDONCE;
+ item.stateMask = TVIS_EXPANDEDONCE;
+ SendMessageW(info->hwndTreeView, TVM_SETITEMW, 0, (LPARAM)&item);
+ }
+
+ hr = cur->ParseDisplayName(NULL, NULL, name+len, NULL, &new_item, NULL);
+ if(FAILED(hr))
+ goto cleanup;
+
+ added = InsertTreeViewItem(info, cur, new_item, item_data->lpifq, NULL, parent);
+ SHFree(new_item);
+
+ SendMessageW(info->hwndTreeView, TVM_SORTCHILDREN, FALSE, (LPARAM)parent);
+ return BrsFolder_Rename(info, added);
+
+cleanup:
+ return hr;
+}
+
+static BOOL BrsFolder_OnCommand( browse_info *info, UINT id )
+{
+ LPBROWSEINFOW lpBrowseInfo = info->lpBrowseInfo;
+
+ switch (id)
+ {
+ case IDOK:
+ /* The original pidl is owned by the treeview and will be free'd. */
+ info->pidlRet = ILClone(info->pidlRet);
+ if (info->pidlRet == NULL) /* A null pidl would mean a cancel */
+ info->pidlRet = _ILCreateDesktop();
+ pdump( info->pidlRet );
+ if (lpBrowseInfo->pszDisplayName)
+ SHGetPathFromIDListW( info->pidlRet, lpBrowseInfo->pszDisplayName );
+ EndDialog( info->hWnd, 1 );
+ return TRUE;
+
+ case IDCANCEL:
+ EndDialog( info->hWnd, 0 );
+ return TRUE;
+
+ case IDC_BROWSE_FOR_FOLDER_NEW_FOLDER:
+ BrsFolder_NewFolder(info);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static BOOL BrsFolder_OnSetExpanded(browse_info *info, LPVOID selection,
+ BOOL is_str, HTREEITEM *pItem)
+{
+ LPITEMIDLIST pidlSelection = (LPITEMIDLIST)selection;
+ LPCITEMIDLIST pidlCurrent, pidlRoot;
+ TVITEMEXW item;
+ BOOL bResult = FALSE;
+
+ memset(&item, 0, sizeof(item));
+
+ /* If 'selection' is a string, convert to a Shell ID List. */
+ if (is_str) {
+ CComPtr<IShellFolder> psfDesktop;
+ HRESULT hr;
+
+ hr = SHGetDesktopFolder(&psfDesktop);
+ if (FAILED(hr))
+ goto done;
+
+ hr = psfDesktop->ParseDisplayName(NULL, NULL, (LPOLESTR)selection,
+ NULL, &pidlSelection, NULL);
+ if (FAILED(hr))
+ goto done;
+ }
+
+ /* Move pidlCurrent behind the SHITEMIDs in pidlSelection, which are the root of
+ * the sub-tree currently displayed. */
+ pidlRoot = info->lpBrowseInfo->pidlRoot;
+ pidlCurrent = pidlSelection;
+ while (!_ILIsEmpty(pidlRoot) && _ILIsEqualSimple(pidlRoot, pidlCurrent)) {
+ pidlRoot = ILGetNext(pidlRoot);
+ pidlCurrent = ILGetNext(pidlCurrent);
+ }
+
+ /* The given ID List is not part of the SHBrowseForFolder's current sub-tree. */
+ if (!_ILIsEmpty(pidlRoot))
+ goto done;
+
+ /* Initialize item to point to the first child of the root folder. */
+ item.mask = TVIF_PARAM;
+ item.hItem = (HTREEITEM)SendMessageW(info->hwndTreeView, TVM_GETNEXTITEM, TVGN_ROOT, 0);
+
+ if (item.hItem)
+ item.hItem = (HTREEITEM)SendMessageW(info->hwndTreeView, TVM_GETNEXTITEM, TVGN_CHILD,
+ (LPARAM)item.hItem);
+
+ /* Walk the tree along the nodes corresponding to the remaining ITEMIDLIST */
+ while (item.hItem && !_ILIsEmpty(pidlCurrent)) {
+ LPTV_ITEMDATA pItemData;
+
+ SendMessageW(info->hwndTreeView, TVM_GETITEMW, 0, (LPARAM)&item);
+ pItemData = (LPTV_ITEMDATA)item.lParam;
+
+ if (_ILIsEqualSimple(pItemData->lpi, pidlCurrent)) {
+ pidlCurrent = ILGetNext(pidlCurrent);
+ if (!_ILIsEmpty(pidlCurrent)) {
+ /* Only expand current node and move on to it's first child,
+ * if we didn't already reach the last SHITEMID */
+ SendMessageW(info->hwndTreeView, TVM_EXPAND, TVE_EXPAND, (LPARAM)item.hItem);
+ item.hItem = (HTREEITEM)SendMessageW(info->hwndTreeView, TVM_GETNEXTITEM, TVGN_CHILD,
+ (LPARAM)item.hItem);
+ }
+ } else {
+ item.hItem = (HTREEITEM)SendMessageW(info->hwndTreeView, TVM_GETNEXTITEM, TVGN_NEXT,
+ (LPARAM)item.hItem);
+ }
+ }
+
+ if (_ILIsEmpty(pidlCurrent) && item.hItem)
+ bResult = TRUE;
+
+done:
+ if (pidlSelection && pidlSelection != (LPITEMIDLIST)selection)
+ ILFree(pidlSelection);
+
+ if (pItem)
+ *pItem = item.hItem;
+
+ return bResult;
+}
+
+static BOOL BrsFolder_OnSetSelectionW(browse_info *info, LPVOID selection, BOOL is_str) {
+ HTREEITEM hItem;
+ BOOL bResult;
+
+ if (!selection) return FALSE;
+
+ bResult = BrsFolder_OnSetExpanded(info, selection, is_str, &hItem);
+ if (bResult)
+ SendMessageW(info->hwndTreeView, TVM_SELECTITEM, TVGN_CARET, (LPARAM)hItem );
+ return bResult;
+}
+
+static BOOL BrsFolder_OnSetSelectionA(browse_info *info, LPVOID selection, BOOL is_str) {
+ LPWSTR selectionW = NULL;
+ BOOL result = FALSE;
+ int length;
+
+ if (!is_str)
+ return BrsFolder_OnSetSelectionW(info, selection, is_str);
+
+ if ((length = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)selection, -1, NULL, 0)) &&
+ (selectionW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, length * sizeof(WCHAR))) &&
+ MultiByteToWideChar(CP_ACP, 0, (LPCSTR)selection, -1, selectionW, length))
+ {
+ result = BrsFolder_OnSetSelectionW(info, selectionW, is_str);
+ }
+
+ HeapFree(GetProcessHeap(), 0, selectionW);
+ return result;
+}
+
+static BOOL BrsFolder_OnWindowPosChanging(browse_info *info, WINDOWPOS *pos)
+{
+ if ((info->lpBrowseInfo->ulFlags & BIF_NEWDIALOGSTYLE) && !(pos->flags & SWP_NOSIZE))
+ {
+ if (pos->cx < info->szMin.cx)
+ pos->cx = info->szMin.cx;
+ if (pos->cy < info->szMin.cy)
+ pos->cy = info->szMin.cy;
+ }
+ return 0;
+}
+
+static INT BrsFolder_OnDestroy(browse_info *info)
+{
+ if (info->layout)
+ {
+ SHFree(info->layout);
+ info->layout = NULL;
+ }
+
+ return 0;
+}
+
+/*************************************************************************
+ * BrsFolderDlgProc32 (not an exported API function)
+ */
+static INT_PTR CALLBACK BrsFolderDlgProc( HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam )
+{
+ browse_info *info;
+
+ TRACE("hwnd=%p msg=%04x 0x%08lx 0x%08lx\n", hWnd, msg, wParam, lParam );
+
+ if (msg == WM_INITDIALOG)
+ return BrsFolder_OnCreate( hWnd, (browse_info*) lParam );
+
+ info = (browse_info*) GetPropW( hWnd, szBrowseFolderInfo );
+
+ switch (msg)
+ {
+ case WM_NOTIFY:
+ return BrsFolder_OnNotify( info, (UINT)wParam, (LPNMHDR)lParam);
+
+ case WM_COMMAND:
+ return BrsFolder_OnCommand( info, wParam );
+
+ case WM_WINDOWPOSCHANGING:
+ return BrsFolder_OnWindowPosChanging( info, (WINDOWPOS *)lParam);
+
+ case WM_SIZE:
+ if (info->layout) /* new style dialogs */
+ LayoutUpdate(hWnd, info->layout, g_layout_info, LAYOUT_INFO_COUNT);
+ return 0;
+
+ case BFFM_SETSTATUSTEXTA:
+ TRACE("Set status %s\n", debugstr_a((LPSTR)lParam));
+ SetWindowTextA(GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_STATUS), (LPSTR)lParam);
+ break;
+
+ case BFFM_SETSTATUSTEXTW:
+ TRACE("Set status %s\n", debugstr_w((LPWSTR)lParam));
+ SetWindowTextW(GetDlgItem(hWnd, IDC_BROWSE_FOR_FOLDER_STATUS), (LPWSTR)lParam);
+ break;
+
+ case BFFM_ENABLEOK:
+ TRACE("Enable %ld\n", lParam);
+ EnableWindow(GetDlgItem(hWnd, 1), (lParam)?TRUE:FALSE);
+ break;
+
+ case BFFM_SETOKTEXT: /* unicode only */
+ TRACE("Set OK text %s\n", debugstr_w((LPWSTR)wParam));
+ SetWindowTextW(GetDlgItem(hWnd, 1), (LPWSTR)wParam);
+ break;
+
+ case BFFM_SETSELECTIONA:
+ return BrsFolder_OnSetSelectionA(info, (LPVOID)lParam, (BOOL)wParam);
+
+ case BFFM_SETSELECTIONW:
+ return BrsFolder_OnSetSelectionW(info, (LPVOID)lParam, (BOOL)wParam);
+
+ case BFFM_SETEXPANDED: /* unicode only */
+ return BrsFolder_OnSetExpanded(info, (LPVOID)lParam, (BOOL)wParam, NULL);
+
+ case WM_DESTROY:
+ return BrsFolder_OnDestroy(info);
+ }
+ return FALSE;
+}
+
+
+
+
+/*************************************************************************
+ * SHBrowseForFolderA [SHELL32.@]
+ * SHBrowseForFolder [SHELL32.@]
+ */
+LPITEMIDLIST WINAPI SHBrowseForFolderA (LPBROWSEINFOA lpbi)
+{
+ BROWSEINFOW bi;
+ LPITEMIDLIST lpid;
+ INT len;
+ LPWSTR title;
+
+ TRACE("%p\n", lpbi);
+
+ bi.hwndOwner = lpbi->hwndOwner;
+ bi.pidlRoot = lpbi->pidlRoot;
+ if (lpbi->pszDisplayName)
+ {
+ bi.pszDisplayName = (WCHAR *)HeapAlloc( GetProcessHeap(), 0, MAX_PATH * sizeof(WCHAR) );
+ MultiByteToWideChar( CP_ACP, 0, lpbi->pszDisplayName, -1, bi.pszDisplayName, MAX_PATH );
+ }
+ else
+ bi.pszDisplayName = NULL;
+
+ if (lpbi->lpszTitle)
+ {
+ len = MultiByteToWideChar( CP_ACP, 0, lpbi->lpszTitle, -1, NULL, 0 );
+ title = (WCHAR *)HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) );
+ MultiByteToWideChar( CP_ACP, 0, lpbi->lpszTitle, -1, title, len );
+ }
+ else
+ title = NULL;
+
+ bi.lpszTitle = title;
+ bi.ulFlags = lpbi->ulFlags;
+ bi.lpfn = lpbi->lpfn;
+ bi.lParam = lpbi->lParam;
+ bi.iImage = lpbi->iImage;
+ lpid = SHBrowseForFolderW( &bi );
+ if (bi.pszDisplayName)
+ {
+ WideCharToMultiByte( CP_ACP, 0, bi.pszDisplayName, -1,
+ lpbi->pszDisplayName, MAX_PATH, 0, NULL);
+ HeapFree( GetProcessHeap(), 0, bi.pszDisplayName );
+ }
+ HeapFree(GetProcessHeap(), 0, title);
+ lpbi->iImage = bi.iImage;
+ return lpid;
+}
+
+
+/*************************************************************************
+ * SHBrowseForFolderW [SHELL32.@]
+ *
+ * NOTES
+ * crashes when passed a null pointer
+ */
+LPITEMIDLIST WINAPI SHBrowseForFolderW (LPBROWSEINFOW lpbi)
+{
+ browse_info info;
+ DWORD r;
+ HRESULT hr;
+ WORD wDlgId;
+
+ info.hWnd = 0;
+ info.pidlRet = NULL;
+ info.lpBrowseInfo = lpbi;
+ info.hwndTreeView = NULL;
+
+ hr = OleInitialize(NULL);
+
+ if (lpbi->ulFlags & BIF_NEWDIALOGSTYLE)
+ wDlgId = IDD_BROWSE_FOR_FOLDER_NEW;
+ else
+ wDlgId = IDD_BROWSE_FOR_FOLDER;
+ r = DialogBoxParamW( shell32_hInstance, MAKEINTRESOURCEW(wDlgId), lpbi->hwndOwner,
+ BrsFolderDlgProc, (LPARAM)&info );
+ if (SUCCEEDED(hr))
+ OleUninitialize();
+ if (!r)
+ {
+ ILFree(info.pidlRet);
+ return NULL;
+ }
+
+ return info.pidlRet;
+}
--- /dev/null
- #include "xdg.h"
-
+/*
+ * SHFileOperation
+ *
+ * Copyright 2000 Juergen Schmied
+ * Copyright 2002 Andriy Palamarchuk
+ * Copyright 2004 Dietrich Teickner (from Odin)
+ * Copyright 2004 Rolf Kalbermatter
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include "precomp.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(shell);
+
+#define IsAttrib(x, y) ((INVALID_FILE_ATTRIBUTES != (x)) && ((x) & (y)))
+#define IsAttribFile(x) (!((x) & FILE_ATTRIBUTE_DIRECTORY))
+#define IsAttribDir(x) IsAttrib(x, FILE_ATTRIBUTE_DIRECTORY)
+#define IsDotDir(x) ((x[0] == '.') && ((x[1] == 0) || ((x[1] == '.') && (x[2] == 0))))
+
+#define FO_MASK 0xF
+#define WM_FILE (WM_USER + 1)
+#define TIMER_ID (100)
+
+static const WCHAR wWildcardFile[] = {'*',0};
+static const WCHAR wWildcardChars[] = {'*','?',0};
+
+static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec);
+static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path);
+static DWORD SHNotifyDeleteFileW(LPCWSTR path);
+static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest, BOOL isdir);
+static DWORD SHNotifyCopyFileW(LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists);
+static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly);
+
+typedef struct
+{
+ SHFILEOPSTRUCTW *req;
+ DWORD dwYesToAllMask;
+ BOOL bManyItems;
+ BOOL bCancelled;
+} FILE_OPERATION;
+
+#define ERROR_SHELL_INTERNAL_FILE_NOT_FOUND 1026
+
+typedef struct
+{
+ DWORD attributes;
+ LPWSTR szDirectory;
+ LPWSTR szFilename;
+ LPWSTR szFullPath;
+ BOOL bFromWildcard;
+ BOOL bFromRelative;
+ BOOL bExists;
+} FILE_ENTRY;
+
+typedef struct
+{
+ FILE_ENTRY *feFiles;
+ DWORD num_alloc;
+ DWORD dwNumFiles;
+ BOOL bAnyFromWildcard;
+ BOOL bAnyDirectories;
+ BOOL bAnyDontExist;
+} FILE_LIST;
+
+typedef struct
+{
+ FILE_LIST * from;
+ FILE_LIST * to;
+ FILE_OPERATION * op;
+ DWORD Index;
+ HWND hDlgCtrl;
+ HWND hwndDlg;
+}FILE_OPERATION_CONTEXT;
+
+
+/* Confirm dialogs with an optional "Yes To All" as used in file operations confirmations
+ */
+static const WCHAR CONFIRM_MSG_PROP[] = {'W','I','N','E','_','C','O','N','F','I','R','M',0};
+
+struct confirm_msg_info
+{
+ LPWSTR lpszText;
+ LPWSTR lpszCaption;
+ HICON hIcon;
+ BOOL bYesToAll;
+};
+
+/* as some buttons may be hidden and the dialog height may change we may need
+ * to move the controls */
+static void confirm_msg_move_button(HWND hDlg, INT iId, INT *xPos, INT yOffset, BOOL bShow)
+{
+ HWND hButton = GetDlgItem(hDlg, iId);
+ RECT r;
+
+ if (bShow)
+ {
+ POINT pt;
+ int width;
+
+ GetWindowRect(hButton, &r);
+ width = r.right - r.left;
+ pt.x = r.left;
+ pt.y = r.top;
+ ScreenToClient(hDlg, &pt);
+ MoveWindow(hButton, *xPos - width, pt.y - yOffset, width, r.bottom - r.top, FALSE);
+ *xPos -= width + 5;
+ }
+ else
+ ShowWindow(hButton, SW_HIDE);
+}
+
+/* Note: we paint the text manually and don't use the static control to make
+ * sure the text has the same height as the one computed in WM_INITDIALOG
+ */
+static INT_PTR ConfirmMsgBox_Paint(HWND hDlg)
+{
+ PAINTSTRUCT ps;
+ HFONT hOldFont;
+ RECT r;
+ HDC hdc;
+
+ BeginPaint(hDlg, &ps);
+ hdc = ps.hdc;
+
+ GetClientRect(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), &r);
+ /* this will remap the rect to dialog coords */
+ MapWindowPoints(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), hDlg, (LPPOINT)&r, 2);
+ hOldFont = (HFONT)SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDC_YESTOALL_MESSAGE, WM_GETFONT, 0, 0));
+ DrawTextW(hdc, (LPWSTR)GetPropW(hDlg, CONFIRM_MSG_PROP), -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK);
+ SelectObject(hdc, hOldFont);
+ EndPaint(hDlg, &ps);
+
+ return TRUE;
+}
+
+static INT_PTR ConfirmMsgBox_Init(HWND hDlg, LPARAM lParam)
+{
+ struct confirm_msg_info *info = (struct confirm_msg_info *)lParam;
+ INT xPos, yOffset;
+ int width, height;
+ HFONT hOldFont;
+ HDC hdc;
+ RECT r;
+
+ SetWindowTextW(hDlg, info->lpszCaption);
+ ShowWindow(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), SW_HIDE);
+ SetPropW(hDlg, CONFIRM_MSG_PROP, info->lpszText);
+ SendDlgItemMessageW(hDlg, IDC_YESTOALL_ICON, STM_SETICON, (WPARAM)info->hIcon, 0);
+
+ /* compute the text height and resize the dialog */
+ GetClientRect(GetDlgItem(hDlg, IDC_YESTOALL_MESSAGE), &r);
+ hdc = GetDC(hDlg);
+ yOffset = r.bottom;
+ hOldFont = (HFONT)SelectObject(hdc, (HFONT)SendDlgItemMessageW(hDlg, IDC_YESTOALL_MESSAGE, WM_GETFONT, 0, 0));
+ DrawTextW(hdc, info->lpszText, -1, &r, DT_NOPREFIX | DT_PATH_ELLIPSIS | DT_WORDBREAK | DT_CALCRECT);
+ SelectObject(hdc, hOldFont);
+ yOffset -= r.bottom;
+ yOffset = min(yOffset, 35); /* don't make the dialog too small */
+ ReleaseDC(hDlg, hdc);
+
+ GetClientRect(hDlg, &r);
+ xPos = r.right - 7;
+ GetWindowRect(hDlg, &r);
+ width = r.right - r.left;
+ height = r.bottom - r.top - yOffset;
+ MoveWindow(hDlg, (GetSystemMetrics(SM_CXSCREEN) - width)/2,
+ (GetSystemMetrics(SM_CYSCREEN) - height)/2, width, height, FALSE);
+
+ confirm_msg_move_button(hDlg, IDCANCEL, &xPos, yOffset, info->bYesToAll);
+ confirm_msg_move_button(hDlg, IDNO, &xPos, yOffset, TRUE);
+ confirm_msg_move_button(hDlg, IDC_YESTOALL, &xPos, yOffset, info->bYesToAll);
+ confirm_msg_move_button(hDlg, IDYES, &xPos, yOffset, TRUE);
+
+ return TRUE;
+}
+
+static INT_PTR CALLBACK ConfirmMsgBoxProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ return ConfirmMsgBox_Init(hDlg, lParam);
+ case WM_PAINT:
+ return ConfirmMsgBox_Paint(hDlg);
+ case WM_COMMAND:
+ EndDialog(hDlg, wParam);
+ break;
+ case WM_CLOSE:
+ EndDialog(hDlg, IDCANCEL);
+ break;
+ }
+ return FALSE;
+}
+
+int SHELL_ConfirmMsgBox(HWND hWnd, LPWSTR lpszText, LPWSTR lpszCaption, HICON hIcon, BOOL bYesToAll)
+{
+ struct confirm_msg_info info;
+
+ info.lpszText = lpszText;
+ info.lpszCaption = lpszCaption;
+ info.hIcon = hIcon;
+ info.bYesToAll = bYesToAll;
+ return DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_YESTOALL_MSGBOX), hWnd, ConfirmMsgBoxProc, (LPARAM)&info);
+}
+
+/* confirmation dialogs content */
+typedef struct
+{
+ HINSTANCE hIconInstance;
+ UINT icon_resource_id;
+ UINT caption_resource_id, text_resource_id;
+} SHELL_ConfirmIDstruc;
+
+static BOOL SHELL_ConfirmIDs(int nKindOfDialog, SHELL_ConfirmIDstruc *ids)
+{
+ ids->hIconInstance = shell32_hInstance;
+ switch (nKindOfDialog)
+ {
+ case ASK_DELETE_FILE:
+ ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
+ ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
+ ids->text_resource_id = IDS_DELETEITEM_TEXT;
+ return TRUE;
+
+ case ASK_DELETE_FOLDER:
+ ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
+ ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION;
+ ids->text_resource_id = IDS_DELETEITEM_TEXT;
+ return TRUE;
+
+ case ASK_DELETE_MULTIPLE_ITEM:
+ ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
+ ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
+ ids->text_resource_id = IDS_DELETEMULTIPLE_TEXT;
+ return TRUE;
+
+ case ASK_TRASH_FILE:
+ ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
+ ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
+ ids->text_resource_id = IDS_TRASHITEM_TEXT;
+ return TRUE;
+
+ case ASK_TRASH_FOLDER:
+ ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
+ ids->caption_resource_id = IDS_DELETEFOLDER_CAPTION;
+ ids->text_resource_id = IDS_TRASHFOLDER_TEXT;
+ return TRUE;
+
+ case ASK_TRASH_MULTIPLE_ITEM:
+ ids->icon_resource_id = IDI_SHELL_TRASH_FILE;
+ ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
+ ids->text_resource_id = IDS_TRASHMULTIPLE_TEXT;
+ return TRUE;
+
+ case ASK_CANT_TRASH_ITEM:
+ ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
+ ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
+ ids->text_resource_id = IDS_CANTTRASH_TEXT;
+ return TRUE;
+
+ case ASK_DELETE_SELECTED:
+ ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
+ ids->caption_resource_id = IDS_DELETEITEM_CAPTION;
+ ids->text_resource_id = IDS_DELETESELECTED_TEXT;
+ return TRUE;
+
+ case ASK_OVERWRITE_FILE:
+ ids->hIconInstance = NULL;
+ ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
+ ids->caption_resource_id = IDS_OVERWRITEFILE_CAPTION;
+ ids->text_resource_id = IDS_OVERWRITEFILE_TEXT;
+ return TRUE;
+
+ case ASK_OVERWRITE_FOLDER:
+ ids->hIconInstance = NULL;
+ ids->icon_resource_id = IDI_SHELL_CONFIRM_DELETE;
+ ids->caption_resource_id = IDS_OVERWRITEFILE_CAPTION;
+ ids->text_resource_id = IDS_OVERWRITEFOLDER_TEXT;
+ return TRUE;
+
+ default:
+ FIXME(" Unhandled nKindOfDialog %d stub\n", nKindOfDialog);
+ }
+ return FALSE;
+}
+
+static BOOL SHELL_ConfirmDialogW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir, FILE_OPERATION *op)
+{
+ WCHAR szCaption[255], szText[255], szBuffer[MAX_PATH + 256];
+ SHELL_ConfirmIDstruc ids;
+ DWORD_PTR args[1];
+ HICON hIcon;
+ int ret;
+
+ assert(nKindOfDialog >= 0 && nKindOfDialog < 32);
+ if (op && (op->dwYesToAllMask & (1 << nKindOfDialog)))
+ return TRUE;
+
+ if (!SHELL_ConfirmIDs(nKindOfDialog, &ids)) return FALSE;
+
+ LoadStringW(shell32_hInstance, ids.caption_resource_id, szCaption, sizeof(szCaption)/sizeof(WCHAR));
+ LoadStringW(shell32_hInstance, ids.text_resource_id, szText, sizeof(szText)/sizeof(WCHAR));
+
+ args[0] = (DWORD_PTR)szDir;
+ FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
+ szText, 0, 0, szBuffer, sizeof(szBuffer), (va_list*)args);
+ hIcon = LoadIconW(ids.hIconInstance, (LPWSTR)MAKEINTRESOURCE(ids.icon_resource_id));
+
+ ret = SHELL_ConfirmMsgBox(hWnd, szBuffer, szCaption, hIcon, op && op->bManyItems);
+ if (op)
+ {
+ if (ret == IDC_YESTOALL)
+ {
+ op->dwYesToAllMask |= (1 << nKindOfDialog);
+ ret = IDYES;
+ }
+ if (ret == IDCANCEL)
+ op->bCancelled = TRUE;
+ if (ret != IDYES)
+ op->req->fAnyOperationsAborted = TRUE;
+ }
+ return ret == IDYES;
+}
+
+BOOL SHELL_ConfirmYesNoW(HWND hWnd, int nKindOfDialog, LPCWSTR szDir)
+{
+ return SHELL_ConfirmDialogW(hWnd, nKindOfDialog, szDir, NULL);
+}
+
+static DWORD SHELL32_AnsiToUnicodeBuf(LPCSTR aPath, LPWSTR *wPath, DWORD minChars)
+{
+ DWORD len = MultiByteToWideChar(CP_ACP, 0, aPath, -1, NULL, 0);
+
+ if (len < minChars)
+ len = minChars;
+
+ *wPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
+ if (*wPath)
+ {
+ MultiByteToWideChar(CP_ACP, 0, aPath, -1, *wPath, len);
+ return NO_ERROR;
+ }
+ return E_OUTOFMEMORY;
+}
+
+static void SHELL32_FreeUnicodeBuf(LPWSTR wPath)
+{
+ HeapFree(GetProcessHeap(), 0, wPath);
+}
+
+EXTERN_C HRESULT WINAPI SHIsFileAvailableOffline(LPCWSTR path, LPDWORD status)
+{
+ FIXME("(%s, %p) stub\n", debugstr_w(path), status);
+ return E_FAIL;
+}
+
+/**************************************************************************
+ * SHELL_DeleteDirectory() [internal]
+ *
+ * Asks for confirmation when bShowUI is true and deletes the directory and
+ * all its subdirectories and files if necessary.
+ */
+BOOL SHELL_DeleteDirectoryW(HWND hwnd, LPCWSTR pszDir, BOOL bShowUI)
+{
+ BOOL ret = TRUE;
+ HANDLE hFind;
+ WIN32_FIND_DATAW wfd;
+ WCHAR szTemp[MAX_PATH];
+
+ /* Make sure the directory exists before eventually prompting the user */
+ PathCombineW(szTemp, pszDir, wWildcardFile);
+ hFind = FindFirstFileW(szTemp, &wfd);
+ if (hFind == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ if (!bShowUI || (ret = SHELL_ConfirmDialogW(hwnd, ASK_DELETE_FOLDER, pszDir, NULL)))
+ {
+ do
+ {
+ if (IsDotDir(wfd.cFileName))
+ continue;
+ PathCombineW(szTemp, pszDir, wfd.cFileName);
+ if (FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes)
+ ret = SHELL_DeleteDirectoryW(hwnd, szTemp, FALSE);
+ else
+ ret = (SHNotifyDeleteFileW(szTemp) == ERROR_SUCCESS);
+ } while (ret && FindNextFileW(hFind, &wfd));
+ }
+ FindClose(hFind);
+ if (ret)
+ ret = (SHNotifyRemoveDirectoryW(pszDir) == ERROR_SUCCESS);
+ return ret;
+}
+
+/**************************************************************************
+ * Win32CreateDirectory [SHELL32.93]
+ *
+ * Creates a directory. Also triggers a change notify if one exists.
+ *
+ * PARAMS
+ * path [I] path to directory to create
+ *
+ * RETURNS
+ * TRUE if successful, FALSE otherwise
+ */
+
+static DWORD SHNotifyCreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
+{
+ TRACE("(%s, %p)\n", debugstr_w(path), sec);
+
+ if (CreateDirectoryW(path, sec))
+ {
+ SHChangeNotify(SHCNE_MKDIR, SHCNF_PATHW, path, NULL);
+ return ERROR_SUCCESS;
+ }
+ return GetLastError();
+}
+
+/**********************************************************************/
+
+EXTERN_C BOOL WINAPI Win32CreateDirectoryW(LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
+{
+ return (SHNotifyCreateDirectoryW(path, sec) == ERROR_SUCCESS);
+}
+
+/************************************************************************
+ * Win32RemoveDirectory [SHELL32.94]
+ *
+ * Deletes a directory. Also triggers a change notify if one exists.
+ *
+ * PARAMS
+ * path [I] path to directory to delete
+ *
+ * RETURNS
+ * TRUE if successful, FALSE otherwise
+ */
+static DWORD SHNotifyRemoveDirectoryW(LPCWSTR path)
+{
+ BOOL ret;
+ TRACE("(%s)\n", debugstr_w(path));
+
+ ret = RemoveDirectoryW(path);
+ if (!ret)
+ {
+ /* Directory may be write protected */
+ DWORD dwAttr = GetFileAttributesW(path);
+ if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY))
+ if (SetFileAttributesW(path, dwAttr & ~FILE_ATTRIBUTE_READONLY))
+ ret = RemoveDirectoryW(path);
+ }
+ if (ret)
+ {
+ SHChangeNotify(SHCNE_RMDIR, SHCNF_PATHW, path, NULL);
+ return ERROR_SUCCESS;
+ }
+ return GetLastError();
+}
+
+/***********************************************************************/
+
+EXTERN_C BOOL WINAPI Win32RemoveDirectoryW(LPCWSTR path)
+{
+ return (SHNotifyRemoveDirectoryW(path) == ERROR_SUCCESS);
+}
+
+/************************************************************************
+ * Win32DeleteFile [SHELL32.164]
+ *
+ * Deletes a file. Also triggers a change notify if one exists.
+ *
+ * PARAMS
+ * path [I] path to file to delete
+ *
+ * RETURNS
+ * TRUE if successful, FALSE otherwise
+ */
+static DWORD SHNotifyDeleteFileW(LPCWSTR path)
+{
+ BOOL ret;
+
+ TRACE("(%s)\n", debugstr_w(path));
+
+ ret = DeleteFileW(path);
+ if (!ret)
+ {
+ /* File may be write protected or a system file */
+ DWORD dwAttr = GetFileAttributesW(path);
+ if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
+ if (SetFileAttributesW(path, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
+ ret = DeleteFileW(path);
+ }
+ if (ret)
+ {
+ SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, path, NULL);
+ return ERROR_SUCCESS;
+ }
+ return GetLastError();
+}
+
+/***********************************************************************/
+
+EXTERN_C DWORD WINAPI Win32DeleteFileW(LPCWSTR path)
+{
+ return (SHNotifyDeleteFileW(path) == ERROR_SUCCESS);
+}
+
+/************************************************************************
+ * SHNotifyMoveFile [internal]
+ *
+ * Moves a file. Also triggers a change notify if one exists.
+ *
+ * PARAMS
+ * src [I] path to source file to move
+ * dest [I] path to target file to move to
+ *
+ * RETURNS
+ * ERORR_SUCCESS if successful
+ */
+static DWORD SHNotifyMoveFileW(LPCWSTR src, LPCWSTR dest, BOOL isdir)
+{
+ BOOL ret;
+
+ TRACE("(%s %s)\n", debugstr_w(src), debugstr_w(dest));
+
+ ret = MoveFileExW(src, dest, MOVEFILE_REPLACE_EXISTING);
+
+ /* MOVEFILE_REPLACE_EXISTING fails with dirs, so try MoveFile */
+ if (!ret)
+ ret = MoveFileW(src, dest);
+
+ if (!ret)
+ {
+ DWORD dwAttr;
+
+ dwAttr = SHFindAttrW(dest, FALSE);
+ if (INVALID_FILE_ATTRIBUTES == dwAttr)
+ {
+ /* Source file may be write protected or a system file */
+ dwAttr = GetFileAttributesW(src);
+ if (IsAttrib(dwAttr, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
+ if (SetFileAttributesW(src, dwAttr & ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)))
+ ret = MoveFileW(src, dest);
+ }
+ }
+ if (ret)
+ {
+ SHChangeNotify(isdir ? SHCNE_MKDIR : SHCNE_CREATE, SHCNF_PATHW, dest, NULL);
+ SHChangeNotify(isdir ? SHCNE_RMDIR : SHCNE_DELETE, SHCNF_PATHW, src, NULL);
+ return ERROR_SUCCESS;
+ }
+ return GetLastError();
+}
+
+static DWORD WINAPI SHOperationProgressRoutine(LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred, LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, DWORD dwStreamNumber, DWORD dwCallbackReason, HANDLE hSourceFile, HANDLE hDestinationFile, LPVOID lpData)
+{
+ FILE_OPERATION_CONTEXT * Context;
+ LARGE_INTEGER Progress;
+
+ /* get context */
+ Context = (FILE_OPERATION_CONTEXT*)lpData;
+
+ if (TotalBytesTransferred.QuadPart)
+ {
+ Progress.QuadPart = (TotalBytesTransferred.QuadPart * 100) / TotalFileSize.QuadPart;
+ }
+ else
+ {
+ Progress.QuadPart = 1;
+ }
+
+ /* update progress bar */
+ SendMessageW(Context->hDlgCtrl, PBM_SETPOS, (WPARAM)Progress.u.LowPart, 0);
+
+ if (TotalBytesTransferred.QuadPart == TotalFileSize.QuadPart)
+ {
+ /* file was copied */
+ Context->Index++;
+ PostMessageW(Context->hwndDlg, WM_FILE, 0, 0);
+ }
+
+ return PROGRESS_CONTINUE;
+}
+
+BOOL
+QueueFile(
+ FILE_OPERATION_CONTEXT * Context)
+{
+ FILE_ENTRY * from, *to = NULL;
+ BOOL bRet = FALSE;
+
+ if (Context->Index >= Context->from->dwNumFiles)
+ return FALSE;
+
+ /* get current file */
+ from = &Context->from->feFiles[Context->Index];
+
+ if (Context->op->req->wFunc != FO_DELETE)
+ to = &Context->to->feFiles[Context->Index];
+
+ /* update status */
+ SetDlgItemTextW(Context->hwndDlg, 14001, from->szFullPath);
+
+ if (Context->op->req->wFunc == FO_COPY)
+ {
+ bRet = CopyFileExW(from->szFullPath, to->szFullPath, SHOperationProgressRoutine, (LPVOID)Context, &Context->op->bCancelled, 0);
+ }
+ else if (Context->op->req->wFunc == FO_MOVE)
+ {
+ //bRet = MoveFileWithProgressW(from->szFullPath, to->szFullPath, SHOperationProgressRoutine, (LPVOID)Context, MOVEFILE_COPY_ALLOWED);
+ }
+ else if (Context->op->req->wFunc == FO_DELETE)
+ {
+ bRet = DeleteFile(from->szFullPath);
+ }
+
+ return bRet;
+}
+
+static INT_PTR CALLBACK SHOperationDialog(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ FILE_OPERATION_CONTEXT * Context;
+
+ Context = (FILE_OPERATION_CONTEXT*) GetWindowLongPtr(hwndDlg, DWLP_USER);
+
+ switch(uMsg)
+ {
+ case WM_INITDIALOG:
+ SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG)lParam);
+
+ /* get context */
+ Context = (FILE_OPERATION_CONTEXT*)lParam;
+
+ /* store progress bar handle */
+ Context->hDlgCtrl = GetDlgItem(hwndDlg, 14000);
+
+ /* store window handle */
+ Context->hwndDlg = hwndDlg;
+
+ /* set progress bar range */
+ (void)SendMessageW(Context->hDlgCtrl, (UINT) PBM_SETRANGE, 0, MAKELPARAM(0, 100));
+
+ /* start file queueing */
+ SetTimer(hwndDlg, TIMER_ID, 1000, NULL);
+ //QueueFile(Context);
+
+ return TRUE;
+
+ case WM_CLOSE:
+ Context->op->bCancelled = TRUE;
+ EndDialog(hwndDlg, Context->op->bCancelled);
+ return TRUE;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam) == 14002)
+ {
+ Context->op->bCancelled = TRUE;
+ EndDialog(hwndDlg, Context->op->bCancelled);
+ return TRUE;
+ }; break;
+
+ case WM_TIMER:
+ if (wParam == TIMER_ID)
+ {
+ QueueFile(Context);
+ KillTimer(hwndDlg, TIMER_ID);
+ }; break;
+
+ case WM_FILE:
+ if (!QueueFile(Context))
+ EndDialog(hwndDlg, Context->op->bCancelled);
+ default:
+ break;
+ }
+ return FALSE;
+}
+
+HRESULT
+SHShowFileOperationDialog(FILE_OPERATION *op, FILE_LIST *flFrom, FILE_LIST *flTo)
+{
+ HWND hwnd;
+ BOOL bRet;
+ MSG msg;
+ FILE_OPERATION_CONTEXT Context;
+
+ Context.from = flFrom;
+ Context.to = flTo;
+ Context.op = op;
+ Context.Index = 0;
+ Context.op->bCancelled = FALSE;
+
+ hwnd = CreateDialogParam(shell32_hInstance, MAKEINTRESOURCE(IDD_FILE_COPY), NULL, SHOperationDialog, (LPARAM)&Context);
+ if (hwnd == NULL)
+ {
+ ERR("Failed to create dialog\n");
+ return E_FAIL;
+ }
+ ShowWindow(hwnd, SW_SHOWNORMAL);
+
+ while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
+ {
+ if (!IsWindow(hwnd) || !IsDialogMessage(hwnd, &msg))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ }
+
+ return NOERROR;
+}
+
+
+/************************************************************************
+ * SHNotifyCopyFile [internal]
+ *
+ * Copies a file. Also triggers a change notify if one exists.
+ *
+ * PARAMS
+ * src [I] path to source file to move
+ * dest [I] path to target file to move to
+ * bFailIfExists [I] if TRUE, the target file will not be overwritten if
+ * a file with this name already exists
+ *
+ * RETURNS
+ * ERROR_SUCCESS if successful
+ */
+static DWORD SHNotifyCopyFileW(LPCWSTR src, LPCWSTR dest, BOOL bFailIfExists)
+{
+ BOOL ret;
+ DWORD attribs;
+
+ TRACE("(%s %s %s)\n", debugstr_w(src), debugstr_w(dest), bFailIfExists ? "failIfExists" : "");
+
+ /* Destination file may already exist with read only attribute */
+ attribs = GetFileAttributesW(dest);
+ if (IsAttrib(attribs, FILE_ATTRIBUTE_READONLY))
+ SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY);
+
+ if (GetFileAttributesW(dest) & FILE_ATTRIBUTE_READONLY)
+ {
+ SetFileAttributesW(dest, attribs & ~FILE_ATTRIBUTE_READONLY);
+ if (GetFileAttributesW(dest) & FILE_ATTRIBUTE_READONLY)
+ {
+ TRACE("[shell32, SHNotifyCopyFileW] STILL SHIT\n");
+ }
+ }
+
+ ret = CopyFileW(src, dest, bFailIfExists);
+ if (ret)
+ {
+ SHChangeNotify(SHCNE_CREATE, SHCNF_PATHW, dest, NULL);
+ return ERROR_SUCCESS;
+ }
+
+ return GetLastError();
+}
+
+/*************************************************************************
+ * SHCreateDirectory [SHELL32.165]
+ *
+ * This function creates a file system folder whose fully qualified path is
+ * given by path. If one or more of the intermediate folders do not exist,
+ * they will be created as well.
+ *
+ * PARAMS
+ * hWnd [I]
+ * path [I] path of directory to create
+ *
+ * RETURNS
+ * ERROR_SUCCESS or one of the following values:
+ * ERROR_BAD_PATHNAME if the path is relative
+ * ERROR_FILE_EXISTS when a file with that name exists
+ * ERROR_PATH_NOT_FOUND can't find the path, probably invalid
+ * ERROR_INVALID_NAME if the path contains invalid chars
+ * ERROR_ALREADY_EXISTS when the directory already exists
+ * ERROR_FILENAME_EXCED_RANGE if the filename was to long to process
+ *
+ * NOTES
+ * exported by ordinal
+ * Win9x exports ANSI
+ * WinNT/2000 exports Unicode
+ */
+int WINAPI SHCreateDirectory(HWND hWnd, LPCWSTR path)
+{
+ return SHCreateDirectoryExW(hWnd, path, NULL);
+}
+
+/*************************************************************************
+ * SHCreateDirectoryExA [SHELL32.@]
+ *
+ * This function creates a file system folder whose fully qualified path is
+ * given by path. If one or more of the intermediate folders do not exist,
+ * they will be created as well.
+ *
+ * PARAMS
+ * hWnd [I]
+ * path [I] path of directory to create
+ * sec [I] security attributes to use or NULL
+ *
+ * RETURNS
+ * ERROR_SUCCESS or one of the following values:
+ * ERROR_BAD_PATHNAME or ERROR_PATH_NOT_FOUND if the path is relative
+ * ERROR_INVALID_NAME if the path contains invalid chars
+ * ERROR_FILE_EXISTS when a file with that name exists
+ * ERROR_ALREADY_EXISTS when the directory already exists
+ * ERROR_FILENAME_EXCED_RANGE if the filename was to long to process
+ *
+ * FIXME: Not implemented yet;
+ * SHCreateDirectoryEx also verifies that the files in the directory will be visible
+ * if the path is a network path to deal with network drivers which might have a limited
+ * but unknown maximum path length. If not:
+ *
+ * If hWnd is set to a valid window handle, a message box is displayed warning
+ * the user that the files may not be accessible. If the user chooses not to
+ * proceed, the function returns ERROR_CANCELLED.
+ *
+ * If hWnd is set to NULL, no user interface is displayed and the function
+ * returns ERROR_CANCELLED.
+ */
+int WINAPI SHCreateDirectoryExA(HWND hWnd, LPCSTR path, LPSECURITY_ATTRIBUTES sec)
+{
+ LPWSTR wPath;
+ DWORD retCode;
+
+ TRACE("(%s, %p)\n", debugstr_a(path), sec);
+
+ retCode = SHELL32_AnsiToUnicodeBuf(path, &wPath, 0);
+ if (!retCode)
+ {
+ retCode = SHCreateDirectoryExW(hWnd, wPath, sec);
+ SHELL32_FreeUnicodeBuf(wPath);
+ }
+ return retCode;
+}
+
+/*************************************************************************
+ * SHCreateDirectoryExW [SHELL32.@]
+ *
+ * See SHCreateDirectoryExA.
+ */
+int WINAPI SHCreateDirectoryExW(HWND hWnd, LPCWSTR path, LPSECURITY_ATTRIBUTES sec)
+{
+ int ret = ERROR_BAD_PATHNAME;
+ TRACE("(%p, %s, %p)\n", hWnd, debugstr_w(path), sec);
+
+ if (PathIsRelativeW(path))
+ {
+ SetLastError(ret);
+ }
+ else
+ {
+ ret = SHNotifyCreateDirectoryW(path, sec);
+ /* Refuse to work on certain error codes before trying to create directories recursively */
+ if (ret != ERROR_SUCCESS &&
+ ret != ERROR_FILE_EXISTS &&
+ ret != ERROR_ALREADY_EXISTS &&
+ ret != ERROR_FILENAME_EXCED_RANGE)
+ {
+ WCHAR *pEnd, *pSlash, szTemp[MAX_PATH + 1]; /* extra for PathAddBackslash() */
+
+ lstrcpynW(szTemp, path, MAX_PATH);
+ pEnd = PathAddBackslashW(szTemp);
+ pSlash = szTemp + 3;
+
+ while (*pSlash)
+ {
+ while (*pSlash && *pSlash != '\\') pSlash++;
+ if (*pSlash)
+ {
+ *pSlash = 0; /* terminate path at separator */
+
+ ret = SHNotifyCreateDirectoryW(szTemp, pSlash + 1 == pEnd ? sec : NULL);
+ }
+ *pSlash++ = '\\'; /* put the separator back */
+ }
+ }
+
+ if (ret && hWnd && (ERROR_CANCELLED != ret))
+ {
+ /* We failed and should show a dialog box */
+ FIXME("Show system error message, creating path %s, failed with error %d\n", debugstr_w(path), ret);
+ ret = ERROR_CANCELLED; /* Error has been already presented to user (not really yet!) */
+ }
+ }
+
+ return ret;
+}
+
+/*************************************************************************
+ * SHFindAttrW [internal]
+ *
+ * Get the Attributes for a file or directory. The difference to GetAttributes()
+ * is that this function will also work for paths containing wildcard characters
+ * in its filename.
+
+ * PARAMS
+ * path [I] path of directory or file to check
+ * fileOnly [I] TRUE if only files should be found
+ *
+ * RETURNS
+ * INVALID_FILE_ATTRIBUTES if the path does not exist, the actual attributes of
+ * the first file or directory found otherwise
+ */
+static DWORD SHFindAttrW(LPCWSTR pName, BOOL fileOnly)
+{
+ WIN32_FIND_DATAW wfd;
+ BOOL b_FileMask = fileOnly && (NULL != StrPBrkW(pName, wWildcardChars));
+ DWORD dwAttr = INVALID_FILE_ATTRIBUTES;
+ HANDLE hFind = FindFirstFileW(pName, &wfd);
+
+ TRACE("%s %d\n", debugstr_w(pName), fileOnly);
+ if (INVALID_HANDLE_VALUE != hFind)
+ {
+ do
+ {
+ if (b_FileMask && IsAttribDir(wfd.dwFileAttributes))
+ continue;
+ dwAttr = wfd.dwFileAttributes;
+ break;
+ } while (FindNextFileW(hFind, &wfd));
+
+ FindClose(hFind);
+ }
+ return dwAttr;
+}
+
+/*************************************************************************
+ *
+ * _ConvertAtoW helper function for SHFileOperationA
+ *
+ * Converts a string or string-list to unicode.
+ */
+static DWORD _ConvertAtoW(PCSTR strSrc, PCWSTR* pStrDest, BOOL isList)
+{
+ *pStrDest = NULL;
+
+ // If the input is null, nothing to convert.
+ if (!strSrc)
+ return 0;
+
+ // Measure the total size, depending on if it's a zero-terminated list.
+ int sizeA = 0;
+ if (isList)
+ {
+ PCSTR tmpSrc = strSrc;
+ int size;
+ do
+ {
+ size = lstrlenA(tmpSrc) + 1;
+ sizeA += size;
+ tmpSrc += size;
+ } while (size != 1);
+ }
+ else
+ {
+ sizeA = lstrlenA(strSrc) + 1;
+ }
+
+ // Measure the needed allocation size.
+ int sizeW = MultiByteToWideChar(CP_ACP, 0, strSrc, sizeA, NULL, 0);
+ if (!sizeW)
+ return GetLastError();
+
+ PWSTR strDest = (PWSTR) HeapAlloc(GetProcessHeap(), 0, sizeW * sizeof(WCHAR));
+ if (!strDest)
+ return ERROR_OUTOFMEMORY;
+
+ int err = MultiByteToWideChar(CP_ACP, 0, strSrc, sizeA, strDest, sizeW);
+ if (!err)
+ {
+ HeapFree(GetProcessHeap(), 0, strDest);
+ return GetLastError();
+ }
+
+ *pStrDest = strDest;
+ return 0;
+}
+
+/*************************************************************************
+ * SHFileOperationA [SHELL32.@]
+ *
+ * Function to copy, move, delete and create one or more files with optional
+ * user prompts.
+ *
+ * PARAMS
+ * lpFileOp [I/O] pointer to a structure containing all the necessary information
+ *
+ * RETURNS
+ * Success: ERROR_SUCCESS.
+ * Failure: ERROR_CANCELLED.
+ *
+ * NOTES
+ * exported by name
+ */
+int WINAPI SHFileOperationA(LPSHFILEOPSTRUCTA lpFileOp)
+{
+ int errCode, retCode;
+ SHFILEOPSTRUCTW nFileOp = { 0 };
+
+ // Convert A information to W
+ nFileOp.hwnd = lpFileOp->hwnd;
+ nFileOp.wFunc = lpFileOp->wFunc;
+ nFileOp.fFlags = lpFileOp->fFlags;
+
+ errCode = _ConvertAtoW(lpFileOp->pFrom, &nFileOp.pFrom, TRUE);
+ if (errCode != 0)
+ goto cleanup;
+
+ if (FO_DELETE != (nFileOp.wFunc & FO_MASK))
+ {
+ errCode = _ConvertAtoW(lpFileOp->pTo, &nFileOp.pTo, TRUE);
+ if (errCode != 0)
+ goto cleanup;
+ }
+
+ if (nFileOp.fFlags & FOF_SIMPLEPROGRESS)
+ {
+ errCode = _ConvertAtoW(lpFileOp->lpszProgressTitle, &nFileOp.lpszProgressTitle, FALSE);
+ if (errCode != 0)
+ goto cleanup;
+ }
+
+ // Call the actual function
+ retCode = SHFileOperationW(&nFileOp);
+
+ // Cleanup
+cleanup:
+ if (nFileOp.pFrom)
+ HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.pFrom);
+ if (nFileOp.pTo)
+ HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.pTo);
+ if (nFileOp.lpszProgressTitle)
+ HeapFree(GetProcessHeap(), 0, (PVOID) nFileOp.lpszProgressTitle);
+
+ if (errCode != 0)
+ {
+ lpFileOp->fAnyOperationsAborted = TRUE;
+ SetLastError(errCode);
+
+ return errCode;
+ }
+
+ // Thankfully, starting with NT4 the name mappings are always unicode, so no need to convert.
+ lpFileOp->hNameMappings = nFileOp.hNameMappings;
+ lpFileOp->fAnyOperationsAborted = nFileOp.fAnyOperationsAborted;
+ return retCode;
+}
+
+static void __inline grow_list(FILE_LIST *list)
+{
+ FILE_ENTRY *newx = (FILE_ENTRY *)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, list->feFiles,
+ list->num_alloc * 2 * sizeof(*newx) );
+ list->feFiles = newx;
+ list->num_alloc *= 2;
+}
+
+/* adds a file to the FILE_ENTRY struct
+ */
+static void add_file_to_entry(FILE_ENTRY *feFile, LPCWSTR szFile)
+{
+ DWORD dwLen = lstrlenW(szFile) + 1;
+ LPCWSTR ptr;
+
+ feFile->szFullPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
+ lstrcpyW(feFile->szFullPath, szFile);
+
+ ptr = StrRChrW(szFile, NULL, '\\');
+ if (ptr)
+ {
+ dwLen = ptr - szFile + 1;
+ feFile->szDirectory = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
+ lstrcpynW(feFile->szDirectory, szFile, dwLen);
+
+ dwLen = lstrlenW(feFile->szFullPath) - dwLen + 1;
+ feFile->szFilename = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwLen * sizeof(WCHAR));
+ lstrcpyW(feFile->szFilename, ptr + 1); /* skip over backslash */
+ }
+ feFile->bFromWildcard = FALSE;
+}
+
+static LPWSTR wildcard_to_file(LPCWSTR szWildCard, LPCWSTR szFileName)
+{
+ LPCWSTR ptr;
+ LPWSTR szFullPath;
+ DWORD dwDirLen, dwFullLen;
+
+ ptr = StrRChrW(szWildCard, NULL, '\\');
+ dwDirLen = ptr - szWildCard + 1;
+
+ dwFullLen = dwDirLen + lstrlenW(szFileName) + 1;
+ szFullPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwFullLen * sizeof(WCHAR));
+
+ lstrcpynW(szFullPath, szWildCard, dwDirLen + 1);
+ lstrcatW(szFullPath, szFileName);
+
+ return szFullPath;
+}
+
+static void parse_wildcard_files(FILE_LIST *flList, LPCWSTR szFile, LPDWORD pdwListIndex)
+{
+ WIN32_FIND_DATAW wfd;
+ HANDLE hFile = FindFirstFileW(szFile, &wfd);
+ FILE_ENTRY *file;
+ LPWSTR szFullPath;
+ BOOL res;
+
+ if (hFile == INVALID_HANDLE_VALUE) return;
+
+ for (res = TRUE; res; res = FindNextFileW(hFile, &wfd))
+ {
+ if (IsDotDir(wfd.cFileName))
+ continue;
+
+ if (*pdwListIndex >= flList->num_alloc)
+ grow_list( flList );
+
+ szFullPath = wildcard_to_file(szFile, wfd.cFileName);
+ file = &flList->feFiles[(*pdwListIndex)++];
+ add_file_to_entry(file, szFullPath);
+ file->bFromWildcard = TRUE;
+ file->attributes = wfd.dwFileAttributes;
+
+ if (IsAttribDir(file->attributes))
+ flList->bAnyDirectories = TRUE;
+
+ HeapFree(GetProcessHeap(), 0, szFullPath);
+ }
+
+ FindClose(hFile);
+}
+
+/* takes the null-separated file list and fills out the FILE_LIST */
+static HRESULT parse_file_list(FILE_LIST *flList, LPCWSTR szFiles)
+{
+ LPCWSTR ptr = szFiles;
+ WCHAR szCurFile[MAX_PATH];
+ DWORD i = 0;
+
+ if (!szFiles)
+ return ERROR_INVALID_PARAMETER;
+
+ flList->bAnyFromWildcard = FALSE;
+ flList->bAnyDirectories = FALSE;
+ flList->bAnyDontExist = FALSE;
+ flList->num_alloc = 32;
+ flList->dwNumFiles = 0;
+
+ /* empty list */
+ if (!szFiles[0])
+ return ERROR_ACCESS_DENIED;
+
+ flList->feFiles = (FILE_ENTRY *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
+ flList->num_alloc * sizeof(FILE_ENTRY));
+
+ while (*ptr)
+ {
+ if (i >= flList->num_alloc) grow_list( flList );
+
+ /* change relative to absolute path */
+ if (PathIsRelativeW(ptr))
+ {
+ GetCurrentDirectoryW(MAX_PATH, szCurFile);
+ PathCombineW(szCurFile, szCurFile, ptr);
+ flList->feFiles[i].bFromRelative = TRUE;
+ }
+ else
+ {
+ lstrcpyW(szCurFile, ptr);
+ flList->feFiles[i].bFromRelative = FALSE;
+ }
+
+ /* parse wildcard files if they are in the filename */
+ if (StrPBrkW(szCurFile, wWildcardChars))
+ {
+ parse_wildcard_files(flList, szCurFile, &i);
+ flList->bAnyFromWildcard = TRUE;
+ i--;
+ }
+ else
+ {
+ FILE_ENTRY *file = &flList->feFiles[i];
+ add_file_to_entry(file, szCurFile);
+ file->attributes = GetFileAttributesW( file->szFullPath );
+ file->bExists = (file->attributes != INVALID_FILE_ATTRIBUTES);
+
+ if (!file->bExists)
+ flList->bAnyDontExist = TRUE;
+
+ if (IsAttribDir(file->attributes))
+ flList->bAnyDirectories = TRUE;
+ }
+
+ /* advance to the next string */
+ ptr += lstrlenW(ptr) + 1;
+ i++;
+ }
+ flList->dwNumFiles = i;
+
+ return S_OK;
+}
+
+/* free the FILE_LIST */
+static void destroy_file_list(FILE_LIST *flList)
+{
+ DWORD i;
+
+ if (!flList || !flList->feFiles)
+ return;
+
+ for (i = 0; i < flList->dwNumFiles; i++)
+ {
+ HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szDirectory);
+ HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFilename);
+ HeapFree(GetProcessHeap(), 0, flList->feFiles[i].szFullPath);
+ }
+
+ HeapFree(GetProcessHeap(), 0, flList->feFiles);
+}
+
+static void copy_dir_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, LPCWSTR szDestPath)
+{
+ WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
+ SHFILEOPSTRUCTW fileOp;
+
+ static const WCHAR wildCardFiles[] = {'*','.','*',0};
+
+ if (IsDotDir(feFrom->szFilename))
+ return;
+
+ if (PathFileExistsW(szDestPath))
+ PathCombineW(szTo, szDestPath, feFrom->szFilename);
+ else
+ lstrcpyW(szTo, szDestPath);
+
+ if (!(op->req->fFlags & FOF_NOCONFIRMATION) && PathFileExistsW(szTo))
+ {
+ if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FOLDER, feFrom->szFilename, op))
+ {
+ /* Vista returns an ERROR_CANCELLED even if user pressed "No" */
+ if (!op->bManyItems)
+ op->bCancelled = TRUE;
+ return;
+ }
+ }
+
+ szTo[lstrlenW(szTo) + 1] = '\0';
+ SHNotifyCreateDirectoryW(szTo, NULL);
+
+ PathCombineW(szFrom, feFrom->szFullPath, wildCardFiles);
+ szFrom[lstrlenW(szFrom) + 1] = '\0';
+
+ fileOp = *op->req;
+ fileOp.pFrom = szFrom;
+ fileOp.pTo = szTo;
+ fileOp.fFlags &= ~FOF_MULTIDESTFILES; /* we know we're copying to one dir */
+
+ /* Don't ask the user about overwriting files when he accepted to overwrite the
+ folder. FIXME: this is not exactly what Windows does - e.g. there would be
+ an additional confirmation for a nested folder */
+ fileOp.fFlags |= FOF_NOCONFIRMATION;
+
+ SHFileOperationW(&fileOp);
+}
+
+static BOOL copy_file_to_file(FILE_OPERATION *op, const WCHAR *szFrom, const WCHAR *szTo)
+{
+ if (!(op->req->fFlags & FOF_NOCONFIRMATION) && PathFileExistsW(szTo))
+ {
+ if (!SHELL_ConfirmDialogW(op->req->hwnd, ASK_OVERWRITE_FILE, PathFindFileNameW(szTo), op))
+ return 0;
+ }
+
+ return SHNotifyCopyFileW(szFrom, szTo, FALSE) == 0;
+}
+
+/* copy a file or directory to another directory */
+static void copy_to_dir(FILE_OPERATION *op, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo)
+{
+ if (!PathFileExistsW(feTo->szFullPath))
+ SHNotifyCreateDirectoryW(feTo->szFullPath, NULL);
+
+ if (IsAttribFile(feFrom->attributes))
+ {
+ WCHAR szDestPath[MAX_PATH];
+
+ PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename);
+ copy_file_to_file(op, feFrom->szFullPath, szDestPath);
+ }
+ else if (!(op->req->fFlags & FOF_FILESONLY && feFrom->bFromWildcard))
+ copy_dir_to_dir(op, feFrom, feTo->szFullPath);
+}
+
+static void create_dest_dirs(LPCWSTR szDestDir)
+{
+ WCHAR dir[MAX_PATH];
+ LPCWSTR ptr = StrChrW(szDestDir, '\\');
+
+ /* make sure all directories up to last one are created */
+ while (ptr && (ptr = StrChrW(ptr + 1, '\\')))
+ {
+ lstrcpynW(dir, szDestDir, ptr - szDestDir + 1);
+
+ if (!PathFileExistsW(dir))
+ SHNotifyCreateDirectoryW(dir, NULL);
+ }
+
+ /* create last directory */
+ if (!PathFileExistsW(szDestDir))
+ SHNotifyCreateDirectoryW(szDestDir, NULL);
+}
+
+/* the FO_COPY operation */
+static HRESULT copy_files(FILE_OPERATION *op, const FILE_LIST *flFrom, FILE_LIST *flTo)
+{
+ DWORD i;
+ const FILE_ENTRY *entryToCopy;
+ const FILE_ENTRY *fileDest = &flTo->feFiles[0];
+
+ if (flFrom->bAnyDontExist)
+ return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
+
+ if (flTo->dwNumFiles == 0)
+ {
+ /* If the destination is empty, SHFileOperation should use the current directory */
+ WCHAR curdir[MAX_PATH+1];
+
+ GetCurrentDirectoryW(MAX_PATH, curdir);
+ curdir[lstrlenW(curdir)+1] = 0;
+
+ destroy_file_list(flTo);
+ ZeroMemory(flTo, sizeof(FILE_LIST));
+ parse_file_list(flTo, curdir);
+ fileDest = &flTo->feFiles[0];
+ }
+
+ if (op->req->fFlags & FOF_MULTIDESTFILES)
+ {
+ if (flFrom->bAnyFromWildcard)
+ return ERROR_CANCELLED;
+
+ if (flFrom->dwNumFiles != flTo->dwNumFiles)
+ {
+ if (flFrom->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes))
+ return ERROR_CANCELLED;
+
+ /* Free all but the first entry. */
+ for (i = 1; i < flTo->dwNumFiles; i++)
+ {
+ HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szDirectory);
+ HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFilename);
+ HeapFree(GetProcessHeap(), 0, flTo->feFiles[i].szFullPath);
+ }
+
+ flTo->dwNumFiles = 1;
+ }
+ else if (IsAttribDir(fileDest->attributes))
+ {
+ for (i = 1; i < flTo->dwNumFiles; i++)
+ if (!IsAttribDir(flTo->feFiles[i].attributes) ||
+ !IsAttribDir(flFrom->feFiles[i].attributes))
+ {
+ return ERROR_CANCELLED;
+ }
+ }
+ }
+ else if (flFrom->dwNumFiles != 1)
+ {
+ if (flTo->dwNumFiles != 1 && !IsAttribDir(fileDest->attributes))
+ return ERROR_CANCELLED;
+
+ if (PathFileExistsW(fileDest->szFullPath) &&
+ IsAttribFile(fileDest->attributes))
+ {
+ return ERROR_CANCELLED;
+ }
+
+ if (flTo->dwNumFiles == 1 && fileDest->bFromRelative &&
+ !PathFileExistsW(fileDest->szFullPath))
+ {
+ return ERROR_CANCELLED;
+ }
+ }
+
+ for (i = 0; i < flFrom->dwNumFiles; i++)
+ {
+ entryToCopy = &flFrom->feFiles[i];
+
+ if ((op->req->fFlags & FOF_MULTIDESTFILES) &&
+ flTo->dwNumFiles > 1)
+ {
+ fileDest = &flTo->feFiles[i];
+ }
+
+ if (IsAttribDir(entryToCopy->attributes) &&
+ !lstrcmpiW(entryToCopy->szFullPath, fileDest->szDirectory))
+ {
+ return ERROR_SUCCESS;
+ }
+
+ create_dest_dirs(fileDest->szDirectory);
+
+ if (!lstrcmpiW(entryToCopy->szFullPath, fileDest->szFullPath))
+ {
+ if (IsAttribFile(entryToCopy->attributes))
+ return ERROR_NO_MORE_SEARCH_HANDLES;
+ else
+ return ERROR_SUCCESS;
+ }
+
+ if ((flFrom->dwNumFiles > 1 && flTo->dwNumFiles == 1) ||
+ IsAttribDir(fileDest->attributes))
+ {
+ copy_to_dir(op, entryToCopy, fileDest);
+ }
+ else if (IsAttribDir(entryToCopy->attributes))
+ {
+ copy_dir_to_dir(op, entryToCopy, fileDest->szFullPath);
+ }
+ else
+ {
+ if (!copy_file_to_file(op, entryToCopy->szFullPath, fileDest->szFullPath))
+ {
+ op->req->fAnyOperationsAborted = TRUE;
+ return ERROR_CANCELLED;
+ }
+ }
+
+ /* Vista return code. XP would return e.g. ERROR_FILE_NOT_FOUND, ERROR_ALREADY_EXISTS */
+ if (op->bCancelled)
+ return ERROR_CANCELLED;
+ }
+
+ /* Vista return code. On XP if the used pressed "No" for the last item,
+ * ERROR_ARENA_TRASHED would be returned */
+ return ERROR_SUCCESS;
+}
+
+static BOOL confirm_delete_list(HWND hWnd, DWORD fFlags, BOOL fTrash, const FILE_LIST *flFrom)
+{
+ if (flFrom->dwNumFiles > 1)
+ {
+ WCHAR tmp[8];
+ const WCHAR format[] = {'%','d',0};
+
+ wnsprintfW(tmp, sizeof(tmp)/sizeof(tmp[0]), format, flFrom->dwNumFiles);
+ return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_MULTIPLE_ITEM:ASK_DELETE_MULTIPLE_ITEM), tmp, NULL);
+ }
+ else
+ {
+ const FILE_ENTRY *fileEntry = &flFrom->feFiles[0];
+
+ if (IsAttribFile(fileEntry->attributes))
+ return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FILE:ASK_DELETE_FILE), fileEntry->szFullPath, NULL);
+ else if (!(fFlags & FOF_FILESONLY && fileEntry->bFromWildcard))
+ return SHELL_ConfirmDialogW(hWnd, (fTrash?ASK_TRASH_FOLDER:ASK_DELETE_FOLDER), fileEntry->szFullPath, NULL);
+ }
+ return TRUE;
+}
+
+/* the FO_DELETE operation */
+static HRESULT delete_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom)
+{
+ const FILE_ENTRY *fileEntry;
+ DWORD i;
+ BOOL bPathExists;
+ BOOL bTrash;
+
+ if (!flFrom->dwNumFiles)
+ return ERROR_SUCCESS;
+
+ /* Windows also checks only the first item */
+ bTrash = (lpFileOp->fFlags & FOF_ALLOWUNDO)
+ && TRASH_CanTrashFile(flFrom->feFiles[0].szFullPath);
+
+ if (!(lpFileOp->fFlags & FOF_NOCONFIRMATION) || (!bTrash && lpFileOp->fFlags & FOF_WANTNUKEWARNING))
+ if (!confirm_delete_list(lpFileOp->hwnd, lpFileOp->fFlags, bTrash, flFrom))
+ {
+ lpFileOp->fAnyOperationsAborted = TRUE;
+ return 0;
+ }
+
+ for (i = 0; i < flFrom->dwNumFiles; i++)
+ {
+ bPathExists = TRUE;
+ fileEntry = &flFrom->feFiles[i];
+
+ if (!IsAttribFile(fileEntry->attributes) &&
+ (lpFileOp->fFlags & FOF_FILESONLY && fileEntry->bFromWildcard))
+ continue;
+
+ if (bTrash)
+ {
+ BOOL bDelete;
+ if (TRASH_TrashFile(fileEntry->szFullPath))
+ {
+ SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, fileEntry->szFullPath, NULL);
+ continue;
+ }
+
+ /* Note: Windows silently deletes the file in such a situation, we show a dialog */
+ if (!(lpFileOp->fFlags & FOF_NOCONFIRMATION) || (lpFileOp->fFlags & FOF_WANTNUKEWARNING))
+ bDelete = SHELL_ConfirmDialogW(lpFileOp->hwnd, ASK_CANT_TRASH_ITEM, fileEntry->szFullPath, NULL);
+ else
+ bDelete = TRUE;
+
+ if (!bDelete)
+ {
+ lpFileOp->fAnyOperationsAborted = TRUE;
+ break;
+ }
+ }
+
+ /* delete the file or directory */
+ if (IsAttribFile(fileEntry->attributes))
+ {
+ bPathExists = DeleteFileW(fileEntry->szFullPath);
+ SHChangeNotify(SHCNE_DELETE, SHCNF_PATHW, fileEntry->szFullPath, NULL);
+ }
+ else
+ bPathExists = SHELL_DeleteDirectoryW(lpFileOp->hwnd, fileEntry->szFullPath, FALSE);
+
+ if (!bPathExists)
+ {
+ DWORD err = GetLastError();
+
+ if (ERROR_FILE_NOT_FOUND == err)
+ {
+ // This is a windows 2003 server specific value which ahs been removed.
+ // Later versions of windows return ERROR_FILE_NOT_FOUND.
+ return 1026;
+ }
+ else
+ {
+ return err;
+ }
+ }
+ }
+
+ return ERROR_SUCCESS;
+}
+
+static void move_dir_to_dir(LPSHFILEOPSTRUCTW lpFileOp, const FILE_ENTRY *feFrom, LPCWSTR szDestPath)
+{
+ WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
+ SHFILEOPSTRUCTW fileOp;
+
+ static const WCHAR wildCardFiles[] = {'*','.','*',0};
+
+ if (IsDotDir(feFrom->szFilename))
+ return;
+
+ SHNotifyCreateDirectoryW(szDestPath, NULL);
+
+ PathCombineW(szFrom, feFrom->szFullPath, wildCardFiles);
+ szFrom[lstrlenW(szFrom) + 1] = '\0';
+
+ lstrcpyW(szTo, szDestPath);
+ szTo[lstrlenW(szDestPath) + 1] = '\0';
+
+ fileOp = *lpFileOp;
+ fileOp.pFrom = szFrom;
+ fileOp.pTo = szTo;
+
+ SHFileOperationW(&fileOp);
+}
+
+/* moves a file or directory to another directory */
+static void move_to_dir(LPSHFILEOPSTRUCTW lpFileOp, const FILE_ENTRY *feFrom, const FILE_ENTRY *feTo)
+{
+ WCHAR szDestPath[MAX_PATH];
+
+ PathCombineW(szDestPath, feTo->szFullPath, feFrom->szFilename);
+
+ if (IsAttribFile(feFrom->attributes))
+ SHNotifyMoveFileW(feFrom->szFullPath, szDestPath, FALSE);
+ else if (!(lpFileOp->fFlags & FOF_FILESONLY && feFrom->bFromWildcard))
+ move_dir_to_dir(lpFileOp, feFrom, szDestPath);
+}
+
+/* the FO_MOVE operation */
+static HRESULT move_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom, const FILE_LIST *flTo)
+{
+ DWORD i;
+ const FILE_ENTRY *entryToMove;
+ const FILE_ENTRY *fileDest;
+
+ if (!flFrom->dwNumFiles || !flTo->dwNumFiles)
+ return ERROR_CANCELLED;
+
+ if (!(lpFileOp->fFlags & FOF_MULTIDESTFILES) &&
+ flTo->dwNumFiles > 1 && flFrom->dwNumFiles > 1)
+ {
+ return ERROR_CANCELLED;
+ }
+
+ if (!(lpFileOp->fFlags & FOF_MULTIDESTFILES) &&
+ !flFrom->bAnyDirectories &&
+ flFrom->dwNumFiles > flTo->dwNumFiles)
+ {
+ return ERROR_CANCELLED;
+ }
+
+ if (!PathFileExistsW(flTo->feFiles[0].szDirectory))
+ return ERROR_CANCELLED;
+
+ if ((lpFileOp->fFlags & FOF_MULTIDESTFILES) &&
+ flFrom->dwNumFiles != flTo->dwNumFiles)
+ {
+ return ERROR_CANCELLED;
+ }
+
+ fileDest = &flTo->feFiles[0];
+ for (i = 0; i < flFrom->dwNumFiles; i++)
+ {
+ entryToMove = &flFrom->feFiles[i];
+
+ if (lpFileOp->fFlags & FOF_MULTIDESTFILES)
+ fileDest = &flTo->feFiles[i];
+
+ if (!PathFileExistsW(fileDest->szDirectory))
+ return ERROR_CANCELLED;
+
+ if (fileDest->bExists && IsAttribDir(fileDest->attributes))
+ move_to_dir(lpFileOp, entryToMove, fileDest);
+ else
+ SHNotifyMoveFileW(entryToMove->szFullPath, fileDest->szFullPath, IsAttribDir(entryToMove->attributes));
+ }
+
+ return ERROR_SUCCESS;
+}
+
+/* the FO_RENAME files */
+static HRESULT rename_files(LPSHFILEOPSTRUCTW lpFileOp, const FILE_LIST *flFrom, const FILE_LIST *flTo)
+{
+ const FILE_ENTRY *feFrom;
+ const FILE_ENTRY *feTo;
+
+ if (flFrom->dwNumFiles != 1)
+ return ERROR_GEN_FAILURE;
+
+ if (flTo->dwNumFiles != 1)
+ return ERROR_CANCELLED;
+
+ feFrom = &flFrom->feFiles[0];
+ feTo= &flTo->feFiles[0];
+
+ /* fail if destination doesn't exist */
+ if (!feFrom->bExists)
+ return ERROR_SHELL_INTERNAL_FILE_NOT_FOUND;
+
+ /* fail if destination already exists */
+ if (feTo->bExists)
+ return ERROR_ALREADY_EXISTS;
+
+ return SHNotifyMoveFileW(feFrom->szFullPath, feTo->szFullPath, IsAttribDir(feFrom->attributes));
+}
+
+/* alert the user if an unsupported flag is used */
+static void check_flags(FILEOP_FLAGS fFlags)
+{
+ WORD wUnsupportedFlags = FOF_NO_CONNECTED_ELEMENTS |
+ FOF_NOCOPYSECURITYATTRIBS | FOF_NORECURSEREPARSE |
+ FOF_RENAMEONCOLLISION | FOF_WANTMAPPINGHANDLE;
+
+ if (fFlags & wUnsupportedFlags)
+ FIXME("Unsupported flags: %04x\n", fFlags);
+}
+
+/*************************************************************************
+ * SHFileOperationW [SHELL32.@]
+ *
+ * See SHFileOperationA
+ */
+int WINAPI SHFileOperationW(LPSHFILEOPSTRUCTW lpFileOp)
+{
+ FILE_OPERATION op;
+ FILE_LIST flFrom, flTo;
+ int ret = 0;
+
+ if (!lpFileOp)
+ return ERROR_INVALID_PARAMETER;
+
+ check_flags(lpFileOp->fFlags);
+
+ ZeroMemory(&flFrom, sizeof(FILE_LIST));
+ ZeroMemory(&flTo, sizeof(FILE_LIST));
+
+ if ((ret = parse_file_list(&flFrom, lpFileOp->pFrom)))
+ return ret;
+
+ if (lpFileOp->wFunc != FO_DELETE)
+ parse_file_list(&flTo, lpFileOp->pTo);
+
+ ZeroMemory(&op, sizeof(op));
+ op.req = lpFileOp;
+ op.bManyItems = (flFrom.dwNumFiles > 1);
+
+ switch (lpFileOp->wFunc)
+ {
+ case FO_COPY:
+ ret = copy_files(&op, &flFrom, &flTo);
+ break;
+ case FO_DELETE:
+ ret = delete_files(lpFileOp, &flFrom);
+ break;
+ case FO_MOVE:
+ ret = move_files(lpFileOp, &flFrom, &flTo);
+ break;
+ case FO_RENAME:
+ ret = rename_files(lpFileOp, &flFrom, &flTo);
+ break;
+ default:
+ ret = ERROR_INVALID_PARAMETER;
+ break;
+ }
+
+ destroy_file_list(&flFrom);
+
+ if (lpFileOp->wFunc != FO_DELETE)
+ destroy_file_list(&flTo);
+
+ if (ret == ERROR_CANCELLED)
+ lpFileOp->fAnyOperationsAborted = TRUE;
+
+ return ret;
+}
+
+// Used by SHFreeNameMappings
+static int CALLBACK _DestroyCallback(void *p, void *pData)
+{
+ LPSHNAMEMAPPINGW lp = (SHNAMEMAPPINGW *)p;
+
+ SHFree(lp->pszOldPath);
+ SHFree(lp->pszNewPath);
+
+ return TRUE;
+}
+
+/*************************************************************************
+ * SHFreeNameMappings [shell32.246]
+ *
+ * Free the mapping handle returned by SHFileOperation if FOF_WANTSMAPPINGHANDLE
+ * was specified.
+ *
+ * PARAMS
+ * hNameMapping [I] handle to the name mappings used during renaming of files
+ *
+ * RETURNS
+ * Nothing
+ */
+void WINAPI SHFreeNameMappings(HANDLE hNameMapping)
+{
+ if (hNameMapping)
+ {
+ DSA_DestroyCallback((HDSA) hNameMapping, _DestroyCallback, NULL);
+ }
+}
+
+/*************************************************************************
+ * SheGetDirA [SHELL32.@]
+ *
+ * drive = 0: returns the current directory path
+ * drive > 0: returns the current directory path of the specified drive
+ * drive=1 -> A: drive=2 -> B: ...
+ * returns 0 if successful
+*/
+EXTERN_C DWORD WINAPI SheGetDirA(DWORD drive, LPSTR buffer)
+{
+ WCHAR org_path[MAX_PATH];
+ DWORD ret;
+ char drv_path[3];
+
+ /* change current directory to the specified drive */
+ if (drive) {
+ strcpy(drv_path, "A:");
+ drv_path[0] += (char)drive-1;
+
+ GetCurrentDirectoryW(MAX_PATH, org_path);
+
+ SetCurrentDirectoryA(drv_path);
+ }
+
+ /* query current directory path of the specified drive */
+ ret = GetCurrentDirectoryA(MAX_PATH, buffer);
+
+ /* back to the original drive */
+ if (drive)
+ SetCurrentDirectoryW(org_path);
+
+ if (!ret)
+ return GetLastError();
+
+ return 0;
+}
+
+/*************************************************************************
+ * SheGetDirW [SHELL32.@]
+ *
+ * drive = 0: returns the current directory path
+ * drive > 0: returns the current directory path of the specified drive
+ * drive=1 -> A: drive=2 -> B: ...
+ * returns 0 if successful
+ */
+EXTERN_C DWORD WINAPI SheGetDirW(DWORD drive, LPWSTR buffer)
+{
+ WCHAR org_path[MAX_PATH];
+ DWORD ret;
+ char drv_path[3];
+
+ /* change current directory to the specified drive */
+ if (drive)
+ {
+ strcpy(drv_path, "A:");
+ drv_path[0] += (char)drive-1;
+
+ GetCurrentDirectoryW(MAX_PATH, org_path);
+
+ SetCurrentDirectoryA(drv_path);
+ }
+
+ /* query current directory path of the specified drive */
+ ret = GetCurrentDirectoryW(MAX_PATH, buffer);
+
+ /* back to the original drive */
+ if (drive)
+ SetCurrentDirectoryW(org_path);
+
+ if (!ret)
+ return GetLastError();
+
+ return 0;
+}
+
+/*************************************************************************
+ * SheChangeDirA [SHELL32.@]
+ *
+ * changes the current directory to the specified path
+ * and returns 0 if successful
+ */
+EXTERN_C DWORD WINAPI SheChangeDirA(LPSTR path)
+{
+ if (SetCurrentDirectoryA(path))
+ return 0;
+ else
+ return GetLastError();
+}
+
+/*************************************************************************
+ * SheChangeDirW [SHELL32.@]
+ *
+ * changes the current directory to the specified path
+ * and returns 0 if successful
+ */
+EXTERN_C DWORD WINAPI SheChangeDirW(LPWSTR path)
+{
+ if (SetCurrentDirectoryW(path))
+ return 0;
+ else
+ return GetLastError();
+}
+
+/*************************************************************************
+ * IsNetDrive [SHELL32.66]
+ */
+EXTERN_C int WINAPI IsNetDrive(int drive)
+{
+ char root[4];
+ strcpy(root, "A:\\");
+ root[0] += (char)drive;
+ return (GetDriveTypeA(root) == DRIVE_REMOTE);
+}
+
+
+/*************************************************************************
+ * RealDriveType [SHELL32.524]
+ */
+EXTERN_C INT WINAPI RealDriveType(INT drive, BOOL bQueryNet)
+{
+ char root[] = "A:\\";
+ root[0] += (char)drive;
+ return GetDriveTypeA(root);
+}
+
+/***********************************************************************
+ * SHPathPrepareForWriteW (SHELL32.@)
+ */
+EXTERN_C HRESULT WINAPI SHPathPrepareForWriteW(HWND hwnd, IUnknown *modless, LPCWSTR path, DWORD flags)
+{
+ DWORD res;
+ DWORD err;
+ LPCWSTR realpath;
+ int len;
+ WCHAR* last_slash;
+ WCHAR* temppath=NULL;
+
+ TRACE("%p %p %s 0x%80x\n", hwnd, modless, debugstr_w(path), flags);
+
+ if (flags & ~(SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE|SHPPFW_IGNOREFILENAME))
+ FIXME("unimplemented flags 0x%08x\n", flags);
+
+ /* cut off filename if necessary */
+ if (flags & SHPPFW_IGNOREFILENAME)
+ {
+ last_slash = StrRChrW(path, NULL, '\\');
+ if (last_slash == NULL)
+ len = 1;
+ else
+ len = last_slash - path + 1;
+ temppath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR));
+ if (!temppath)
+ return E_OUTOFMEMORY;
+ StrCpyNW(temppath, path, len);
+ realpath = temppath;
+ }
+ else
+ {
+ realpath = path;
+ }
+
+ /* try to create the directory if asked to */
+ if (flags & (SHPPFW_DIRCREATE|SHPPFW_ASKDIRCREATE))
+ {
+ if (flags & SHPPFW_ASKDIRCREATE)
+ FIXME("treating SHPPFW_ASKDIRCREATE as SHPPFW_DIRCREATE\n");
+
+ SHCreateDirectoryExW(0, realpath, NULL);
+ }
+
+ /* check if we can access the directory */
+ res = GetFileAttributesW(realpath);
+
+ HeapFree(GetProcessHeap(), 0, temppath);
+
+ if (res == INVALID_FILE_ATTRIBUTES)
+ {
+ err = GetLastError();
+ if (err == ERROR_FILE_NOT_FOUND)
+ return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
+ return HRESULT_FROM_WIN32(err);
+ }
+ else if (res & FILE_ATTRIBUTE_DIRECTORY)
+ return S_OK;
+ else
+ return HRESULT_FROM_WIN32(ERROR_DIRECTORY);
+}
+
+/***********************************************************************
+ * SHPathPrepareForWriteA (SHELL32.@)
+ */
+EXTERN_C HRESULT WINAPI SHPathPrepareForWriteA(HWND hwnd, IUnknown *modless, LPCSTR path, DWORD flags)
+{
+ WCHAR wpath[MAX_PATH];
+ MultiByteToWideChar( CP_ACP, 0, path, -1, wpath, MAX_PATH);
+ return SHPathPrepareForWriteW(hwnd, modless, wpath, flags);
+}