[SHELL32] Finally fix the icon location path (un)expansion for shell links, and expla...
[reactos.git] / dll / win32 / shell32 / CShellLink.cpp
index ddabd18..fa1adab 100644 (file)
 
 WINE_DEFAULT_DEBUG_CHANNEL(shell);
 
+/*
+ * Allows to define whether or not Windows-compatible behaviour
+ * should be adopted when setting and retrieving icon location paths.
+ * See CShellLink::SetIconLocation(LPCWSTR pszIconPath, INT iIcon)
+ * for more details.
+ */
+#define ICON_LINK_WINDOWS_COMPAT
+
 #define SHLINK_LOCAL  0
 #define SHLINK_REMOTE 1
 
@@ -268,6 +276,8 @@ CShellLink::CShellLink()
     m_bRunAs = FALSE;
     m_bDirty = FALSE;
     m_pDBList = NULL;
+    m_bInInit = FALSE;
+    m_hIcon = NULL;
 
     m_sLinkPath = NULL;
     m_iIdOpen = -1;
@@ -350,10 +360,7 @@ HRESULT STDMETHODCALLTYPE CShellLink::Save(LPCOLESTR pszFileName, BOOL fRemember
             if (m_sLinkPath)
                 HeapFree(GetProcessHeap(), 0, m_sLinkPath);
 
-            m_sLinkPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wcslen(pszFileName) + 1) * sizeof(WCHAR));
-            if (m_sLinkPath)
-                wcscpy(m_sLinkPath, pszFileName);
-
+            m_sLinkPath = strdupW(pszFileName);
             m_bDirty = FALSE;
         }
         else
@@ -520,7 +527,7 @@ static LPWSTR Stream_LoadPath(LPCSTR p, DWORD maxlen)
 {
     UINT len = 0;
 
-    while (p[len] && len < maxlen)
+    while (len < maxlen && p[len])
         len++;
 
     UINT wlen = MultiByteToWideChar(CP_ACP, 0, p, len, NULL, 0);
@@ -1033,11 +1040,9 @@ static HRESULT ShellLink_UpdatePath(LPCWSTR sPathRel, LPCWSTR path, LPCWSTR sWor
         if (!*abs_path)
             wcscpy(abs_path, sPathRel);
 
-        *psPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wcslen(abs_path) + 1) * sizeof(WCHAR));
+        *psPath = strdupW(abs_path);
         if (!*psPath)
             return E_OUTOFMEMORY;
-
-        wcscpy(*psPath, abs_path);
     }
 
     return S_OK;
@@ -1203,7 +1208,6 @@ HRESULT STDMETHODCALLTYPE CShellLink::SetArguments(LPCSTR pszArgs)
         if (!m_sArgs)
             return E_OUTOFMEMORY;
     }
-
     m_bDirty = TRUE;
 
     return S_OK;
@@ -1402,7 +1406,6 @@ HRESULT STDMETHODCALLTYPE CShellLink::Resolve(HWND hwnd, DWORD fFlags)
 
         /* Clear the cached path */
         HeapFree(GetProcessHeap(), 0, m_sPath);
-        m_sPath = NULL;
         m_sPath = shelllink_get_msi_component_path(component);
         if (!m_sPath)
             return E_FAIL;
@@ -1415,12 +1418,10 @@ HRESULT STDMETHODCALLTYPE CShellLink::Resolve(HWND hwnd, DWORD fFlags)
         bSuccess = SHGetPathFromIDListW(m_pPidl, buffer);
         if (bSuccess && *buffer)
         {
-            m_sPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wcslen(buffer) + 1) * sizeof(WCHAR));
+            m_sPath = strdupW(buffer);
             if (!m_sPath)
                 return E_OUTOFMEMORY;
 
-            wcscpy(m_sPath, buffer);
-
             m_bDirty = TRUE;
         }
         else
@@ -1432,11 +1433,10 @@ HRESULT STDMETHODCALLTYPE CShellLink::Resolve(HWND hwnd, DWORD fFlags)
     // FIXME: Strange to do that here...
     if (!m_sIcoPath && m_sPath)
     {
-        m_sIcoPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, (wcslen(m_sPath) + 1) * sizeof(WCHAR));
+        m_sIcoPath = strdupW(m_sPath);
         if (!m_sIcoPath)
             return E_OUTOFMEMORY;
 
-        wcscpy(m_sIcoPath, m_sPath);
         m_Header.nIconIndex = 0;
 
         m_bDirty = TRUE;
@@ -1559,18 +1559,14 @@ HRESULT STDMETHODCALLTYPE CShellLink::SetDescription(LPCWSTR pszName)
     TRACE("(%p)->(desc=%s)\n", this, debugstr_w(pszName));
 
     HeapFree(GetProcessHeap(), 0, m_sDescription);
+    m_sDescription = NULL;
+
     if (pszName)
     {
-        m_sDescription = (LPWSTR)HeapAlloc(GetProcessHeap(), 0,
-                                         (wcslen(pszName) + 1) * sizeof(WCHAR));
+        m_sDescription = strdupW(pszName);
         if (!m_sDescription)
             return E_OUTOFMEMORY;
-
-        wcscpy(m_sDescription, pszName);
     }
-    else
-        m_sDescription = NULL;
-
     m_bDirty = TRUE;
 
     return S_OK;
@@ -1594,17 +1590,14 @@ HRESULT STDMETHODCALLTYPE CShellLink::SetWorkingDirectory(LPCWSTR pszDir)
     TRACE("(%p)->(dir=%s)\n", this, debugstr_w(pszDir));
 
     HeapFree(GetProcessHeap(), 0, m_sWorkDir);
+    m_sWorkDir = NULL;
+
     if (pszDir)
     {
-        m_sWorkDir = (LPWSTR)HeapAlloc(GetProcessHeap(), 0,
-                                     (wcslen(pszDir) + 1) * sizeof(WCHAR));
+        m_sWorkDir = strdupW(pszDir);
         if (!m_sWorkDir)
             return E_OUTOFMEMORY;
-        wcscpy(m_sWorkDir, pszDir);
     }
-    else
-        m_sWorkDir = NULL;
-
     m_bDirty = TRUE;
 
     return S_OK;
@@ -1628,18 +1621,14 @@ HRESULT STDMETHODCALLTYPE CShellLink::SetArguments(LPCWSTR pszArgs)
     TRACE("(%p)->(args=%s)\n", this, debugstr_w(pszArgs));
 
     HeapFree(GetProcessHeap(), 0, m_sArgs);
+    m_sArgs = NULL;
+
     if (pszArgs)
     {
-        m_sArgs = (LPWSTR)HeapAlloc(GetProcessHeap(), 0,
-                                  (wcslen(pszArgs) + 1) * sizeof(WCHAR));
+        m_sArgs = strdupW(pszArgs);
         if (!m_sArgs)
             return E_OUTOFMEMORY;
-
-        wcscpy(m_sArgs, pszArgs);
     }
-    else
-        m_sArgs = NULL;
-
     m_bDirty = TRUE;
 
     return S_OK;
@@ -1664,18 +1653,15 @@ HRESULT STDMETHODCALLTYPE CShellLink::GetIconLocation(LPWSTR pszIconPath, INT cc
         pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_ICON_SIG);
         if (pInfo && (pInfo->cbSize == sizeof(*pInfo)))
         {
+            SHExpandEnvironmentStringsW(pInfo->szwTarget, szPath, _countof(szPath));
+
             m_Header.dwFlags &= ~SLDF_HAS_ICONLOCATION;
             HeapFree(GetProcessHeap(), 0, m_sIcoPath);
-            m_sIcoPath = NULL;
 
-            SHExpandEnvironmentStringsW(pInfo->szwTarget, szPath, _countof(szPath));
-
-            m_sIcoPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0,
-                                           (wcslen(szPath) + 1) * sizeof(WCHAR));
+            m_sIcoPath = strdupW(szPath);
             if (!m_sIcoPath)
                 return E_OUTOFMEMORY;
 
-            wcscpy(m_sIcoPath, szPath);
             m_Header.dwFlags |= SLDF_HAS_ICONLOCATION;
 
             m_bDirty = TRUE;
@@ -1715,6 +1701,9 @@ static HRESULT SHELL_PidlGetIconLocationW(PCIDLIST_ABSOLUTE pidl,
 HRESULT STDMETHODCALLTYPE CShellLink::GetIconLocation(UINT uFlags, PWSTR pszIconFile, UINT cchMax, int *piIndex, UINT *pwFlags)
 {
     HRESULT hr;
+
+    pszIconFile[0] = UNICODE_NULL;
+
     /*
      * It is possible for a shell link to point to another shell link,
      * and in particular there is the possibility to point to itself.
@@ -1733,62 +1722,61 @@ HRESULT STDMETHODCALLTYPE CShellLink::GetIconLocation(UINT uFlags, PWSTR pszIcon
      */
     uFlags |= GIL_FORSHORTCUT;
 
-    if (m_pPidl || m_sPath)
-    {
-        /* first look for an icon using the PIDL (if present) */
-        if (m_pPidl)
-            hr = SHELL_PidlGetIconLocationW(m_pPidl, uFlags, pszIconFile, cchMax, piIndex, pwFlags);
-        else
-            hr = E_FAIL;
-
-#if 0 // FIXME: Analyse further whether this is needed...
-        /* if we couldn't find an icon yet, look for it using the file system path */
-        if (FAILED(hr) && m_sPath)
-        {
-            LPITEMIDLIST pidl;
-            CComPtr<IShellFolder> pdsk;
-
-            hr = SHGetDesktopFolder(&pdsk);
+    if (uFlags & GIL_DEFAULTICON)
+        return S_FALSE;
 
-            /* LPITEMIDLIST pidl = ILCreateFromPathW(sPath); */
-            hr = pdsk->ParseDisplayName(0, NULL, m_sPath, NULL, &pidl, NULL);
-            if (SUCCEEDED(hr))
-            {
-                hr = SHELL_PidlGetIconLocationW(pidl, uFlags, pszIconFile, cchMax, piIndex, pwFlags);
-                SHFree(pidl);
-            }
-        }
-#endif
-        return hr;
+    hr = GetIconLocation(pszIconFile, cchMax, piIndex);
+    if (FAILED(hr) || pszIconFile[0] == UNICODE_NULL)
+    {
+        hr = SHELL_PidlGetIconLocationW(m_pPidl, uFlags, pszIconFile, cchMax, piIndex, pwFlags);
+    }
+    else
+    {
+        *pwFlags = GIL_NOTFILENAME | GIL_PERCLASS;
     }
 
-    return S_OK;
+    return hr;
 }
 
 HRESULT STDMETHODCALLTYPE
 CShellLink::Extract(PCWSTR pszFile, UINT nIconIndex, HICON *phiconLarge, HICON *phiconSmall, UINT nIconSize)
 {
-    SHFILEINFOW info;
+    HRESULT hr = NOERROR;
+    UINT cxyLarge = LOWORD(nIconSize), cxySmall = HIWORD(nIconSize);
 
     if (phiconLarge)
     {
-        SHGetFileInfoW(pszFile, 0, &info, sizeof(info),
-                       SHGFI_ICON | SHGFI_LARGEICON | SHGFI_LINKOVERLAY);
-        *phiconLarge = info.hIcon;
-        if (!info.hIcon)
-            return E_FAIL;
+        *phiconLarge = NULL;
+        PrivateExtractIconsW(pszFile, nIconIndex, cxyLarge, cxyLarge, phiconLarge, NULL, 1, 0);
+
+        if (*phiconLarge == NULL)
+            hr = S_FALSE;
     }
 
     if (phiconSmall)
     {
-        SHGetFileInfoW(pszFile, 0, &info, sizeof(info),
-                       SHGFI_ICON | SHGFI_SMALLICON | SHGFI_LINKOVERLAY);
-        *phiconSmall = info.hIcon;
-        if (!info.hIcon)
-            return E_FAIL;
+        *phiconSmall = NULL;
+        PrivateExtractIconsW(pszFile, nIconIndex, cxySmall, cxySmall, phiconSmall, NULL, 1, 0);
+
+        if (*phiconSmall == NULL)
+            hr = S_FALSE;
     }
 
-    return S_OK;
+    if (hr == S_FALSE)
+    {
+        if (phiconLarge && *phiconLarge)
+        {
+            DestroyIcon(*phiconLarge);
+            *phiconLarge = NULL;
+        }
+        if (phiconSmall && *phiconSmall)
+        {
+            DestroyIcon(*phiconSmall);
+            *phiconSmall = NULL;
+        }
+    }
+
+    return hr;
 }
 
 #if 0
@@ -1869,14 +1857,12 @@ BOOL PathFullyUnExpandEnvStringsW(
 HRESULT STDMETHODCALLTYPE CShellLink::SetIconLocation(LPCWSTR pszIconPath, INT iIcon)
 {
     HRESULT hr = E_FAIL;
-    WCHAR szUnExpIconPath[MAX_PATH];
+    WCHAR szIconPath[MAX_PATH];
 
     TRACE("(%p)->(path=%s iicon=%u)\n", this, debugstr_w(pszIconPath), iIcon);
 
     if (pszIconPath)
     {
-        /* Try to fully unexpand the icon path */
-
         /*
          * Check whether the user-given file path contains unexpanded
          * environment variables. If so, create a target environment block.
@@ -1887,15 +1873,35 @@ HRESULT STDMETHODCALLTYPE CShellLink::SetIconLocation(LPCWSTR pszIconPath, INT i
          * refer to the same place even if the would-be corresponding
          * environment variable could change).
          */
-        // FIXME: http://stackoverflow.com/questions/2976489/ishelllinkseticonlocation-translates-my-icon-path-into-program-files-which-i
-        // if (PathFullyUnExpandEnvStringsW(pszIconPath, szUnExpIconPath, _countof(szUnExpIconPath)))
-        SHExpandEnvironmentStringsW(pszIconPath, szUnExpIconPath, _countof(szUnExpIconPath));
-        if (wcscmp(pszIconPath, szUnExpIconPath) != 0)
+#ifdef ICON_LINK_WINDOWS_COMPAT
+        /* Try to fully unexpand the icon path */
+        // if (PathFullyUnExpandEnvStringsW(pszIconPath, szIconPath, _countof(szIconPath)))
+        BOOL bSuccess = PathUnExpandEnvStringsW(pszIconPath, szIconPath, _countof(szIconPath));
+        if (bSuccess && wcscmp(pszIconPath, szIconPath) != 0)
+#else
+        /*
+         * In some situations, described in http://stackoverflow.com/questions/2976489/ishelllinkseticonlocation-translates-my-icon-path-into-program-files-which-i
+         * the result of PathUnExpandEnvStringsW() could be wrong, and instead
+         * one would have to store the actual provided icon location path, while
+         * creating an icon environment block ONLY if that path already contains
+         * environment variables. This is what the present case is trying to implement.
+         */
+        SHExpandEnvironmentStringsW(pszIconPath, szIconPath, _countof(szIconPath));
+        if (wcscmp(pszIconPath, szIconPath) != 0)
+#endif
         {
-            /* Unexpansion succeeded, so we need an icon environment block */
+            /*
+             * The user-given file path contains unexpanded environment
+             * variables, so we need an icon environment block.
+             */
             EXP_SZ_LINK buffer;
             LPEXP_SZ_LINK pInfo;
 
+#ifdef ICON_LINK_WINDOWS_COMPAT
+            /* Make pszIconPath point to the unexpanded path */
+            LPCWSTR pszOrgIconPath = pszIconPath;
+            pszIconPath = szIconPath;
+#endif
             pInfo = (LPEXP_SZ_LINK)SHFindDataBlock(m_pDBList, EXP_SZ_ICON_SIG);
             if (pInfo)
             {
@@ -1920,8 +1926,8 @@ HRESULT STDMETHODCALLTYPE CShellLink::SetIconLocation(LPCWSTR pszIconPath, INT i
                 buffer.dwSignature = EXP_SZ_ICON_SIG;
             }
 
-            lstrcpynW(pInfo->szwTarget, szUnExpIconPath, _countof(pInfo->szwTarget));
-            WideCharToMultiByte(CP_ACP, 0, szUnExpIconPath, -1,
+            lstrcpynW(pInfo->szwTarget, pszIconPath, _countof(pInfo->szwTarget));
+            WideCharToMultiByte(CP_ACP, 0, pszIconPath, -1,
                                 pInfo->szTarget, _countof(pInfo->szTarget), NULL, NULL);
 
             hr = S_OK;
@@ -1929,28 +1935,40 @@ HRESULT STDMETHODCALLTYPE CShellLink::SetIconLocation(LPCWSTR pszIconPath, INT i
                 hr = AddDataBlock(pInfo);
             if (hr == S_OK)
                 m_Header.dwFlags |= SLDF_HAS_EXP_ICON_SZ;
+
+#ifdef ICON_LINK_WINDOWS_COMPAT
+            /* Set pszIconPath back to the original one */
+            pszIconPath = pszOrgIconPath;
+#else
+            /* Now, make pszIconPath point to the expanded path */
+            pszIconPath = szIconPath;
+#endif
         }
         else
         {
-            /* Unexpansion failed, so we need to remove any icon environment block */
+            /*
+             * The user-given file path does not contain unexpanded environment
+             * variables, so we need to remove any icon environment block.
+             */
             m_Header.dwFlags &= ~SLDF_HAS_EXP_ICON_SZ;
             RemoveDataBlock(EXP_SZ_ICON_SIG);
+
+            /* pszIconPath points to the user path */
         }
     }
 
-    /* Store the original icon path location (this one may contain unexpanded environment strings) */
+#ifdef ICON_LINK_WINDOWS_COMPAT
+    /* Store the original icon path location (may contain unexpanded environment strings) */
+#endif
     if (pszIconPath)
     {
         m_Header.dwFlags &= ~SLDF_HAS_ICONLOCATION;
         HeapFree(GetProcessHeap(), 0, m_sIcoPath);
-        m_sIcoPath = NULL;
 
-        m_sIcoPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0,
-                                     (wcslen(pszIconPath) + 1) * sizeof(WCHAR));
+        m_sIcoPath = strdupW(pszIconPath);
         if (!m_sIcoPath)
             return E_OUTOFMEMORY;
 
-        wcscpy(m_sIcoPath, pszIconPath);
         m_Header.dwFlags |= SLDF_HAS_ICONLOCATION;
     }
 
@@ -1967,17 +1985,14 @@ HRESULT STDMETHODCALLTYPE CShellLink::SetRelativePath(LPCWSTR pszPathRel, DWORD
     TRACE("(%p)->(path=%s %x)\n", this, debugstr_w(pszPathRel), dwReserved);
 
     HeapFree(GetProcessHeap(), 0, m_sPathRel);
+    m_sPathRel = NULL;
+
     if (pszPathRel)
     {
-        m_sPathRel = (LPWSTR)HeapAlloc(GetProcessHeap(), 0,
-                                      (wcslen(pszPathRel) + 1) * sizeof(WCHAR));
+        m_sPathRel = strdupW(pszPathRel);
         if (!m_sPathRel)
             return E_OUTOFMEMORY;
-        wcscpy(m_sPathRel, pszPathRel);
     }
-    else
-        m_sPathRel = NULL;
-
     m_bDirty = TRUE;
 
     return ShellLink_UpdatePath(m_sPathRel, m_sPath, m_sWorkDir, &m_sPath);
@@ -2315,11 +2330,13 @@ HRESULT CShellLink::SetTargetFromPIDLOrPath(LPCITEMIDLIST pidl, LPCWSTR pszFile)
 
     /* Update the cached path (for link info) */
     ShellLink_GetVolumeInfo(pszFile, &volume);
-    m_sPath = (LPWSTR)HeapAlloc(GetProcessHeap(), 0,
-                              (wcslen(pszFile) + 1) * sizeof(WCHAR));
+
+    if (m_sPath)
+        HeapFree(GetProcessHeap(), 0, m_sPath);
+
+    m_sPath = strdupW(pszFile);
     if (!m_sPath)
         return E_OUTOFMEMORY;
-    wcscpy(m_sPath, pszFile);
 
     m_bDirty = TRUE;
     return hr;
@@ -2426,7 +2443,7 @@ HRESULT STDMETHODCALLTYPE CShellLink::SetPath(LPCWSTR pszFile)
             if (hr == S_OK)
                 m_Header.dwFlags |= SLDF_HAS_EXP_SZ;
 
-            /* Now, make pszFile point to the expanded buffer */
+            /* Now, make pszFile point to the expanded path */
             pszFile = szPath;
         }
         else
@@ -2756,203 +2773,246 @@ LPWSTR SH_GetTargetTypeByPath(LPCWSTR lpcwFullPath)
     return wszBuf;
 }
 
-/**************************************************************************
- * SH_ShellLinkDlgProc
- *
- * dialog proc of the shortcut property dialog
- */
-
-INT_PTR CALLBACK CShellLink::SH_ShellLinkDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+BOOL CShellLink::OnInitDialog(HWND hwndDlg, HWND hwndFocus, LPARAM lParam)
 {
-    CShellLink *pThis = reinterpret_cast<CShellLink *>(GetWindowLongPtr(hwndDlg, DWLP_USER));
+    TRACE("CShellLink::OnInitDialog(hwnd %p hwndFocus %p lParam %p)\n", hwndDlg, hwndFocus, lParam);
 
-    switch(uMsg)
+    TRACE("m_sArgs: %S sComponent: %S m_sDescription: %S m_sIcoPath: %S m_sPath: %S m_sPathRel: %S sProduct: %S m_sWorkDir: %S\n", m_sArgs, sComponent, m_sDescription,
+          m_sIcoPath, m_sPath, m_sPathRel, sProduct, m_sWorkDir);
+
+    m_bInInit = TRUE;
+
+    /* Get file information */
+    // FIXME! FIXME! Shouldn't we use m_sIcoPath, m_Header.nIconIndex instead???
+    SHFILEINFOW fi;
+    if (!SHGetFileInfoW(m_sLinkPath, 0, &fi, sizeof(fi), SHGFI_TYPENAME | SHGFI_ICON))
     {
-        case WM_INITDIALOG:
+        ERR("SHGetFileInfoW failed for %ls (%lu)\n", m_sLinkPath, GetLastError());
+        fi.szTypeName[0] = L'\0';
+        fi.hIcon = NULL;
+    }
+
+    if (fi.hIcon)
+    {
+        if (m_hIcon)
+            DestroyIcon(m_hIcon);
+        m_hIcon = fi.hIcon;
+        SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_ICON, STM_SETICON, (WPARAM)m_hIcon, 0);
+    }
+    else
+        ERR("ExtractIconW failed %ls %u\n", m_sIcoPath, m_Header.nIconIndex);
+
+    /* Target type */
+    if (m_sPath)
+        SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TYPE_EDIT, SH_GetTargetTypeByPath(m_sPath));
+
+    /* Target location */
+    if (m_sPath)
+    {
+        WCHAR target[MAX_PATH];
+        StringCchCopyW(target, _countof(target), m_sPath);
+        PathRemoveFileSpecW(target);
+        SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_LOCATION_EDIT, PathFindFileNameW(target));
+    }
+
+    /* Target path */
+    if (m_sPath)
+    {
+        WCHAR newpath[2*MAX_PATH] = L"\0";
+        if (wcschr(m_sPath, ' '))
+            StringCchPrintfExW(newpath, _countof(newpath), NULL, NULL, 0, L"\"%ls\"", m_sPath);
+        else
+            StringCchCopyExW(newpath, _countof(newpath), m_sPath, NULL, NULL, 0);
+
+        if (m_sArgs && m_sArgs[0])
         {
-            LPPROPSHEETPAGEW ppsp = (LPPROPSHEETPAGEW)lParam;
-            if (ppsp == NULL)
-                break;
+            StringCchCatW(newpath, _countof(newpath), L" ");
+            StringCchCatW(newpath, _countof(newpath), m_sArgs);
+        }
+        SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TARGET_TEXT, newpath);
+    }
 
-            TRACE("ShellLink_DlgProc (WM_INITDIALOG hwnd %p lParam %p ppsplParam %x)\n", hwndDlg, lParam, ppsp->lParam);
+    /* Working dir */
+    if (m_sWorkDir)
+        SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_START_IN_EDIT, m_sWorkDir);
 
-            pThis = reinterpret_cast<CShellLink *>(ppsp->lParam);
-            SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pThis);
+    /* Description */
+    if (m_sDescription)
+        SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_COMMENT_EDIT, m_sDescription);
 
-            TRACE("m_sArgs: %S sComponent: %S m_sDescription: %S m_sIcoPath: %S m_sPath: %S m_sPathRel: %S sProduct: %S m_sWorkDir: %S\n", pThis->m_sArgs, pThis->sComponent, pThis->m_sDescription,
-                  pThis->m_sIcoPath, pThis->m_sPath, pThis->m_sPathRel, pThis->sProduct, pThis->m_sWorkDir);
+    m_bInInit = FALSE;
 
-            /* Get file information */
-            // FIXME! FIXME! Shouldn't we use pThis->m_sIcoPath, pThis->m_Header.nIconIndex instead???
-            SHFILEINFOW fi;
-            if (!SHGetFileInfoW(pThis->m_sLinkPath, 0, &fi, sizeof(fi), SHGFI_TYPENAME | SHGFI_ICON))
-            {
-                ERR("SHGetFileInfoW failed for %ls (%lu)\n", pThis->m_sLinkPath, GetLastError());
-                fi.szTypeName[0] = L'\0';
-                fi.hIcon = NULL;
-            }
+    return TRUE;
+}
 
-            if (fi.hIcon) // TODO: destroy icon
-                SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_ICON, STM_SETICON, (WPARAM)fi.hIcon, 0);
-            else
-                ERR("ExtractIconW failed %ls %u\n", pThis->m_sIcoPath, pThis->m_Header.nIconIndex);
+void CShellLink::OnCommand(HWND hwndDlg, int id, HWND hwndCtl, UINT codeNotify)
+{
+    switch (id)
+    {
+        case IDC_SHORTCUT_FIND:
+            SHOpenFolderAndSelectItems(m_pPidl, 0, NULL, 0);
+            ///
+            /// FIXME
+            /// open target directory
+            ///
+            return;
 
-            /* Target type */
-            if (pThis->m_sPath)
-                SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TYPE_EDIT, SH_GetTargetTypeByPath(pThis->m_sPath));
+        case IDC_SHORTCUT_CHANGE_ICON:
+        {
+            WCHAR wszPath[MAX_PATH] = L"";
+
+            if (m_sIcoPath)
+                wcscpy(wszPath, m_sIcoPath);
+            else
+                FindExecutableW(m_sPath, NULL, wszPath);
 
-            /* Target location */
-            if (pThis->m_sPath)
+            INT IconIndex = m_Header.nIconIndex;
+            if (PickIconDlg(hwndDlg, wszPath, _countof(wszPath), &IconIndex))
             {
-                WCHAR target[MAX_PATH];
-                StringCchCopyW(target, _countof(target), pThis->m_sPath);
-                PathRemoveFileSpecW(target);
-                SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_LOCATION_EDIT, PathFindFileNameW(target));
+                SetIconLocation(wszPath, IconIndex);
+                PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
+
+                HICON hIconLarge = CreateShortcutIcon(wszPath, IconIndex);
+                if (hIconLarge)
+                {
+                    if (m_hIcon)
+                        DestroyIcon(m_hIcon);
+                    m_hIcon = hIconLarge;
+                    SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_ICON, STM_SETICON, (WPARAM)m_hIcon, 0);
+                }
             }
+            return;
+        }
 
-            /* Target path */
-            if (pThis->m_sPath)
+        case IDC_SHORTCUT_ADVANCED:
+        {
+            INT_PTR result = DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_SHORTCUT_EXTENDED_PROPERTIES), hwndDlg, ExtendedShortcutProc, (LPARAM)m_bRunAs);
+            if (result == 1 || result == 0)
             {
-                WCHAR newpath[2*MAX_PATH] = L"\0";
-                if (wcschr(pThis->m_sPath, ' '))
-                    StringCchPrintfExW(newpath, _countof(newpath), NULL, NULL, 0, L"\"%ls\"", pThis->m_sPath);
-                else
-                    StringCchCopyExW(newpath, _countof(newpath), pThis->m_sPath, NULL, NULL, 0);
-
-                if (pThis->m_sArgs && pThis->m_sArgs[0])
+                if (m_bRunAs != result)
                 {
-                    StringCchCatW(newpath, _countof(newpath), L" ");
-                    StringCchCatW(newpath, _countof(newpath), pThis->m_sArgs);
+                    PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
                 }
-                SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TARGET_TEXT, newpath);
+
+                m_bRunAs = result;
             }
+            return;
+        }
+    }
+    if (codeNotify == EN_CHANGE)
+    {
+        if (!m_bInInit)
+            PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
+    }
+}
 
-            /* Working dir */
-            if (pThis->m_sWorkDir)
-                SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_START_IN_EDIT, pThis->m_sWorkDir);
+LRESULT CShellLink::OnNotify(HWND hwndDlg, int idFrom, LPNMHDR pnmhdr)
+{
+    WCHAR wszBuf[MAX_PATH];
+    LPPSHNOTIFY lppsn = (LPPSHNOTIFY)pnmhdr;
+
+    if (lppsn->hdr.code == PSN_APPLY)
+    {
+        /* set working directory */
+        GetDlgItemTextW(hwndDlg, IDC_SHORTCUT_START_IN_EDIT, wszBuf, _countof(wszBuf));
+        SetWorkingDirectory(wszBuf);
 
-            /* Description */
-            if (pThis->m_sDescription)
-                SetDlgItemTextW(hwndDlg, IDC_SHORTCUT_COMMENT_EDIT, pThis->m_sDescription);
+        /* set link destination */
+        GetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TARGET_TEXT, wszBuf, _countof(wszBuf));
+        LPWSTR lpszArgs = NULL;
+        LPWSTR unquoted = strdupW(wszBuf);
+        StrTrimW(unquoted, L" ");
 
+        if (!PathFileExistsW(unquoted))
+        {
+            lpszArgs = PathGetArgsW(unquoted);
+            PathRemoveArgsW(unquoted);
+            StrTrimW(lpszArgs, L" ");
+        }
+        if (unquoted[0] == '"' && unquoted[wcslen(unquoted) - 1] == '"')
+            PathUnquoteSpacesW(unquoted);
+
+        WCHAR *pwszExt = PathFindExtensionW(unquoted);
+        if (!wcsicmp(pwszExt, L".lnk"))
+        {
+            // FIXME load localized error msg
+            MessageBoxW(hwndDlg, L"You cannot create a link to a shortcut", L"Error", MB_ICONERROR);
+            SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
             return TRUE;
         }
 
-        case WM_NOTIFY:
+        if (!PathFileExistsW(unquoted))
         {
-            LPPSHNOTIFY lppsn = (LPPSHNOTIFY)lParam;
-            if (lppsn->hdr.code == PSN_APPLY)
-            {
-                WCHAR wszBuf[MAX_PATH];
-                /* set working directory */
-                GetDlgItemTextW(hwndDlg, IDC_SHORTCUT_START_IN_EDIT, wszBuf, _countof(wszBuf));
-                pThis->SetWorkingDirectory(wszBuf);
-                /* set link destination */
-                GetDlgItemTextW(hwndDlg, IDC_SHORTCUT_TARGET_TEXT, wszBuf, _countof(wszBuf));
-                LPWSTR lpszArgs = NULL;
-                LPWSTR unquoted = strdupW(wszBuf);
-                StrTrimW(unquoted, L" ");
-                if (!PathFileExistsW(unquoted))
-                {
-                    lpszArgs = PathGetArgsW(unquoted);
-                    PathRemoveArgsW(unquoted);
-                    StrTrimW(lpszArgs, L" ");
-                }
-                if (unquoted[0] == '"' && unquoted[wcslen(unquoted)-1] == '"')
-                    PathUnquoteSpacesW(unquoted);
+            // FIXME load localized error msg
+            MessageBoxW(hwndDlg, L"The specified file name in the target box is invalid", L"Error", MB_ICONERROR);
+            SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
+            return TRUE;
+        }
 
+        SetPath(unquoted);
+        if (lpszArgs)
+            SetArguments(lpszArgs);
+        else
+            SetArguments(L"\0");
 
-                WCHAR *pwszExt = PathFindExtensionW(unquoted);
-                if (!wcsicmp(pwszExt, L".lnk"))
-                {
-                    // FIXME load localized error msg
-                    MessageBoxW(hwndDlg, L"You cannot create a link to a shortcut", L"Error", MB_ICONERROR);
-                    SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
-                    return TRUE;
-                }
+        HeapFree(GetProcessHeap(), 0, unquoted);
 
-                if (!PathFileExistsW(unquoted))
-                {
-                    // FIXME load localized error msg
-                    MessageBoxW(hwndDlg, L"The specified file name in the target box is invalid", L"Error", MB_ICONERROR);
-                    SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
-                    return TRUE;
-                }
+        TRACE("This %p m_sLinkPath %S\n", this, m_sLinkPath);
+        Save(m_sLinkPath, TRUE);
+        SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, m_sLinkPath, NULL);
+        SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
+        return TRUE;
+    }
+    return FALSE;
+}
 
-                pThis->SetPath(unquoted);
-                if (lpszArgs)
-                    pThis->SetArguments(lpszArgs);
-                else
-                    pThis->SetArguments(L"\0");
+void CShellLink::OnDestroy(HWND hwndDlg)
+{
+    if (m_hIcon)
+    {
+        DestroyIcon(m_hIcon);
+        m_hIcon = NULL;
+    }
+}
 
-                HeapFree(GetProcessHeap(), 0, unquoted);
+/**************************************************************************
+ * SH_ShellLinkDlgProc
+ *
+ * dialog proc of the shortcut property dialog
+ */
 
-                TRACE("This %p m_sLinkPath %S\n", pThis, pThis->m_sLinkPath);
-                pThis->Save(pThis->m_sLinkPath, TRUE);
-                SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
-                return TRUE;
-            }
-            break;
-        }
+INT_PTR CALLBACK
+CShellLink::SH_ShellLinkDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+    LPPROPSHEETPAGEW ppsp;
+    CShellLink *pThis = reinterpret_cast<CShellLink *>(GetWindowLongPtr(hwndDlg, DWLP_USER));
+
+    switch (uMsg)
+    {
+        case WM_INITDIALOG:
+            ppsp = (LPPROPSHEETPAGEW)lParam;
+            if (ppsp == NULL)
+                break;
+
+            pThis = reinterpret_cast<CShellLink *>(ppsp->lParam);
+            SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pThis);
+            return pThis->OnInitDialog(hwndDlg, (HWND)(wParam), lParam);
+
+        case WM_NOTIFY:
+            return pThis->OnNotify(hwndDlg, (int)wParam, (NMHDR *)lParam);
 
         case WM_COMMAND:
-            switch(LOWORD(wParam))
-            {
-                case IDC_SHORTCUT_FIND:
-                    SHOpenFolderAndSelectItems(pThis->m_pPidl, 0, NULL, 0);
-                    ///
-                    /// FIXME
-                    /// open target directory
-                    ///
-                    return TRUE;
-
-                case IDC_SHORTCUT_CHANGE_ICON:
-                {
-                    WCHAR wszPath[MAX_PATH] = L"";
-
-                    if (pThis->m_sIcoPath)
-                        wcscpy(wszPath, pThis->m_sIcoPath);
-                    else
-                        FindExecutableW(pThis->m_sPath, NULL, wszPath);
-
-                    INT IconIndex = pThis->m_Header.nIconIndex;
-                    if (PickIconDlg(hwndDlg, wszPath, _countof(wszPath), &IconIndex))
-                    {
-                        pThis->SetIconLocation(wszPath, IconIndex);
-
-                        HICON hIconLarge = NULL;
-                        if (S_OK == pThis->Extract(wszPath, IconIndex, &hIconLarge, NULL, 0))
-                        {
-                            HICON hIconOld = (HICON)SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_ICON, STM_GETICON, 0, 0);
-                            SendDlgItemMessageW(hwndDlg, IDC_SHORTCUT_ICON, STM_SETICON, (WPARAM)hIconLarge, 0);
-                            DestroyIcon(hIconOld);
-                        }
-                    }
-                    return TRUE;
-                }
+            pThis->OnCommand(hwndDlg, LOWORD(wParam), (HWND)lParam, HIWORD(wParam));
+            break;
 
-                case IDC_SHORTCUT_ADVANCED:
-                {
-                    INT_PTR result = DialogBoxParamW(shell32_hInstance, MAKEINTRESOURCEW(IDD_SHORTCUT_EXTENDED_PROPERTIES), hwndDlg, ExtendedShortcutProc, (LPARAM)pThis->m_bRunAs);
-                    if (result == 1 || result == 0)
-                    {
-                        if (pThis->m_bRunAs != result)
-                        {
-                            PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
-                        }
-
-                        pThis->m_bRunAs = result;
-                    }
-                    return TRUE;
-                }
-            }
-            if (HIWORD(wParam) == EN_CHANGE)
-                PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
+        case WM_DESTROY:
+            pThis->OnDestroy(hwndDlg);
             break;
 
         default:
             break;
     }
+
     return FALSE;
 }
 
@@ -3083,3 +3143,64 @@ HRESULT WINAPI IShellLink_ConstructFromFile(IShellFolder * psf, LPCITEMIDLIST pi
 
     return IShellLink_ConstructFromPath(path, riid, ppv);
 }
+
+HICON CShellLink::CreateShortcutIcon(LPCWSTR wszIconPath, INT IconIndex)
+{
+    const INT cx = GetSystemMetrics(SM_CXICON), cy = GetSystemMetrics(SM_CYICON);
+    const COLORREF crMask = GetSysColor(COLOR_3DFACE);
+    HDC hDC;
+    HIMAGELIST himl = ImageList_Create(cx, cy, ILC_COLOR32 | ILC_MASK, 1, 1);
+    HICON hIcon = NULL, hNewIcon = NULL;
+    HICON hShortcut = (HICON)LoadImageW(shell32_hInstance, MAKEINTRESOURCE(IDI_SHELL_SHORTCUT),
+                                        IMAGE_ICON, cx, cy, 0);
+
+    ::ExtractIconExW(wszIconPath, IconIndex, &hIcon, NULL, 1);
+    if (!hIcon || !hShortcut || !himl)
+        goto cleanup;
+
+    hDC = CreateCompatibleDC(NULL);
+    if (hDC)
+    {
+        // create 32bpp bitmap
+        BITMAPINFO bi;
+        ZeroMemory(&bi, sizeof(bi));
+        bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+        bi.bmiHeader.biWidth = cx;
+        bi.bmiHeader.biHeight = cy;
+        bi.bmiHeader.biPlanes = 1;
+        bi.bmiHeader.biBitCount = 32;
+        LPVOID pvBits;
+        HBITMAP hbm = CreateDIBSection(hDC, &bi, DIB_RGB_COLORS, &pvBits, NULL, 0);
+        if (hbm)
+        {
+            // draw the icon image
+            HGDIOBJ hbmOld = SelectObject(hDC, hbm);
+            {
+                HBRUSH hbr = CreateSolidBrush(crMask);
+                RECT rc = { 0, 0, cx, cy };
+                FillRect(hDC, &rc, hbr);
+                DeleteObject(hbr);
+
+                DrawIconEx(hDC, 0, 0, hIcon, cx, cy, 0, NULL, DI_NORMAL);
+                DrawIconEx(hDC, 0, 0, hShortcut, cx, cy, 0, NULL, DI_NORMAL);
+            }
+            SelectObject(hDC, hbmOld);
+
+            INT iAdded = ImageList_AddMasked(himl, hbm, crMask);
+            hNewIcon = ImageList_GetIcon(himl, iAdded, ILD_NORMAL | ILD_TRANSPARENT);
+
+            DeleteObject(hbm);
+        }
+        DeleteDC(hDC);
+    }
+
+cleanup:
+    if (hIcon)
+        DestroyIcon(hIcon);
+    if (hShortcut)
+        DestroyIcon(hShortcut);
+    if (himl)
+        ImageList_Destroy(himl);
+
+    return hNewIcon;
+}