Repository: reactos/reactos
Pull request #7776: [SHIMGVW] Display error message on delete failure
Author: login: whindsaks
Labels: name: enhancement
description: For PRs with an enhancement/new feature.
Created: 2025-03-08T23:28:14Z | Merged: 2025-03-27T12:43:59Z

JIRA issue: [CORE-20019](https://jira.reactos.org/browse/CORE-20019)

Notes:
- I enabled the copy menu item by switching to OleInit. Turned off the paste verbs.
- Turned off the delete verb in the menu because it's async and we can't tell if it worked and if we need to move to the next image.
- Now blocks accelerators from invoking disabled commands. 


Diff:
diff --git a/dll/win32/shimgvw/resource.h b/dll/win32/shimgvw/resource.h
index 17fce85b7fc84..8398a23818e5e 100644
--- a/dll/win32/shimgvw/resource.h
+++ b/dll/win32/shimgvw/resource.h
@@ -10,6 +10,9 @@
 /* Cursors */
 #define IDC_HANDDRAG 100
 
+/* Control IDs */
+#define IDC_TOOLBAR     40
+
 /* Toolbar images */
 #define IDB_PREV_PIC    50
 #define IDB_NEXT_PIC    51
diff --git a/dll/win32/shimgvw/shimgvw.c b/dll/win32/shimgvw/shimgvw.c
index 392a0a463bebf..3084da58bc94c 100644
--- a/dll/win32/shimgvw/shimgvw.c
+++ b/dll/win32/shimgvw/shimgvw.c
@@ -31,6 +31,7 @@ HINSTANCE           g_hInstance         = NULL;
 HWND                g_hMainWnd          = NULL;
 HWND                g_hwndFullscreen    = NULL;
 SHIMGVW_FILENODE *  g_pCurrentFile      = NULL;
+WCHAR               g_szFile[MAX_PATH];
 GpImage *           g_pImage            = NULL;
 SHIMGVW_SETTINGS    g_Settings;
 UINT                g_ImageId;
@@ -119,7 +120,6 @@ typedef struct tagPREVIEW_DATA
     UINT m_nTimerInterval;
     BOOL m_bHideCursor;
     POINT m_ptOrigin;
-    WCHAR m_szFile[MAX_PATH];
 } PREVIEW_DATA, *PPREVIEW_DATA;
 
 static VOID Preview_ToggleSlideShowEx(PPREVIEW_DATA pData, BOOL StartTimer);
@@ -374,18 +374,20 @@ Preview_pFreeImage(PPREVIEW_DATA pData)
         GdipDisposeImage(g_pImage);
         g_pImage = NULL;
     }
-
-    pData->m_szFile[0] = UNICODE_NULL;
 }
 
 static VOID
 Preview_pLoadImage(PPREVIEW_DATA pData, LPCWSTR szOpenFileName)
 {
     HRESULT hr;
+    BOOL bCanDel;
     Preview_pFreeImage(pData);
     InvalidateRect(pData->m_hwnd, NULL, FALSE); /* Schedule redraw in case we change to "No preview" */
 
+    GetFullPathNameW(szOpenFileName, _countof(g_szFile), g_szFile, NULL);
     hr = LoadImageFromPath(szOpenFileName, &g_pImage);
+    bCanDel = hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) && hr != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
+    SendDlgItemMessageW(g_hMainWnd, IDC_TOOLBAR, TB_ENABLEBUTTON, IDC_DELETE, bCanDel);
     if (FAILED(hr))
     {
         DPRINT1("GdipLoadImageFromStream() failed, %d\n", hr);
@@ -396,17 +398,14 @@ Preview_pLoadImage(PPREVIEW_DATA pData, LPCWSTR szOpenFileName)
 
     Anime_LoadInfo(&pData->m_Anime);
 
-    GetFullPathNameW(szOpenFileName, _countof(pData->m_szFile), pData->m_szFile, NULL);
-    SHAddToRecentDocs(SHARD_PATHW, pData->m_szFile);
-
     /* Reset zoom and redraw display */
     Preview_ResetZoom(pData);
-
-    Preview_UpdateTitle(pData, szOpenFileName);
+    SHAddToRecentDocs(SHARD_PATHW, g_szFile);
+    Preview_UpdateTitle(pData, g_szFile);
 
     ++g_ImageId;
-    EnableCommandIfVerbExists(g_ImageId, g_hMainWnd, IDC_PRINT, L"print", pData->m_szFile);
-    EnableCommandIfVerbExists(g_ImageId, g_hMainWnd, IDC_MODIFY, L"edit", pData->m_szFile);
+    EnableCommandIfVerbExists(g_ImageId, g_hMainWnd, IDC_PRINT, L"print", g_szFile);
+    EnableCommandIfVerbExists(g_ImageId, g_hMainWnd, IDC_MODIFY, L"edit", g_szFile);
 }
 
 static VOID
@@ -562,7 +561,7 @@ Preview_pSaveImageAs(PPREVIEW_DATA pData)
 static VOID
 Preview_pPrintImage(PPREVIEW_DATA pData)
 {
-    ShellExecuteVerb(g_hMainWnd, L"print", pData->m_szFile, FALSE);
+    ShellExecuteVerb(g_hMainWnd, L"print", g_szFile, FALSE);
 }
 
 static VOID
@@ -584,6 +583,14 @@ Preview_UpdateImage(PPREVIEW_DATA pData)
     ZoomWnd_UpdateScroll(pData, TRUE);
 }
 
+static VOID
+Preview_LoadImage(PPREVIEW_DATA pData, SHIMGVW_FILENODE *pNode)
+{
+    Preview_pLoadImageFromNode(pData, pNode);
+    Preview_UpdateImage(pData);
+    Preview_UpdateUI(pData);
+}
+
 static SHIMGVW_FILENODE*
 pBuildFileList(LPCWSTR szFirstFile)
 {
@@ -948,7 +955,7 @@ Preview_CreateToolBar(PPREVIEW_DATA pData)
 
     style |= CCS_BOTTOM;
     hwndToolBar = CreateWindowExW(0, TOOLBARCLASSNAMEW, NULL, style,
-                                  0, 0, 0, 0, pData->m_hwnd, NULL, g_hInstance, NULL);
+                                  0, 0, 0, 0, pData->m_hwnd, (HMENU)IDC_TOOLBAR, g_hInstance, NULL);
     if (!hwndToolBar)
         return FALSE;
 
@@ -1217,8 +1224,8 @@ ZoomWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
             break;
         }
         case WM_CONTEXTMENU:
-            if (Preview_IsMainWnd(pData->m_hwnd))
-                DoShellContextMenuOnFile(hwnd, pData->m_szFile, lParam);
+            if (Preview_IsMainWnd(pData->m_hwnd) && *g_szFile)
+                DoShellContextMenuOnFile(hwnd, g_szFile, lParam);
             break;
         case WM_HSCROLL:
         case WM_VSCROLL:
@@ -1302,9 +1309,7 @@ Preview_OnCreate(HWND hwnd, LPCREATESTRUCT pCS)
         PathUnquoteSpacesW(szFile);
 
         g_pCurrentFile = pBuildFileList(szFile);
-        Preview_pLoadImageFromNode(pData, g_pCurrentFile);
-        Preview_UpdateImage(pData);
-        Preview_UpdateUI(pData);
+        Preview_LoadImage(pData, g_pCurrentFile);
     }
 
     return TRUE;
@@ -1368,14 +1373,11 @@ static VOID
 Preview_Delete(PPREVIEW_DATA pData)
 {
     WCHAR szCurFile[MAX_PATH + 1], szNextFile[MAX_PATH];
-    HWND hwnd = pData->m_hwnd;
-    SHFILEOPSTRUCTW FileOp = { hwnd, FO_DELETE };
-
-    if (!pData->m_szFile[0])
-        return;
+    SHFILEOPSTRUCTW FileOp = { pData->m_hwnd, FO_DELETE };
+    UINT error;
 
     /* FileOp.pFrom must be double-null-terminated */
-    GetFullPathNameW(pData->m_szFile, _countof(szCurFile) - 1, szCurFile, NULL);
+    GetFullPathNameW(g_szFile, _countof(szCurFile) - 1, szCurFile, NULL);
     szCurFile[_countof(szCurFile) - 2] = UNICODE_NULL; /* Avoid buffer overrun */
     szCurFile[lstrlenW(szCurFile) + 1] = UNICODE_NULL;
 
@@ -1388,24 +1390,27 @@ Preview_Delete(PPREVIEW_DATA pData)
 
     /* Confirm file deletion and delete if allowed */
     FileOp.pFrom = szCurFile;
-    FileOp.fFlags = FOF_ALLOWUNDO;
-    if (SHFileOperationW(&FileOp) != 0)
+    FileOp.fFlags = GetKeyState(VK_SHIFT) < 0 ? 0 : FOF_ALLOWUNDO;
+    error = g_szFile[0] ? SHFileOperationW(&FileOp) : ERROR_FILE_NOT_FOUND;
+    if (error != 0)
     {
-        DPRINT("Preview_Delete: SHFileOperationW() failed or canceled\n");
+        enum { SHFO_FIRSTCUSTOMERROR = 0x71 /* DE_SAMEFILE */ };
+        if (!FileOp.fAnyOperationsAborted && error != ERROR_CANCELLED)
+            ErrorBox(FileOp.hwnd, error < SHFO_FIRSTCUSTOMERROR ? error : E_FAIL);
         return;
     }
 
     /* Reload the file list and go next file */
     pFreeFileList(g_pCurrentFile);
     g_pCurrentFile = pBuildFileList(szNextFile);
-    Preview_pLoadImageFromNode(pData, g_pCurrentFile);
+    Preview_LoadImage(pData, g_pCurrentFile);
 }
 
 static VOID
 Preview_Edit(HWND hwnd)
 {
     PPREVIEW_DATA pData = Preview_GetData(g_hMainWnd);
-    ShellExecuteVerb(pData->m_hwnd, L"edit", pData->m_szFile, TRUE);
+    ShellExecuteVerb(pData->m_hwnd, L"edit", g_szFile, TRUE);
 }
 
 static VOID
@@ -1452,16 +1457,28 @@ Preview_GoNextPic(PPREVIEW_DATA pData, BOOL bNext)
             g_pCurrentFile = g_pCurrentFile->Next;
         else
             g_pCurrentFile = g_pCurrentFile->Prev;
-        Preview_pLoadImageFromNode(pData, g_pCurrentFile);
-        Preview_UpdateImage(pData);
-        Preview_UpdateUI(pData);
+        Preview_LoadImage(pData, g_pCurrentFile);
     }
 }
 
+static HRESULT
+IsCommandEnabled(UINT nCmdId)
+{
+    HWND hToolbar = GetDlgItem(g_hMainWnd, IDC_TOOLBAR);
+    LRESULT Ok = SendMessageW(hToolbar, TB_ISBUTTONENABLED, nCmdId, 0);
+    return Ok ? S_OK : SendMessageW(hToolbar, TB_COMMANDTOINDEX, nCmdId, 0) != -1 ? S_FALSE : E_FAIL;
+}
+
 static VOID
-Preview_OnCommand(HWND hwnd, UINT nCommandID)
+Preview_OnCommand(HWND hwnd, WPARAM wParam, LPARAM lParam)
 {
     PPREVIEW_DATA pData = Preview_GetData(hwnd);
+    UINT nCommandID = LOWORD(wParam);
+    if ((!HIWORD(wParam) || !lParam) && IsCommandEnabled(nCommandID) == S_FALSE)
+    {
+        MessageBeep(MB_ICONWARNING);
+        return;
+    }
 
     switch (nCommandID)
     {
@@ -1544,7 +1561,7 @@ Preview_OnCommand(HWND hwnd, UINT nCommandID)
             if (g_pImage)
             {
                 GdipImageRotateFlip(g_pImage, Rotate270FlipNone);
-                Preview_pSaveImage(pData, pData->m_szFile);
+                Preview_pSaveImage(pData, g_szFile);
                 Preview_UpdateImage(pData);
             }
             break;
@@ -1553,15 +1570,13 @@ Preview_OnCommand(HWND hwnd, UINT nCommandID)
             if (g_pImage)
             {
                 GdipImageRotateFlip(g_pImage, Rotate90FlipNone);
-                Preview_pSaveImage(pData, pData->m_szFile);
+                Preview_pSaveImage(pData, g_szFile);
                 Preview_UpdateImage(pData);
             }
             break;
 
         case IDC_DELETE:
             Preview_Delete(pData);
-            Preview_UpdateImage(pData);
-            Preview_UpdateUI(pData);
             break;
 
         case IDC_MODIFY:
@@ -1629,7 +1644,7 @@ Preview_OnDropFiles(HWND hwnd, HDROP hDrop)
 
     pFreeFileList(g_pCurrentFile);
     g_pCurrentFile = pBuildFileList(szFile);
-    Preview_pLoadImageFromNode(pData, g_pCurrentFile);
+    Preview_LoadImage(pData, g_pCurrentFile);
 
     DragFinish(hDrop);
 }
@@ -1647,7 +1662,7 @@ PreviewWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
         }
         case WM_COMMAND:
         {
-            Preview_OnCommand(hwnd, LOWORD(wParam));
+            Preview_OnCommand(hwnd, wParam, lParam);
             break;
         }
         case WM_NOTIFY:
@@ -1732,14 +1747,15 @@ ImageView_Main(HWND hwnd, LPCWSTR szFileName)
     HACCEL hAccel;
     HRESULT hrCoInit;
     INITCOMMONCONTROLSEX Icc = { .dwSize = sizeof(Icc), .dwICC = ICC_WIN95_CLASSES };
+    g_szFile[0] = UNICODE_NULL;
 
     InitCommonControlsEx(&Icc);
     SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); // Give UI higher priority than background threads
 
     /* Initialize COM */
-    hrCoInit = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
+    hrCoInit = OleInitialize(NULL);
     if (FAILED(hrCoInit))
-        DPRINT1("Warning, CoInitializeEx failed with code=%08X\n", (int)hrCoInit);
+        DPRINT1("Warning, OleInitialize failed with code=%08X\n", (int)hrCoInit);
 
     if (!ImageView_LoadSettings())
         ImageView_ResetSettings();
@@ -1807,7 +1823,7 @@ ImageView_Main(HWND hwnd, LPCWSTR szFileName)
 
     /* Release COM resources */
     if (SUCCEEDED(hrCoInit))
-        CoUninitialize();
+        OleUninitialize();
 
     return 0;
 }
diff --git a/dll/win32/shimgvw/shimgvw.h b/dll/win32/shimgvw/shimgvw.h
index cdb6f975fa2ae..4f3c741a3edb7 100644
--- a/dll/win32/shimgvw/shimgvw.h
+++ b/dll/win32/shimgvw/shimgvw.h
@@ -76,6 +76,7 @@ BOOL Anime_OnTimer(PANIME pAnime, WPARAM wParam);
 void DoShellContextMenuOnFile(HWND hwnd, PCWSTR File, LPARAM lParam);
 void EnableCommandIfVerbExists(UINT ImageId, HWND hwnd, UINT CmdId, PCWSTR Verb, PCWSTR File);
 void ShellExecuteVerb(HWND hwnd, PCWSTR Verb, PCWSTR File, BOOL Quit);
+UINT ErrorBox(HWND hwnd, UINT Error);
 void DisplayHelp(HWND hwnd);
 
 static inline LPVOID QuickAlloc(SIZE_T cbSize, BOOL bZero)
diff --git a/dll/win32/shimgvw/shimgvw.rc b/dll/win32/shimgvw/shimgvw.rc
index bec550cb4c358..3efd98fbdb4d0 100644
--- a/dll/win32/shimgvw/shimgvw.rc
+++ b/dll/win32/shimgvw/shimgvw.rc
@@ -59,6 +59,7 @@ BEGIN
     "K",         IDC_ROT_CLOCKW, VIRTKEY, CONTROL
     "L",         IDC_ROT_COUNCW, VIRTKEY, CONTROL
     VK_DELETE,   IDC_DELETE,     VIRTKEY
+    VK_DELETE,   IDC_DELETE,     VIRTKEY, SHIFT
     "P",         IDC_PRINT,      VIRTKEY, CONTROL
     "S",         IDC_SAVEAS,     VIRTKEY, CONTROL
     "E",         IDC_MODIFY,     VIRTKEY, CONTROL
diff --git a/dll/win32/shimgvw/util.c b/dll/win32/shimgvw/util.c
index 5a3b8d46294ea..491e6660ba3fd 100644
--- a/dll/win32/shimgvw/util.c
+++ b/dll/win32/shimgvw/util.c
@@ -62,7 +62,8 @@ ModifyShellContextMenu(IContextMenu *pCM, HMENU hMenu, UINT CmdIdFirst, PCWSTR A
             UINT remove = FALSE;
             if (IsSelfShellVerb(Assoc, buf))
                 ++remove;
-            else if (!lstrcmpiW(L"cut", buf) || !lstrcmpiW(L"copy", buf) || !lstrcmpiW(L"link", buf))
+            else if (!lstrcmpiW(L"cut", buf) || !lstrcmpiW(L"paste", buf) || !lstrcmpiW(L"pastelink", buf) ||
+                     !lstrcmpiW(L"delete", buf) || !lstrcmpiW(L"link", buf))
                 ++remove;
 
             if (remove && DeleteMenu(hMenu, i, MF_BYPOSITION))
@@ -133,10 +134,11 @@ DoShellContextMenu(HWND hwnd, IContextMenu *pCM, PCWSTR File, LPARAM lParam)
 HRESULT
 GetUIObjectOfPath(HWND hwnd, PCWSTR File, REFIID riid, void **ppv)
 {
-    HRESULT hr;
+    HRESULT hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
     IShellFolder *pSF;
     PCUITEMID_CHILD pidlItem;
     PIDLIST_ABSOLUTE pidl = ILCreateFromPath(File);
+    *ppv = NULL;
     if (pidl && SUCCEEDED(SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &pSF), &pidlItem)))
     {
         hr = IShellFolder_GetUIObjectOf(pSF, hwnd, 1, &pidlItem, riid, NULL, ppv);
@@ -241,8 +243,14 @@ ShellExecuteVerb(HWND hwnd, PCWSTR Verb, PCWSTR File, BOOL Quit)
     }
 }
 
+UINT
+ErrorBox(HWND hwnd, UINT Error)
+{
+    return SHELL_ErrorBox(hwnd, Error);
+}
+
 void
 DisplayHelp(HWND hwnd)
 {
-    SHELL_ErrorBox(hwnd, ERROR_NOT_SUPPORTED);
+    ErrorBox(hwnd, ERROR_NOT_SUPPORTED);
 }


Reviews:
user: login: HBelusca
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T13:48:24Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T14:11:15Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T14:13:33Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T14:15:25Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T14:17:01Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T14:19:55Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T14:23:40Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T14:29:01Z
user: login: HBelusca
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T16:37:04Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T17:21:44Z
user: login: HBelusca
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T17:30:21Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T18:07:34Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T18:09:23Z
user: login: whindsaks
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T18:10:47Z
user: login: learn-more
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-26T13:49:38Z
user: login: HBelusca
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T22:41:52Z

Review comments:
diff_hunk: @@ -241,8 +243,14 @@ ShellExecuteVerb(HWND hwnd, PCWSTR Verb, PCWSTR File, BOOL Quit)
     }
 }
 
+UINT
+ErrorBox(HWND hwnd, UINT Error)
+{
+    return SHELL_ErrorBox(hwnd, Error);
path: dll/win32/shimgvw/util.c
user: login: HBelusca
body: Why the extra indirection (ErrorBox -> SHELL_ErrorBox) ?
created_at: 2025-03-09T13:04:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -374,18 +374,21 @@ Preview_pFreeImage(PPREVIEW_DATA pData)
         GdipDisposeImage(g_pImage);
         g_pImage = NULL;
     }
-
-    pData->m_szFile[0] = UNICODE_NULL;
 }
 
 static VOID
 Preview_pLoadImage(PPREVIEW_DATA pData, LPCWSTR szOpenFileName)
 {
     HRESULT hr;
+    BOOL bCanDel;
+    HWND hToolbar = GetDlgItem(g_hMainWnd, IDC_TOOLBAR);
     Preview_pFreeImage(pData);
     InvalidateRect(pData->m_hwnd, NULL, FALSE); /* Schedule redraw in case we change to "No preview" */
 
+    GetFullPathNameW(szOpenFileName, _countof(g_szFile), g_szFile, NULL);
     hr = LoadImageFromPath(szOpenFileName, &g_pImage);
+    bCanDel = hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) && hr != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
+    SendMessageW(hToolbar, TB_ENABLEBUTTON, IDC_DELETE, bCanDel);
path: dll/win32/shimgvw/shimgvw.c
user: login: HBelusca
body: If the hToolbar is going to be used only once in this function, then you could do instead:
```suggestion
    SendDlgItemMessageW(g_hMainWnd, IDC_TOOLBAR, TB_ENABLEBUTTON, IDC_DELETE, bCanDel);
```
created_at: 2025-03-09T13:14:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -374,18 +374,21 @@ Preview_pFreeImage(PPREVIEW_DATA pData)
         GdipDisposeImage(g_pImage);
         g_pImage = NULL;
     }
-
-    pData->m_szFile[0] = UNICODE_NULL;
 }
 
 static VOID
 Preview_pLoadImage(PPREVIEW_DATA pData, LPCWSTR szOpenFileName)
 {
     HRESULT hr;
+    BOOL bCanDel;
+    HWND hToolbar = GetDlgItem(g_hMainWnd, IDC_TOOLBAR);
path: dll/win32/shimgvw/shimgvw.c
user: login: HBelusca
body: and remove the extra variable
```suggestion
```
created_at: 2025-03-09T13:15:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,6 +31,7 @@ HINSTANCE           g_hInstance         = NULL;
 HWND                g_hMainWnd          = NULL;
 HWND                g_hwndFullscreen    = NULL;
 SHIMGVW_FILENODE *  g_pCurrentFile      = NULL;
+WCHAR               g_szFile[MAX_PATH];
path: dll/win32/shimgvw/shimgvw.c
user: login: HBelusca
body: Why do we need yet another global variable to store the (current?) file name? Isn't it sufficient to use what's available via the `g_pCurrentFile` linked list? (Namely, `g_pCurrentFile->Next->FileName`, if `g_pCurrentFile` is the reserved list head node).
created_at: 2025-03-09T13:17:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -584,6 +584,14 @@ Preview_UpdateImage(PPREVIEW_DATA pData)
     ZoomWnd_UpdateScroll(pData, TRUE);
 }
 
+static VOID
+Preview_LoadImage(PPREVIEW_DATA pData, SHIMGVW_FILENODE *pNode)
+{
+    Preview_pLoadImageFromNode(pData, pNode);
+    Preview_UpdateImage(pData);
+    Preview_UpdateUI(pData);
+}
path: dll/win32/shimgvw/shimgvw.c
user: login: HBelusca
body: Note: the function [Preview_GoNextPic()](https://git.reactos.org/?p=reactos.git;a=blob;f=dll/win32/shimgvw/shimgvw.c;hb=bdda5b93d714adbe828fe18c5cb785ed76f8861f#l1445) could also profit from using `Preview_LoadImage()`.
created_at: 2025-03-09T13:19:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1388,24 +1391,26 @@ Preview_Delete(PPREVIEW_DATA pData)
 
     /* Confirm file deletion and delete if allowed */
     FileOp.pFrom = szCurFile;
-    FileOp.fFlags = FOF_ALLOWUNDO;
-    if (SHFileOperationW(&FileOp) != 0)
+    FileOp.fFlags = GetKeyState(VK_SHIFT) < 0 ? 0 : FOF_ALLOWUNDO;
+    error = g_szFile[0] ? SHFileOperationW(&FileOp) : ERROR_FILE_NOT_FOUND;
+    if (error != 0)
     {
-        DPRINT("Preview_Delete: SHFileOperationW() failed or canceled\n");
+        if (!FileOp.fAnyOperationsAborted && error != ERROR_CANCELLED)
+            ErrorBox(FileOp.hwnd, error < 0x70 ? error : E_FAIL);
path: dll/win32/shimgvw/shimgvw.c
user: login: HBelusca
body: What 0x70 corresponds to, as an error?
created_at: 2025-03-09T13:20:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,16 +1457,31 @@ Preview_GoNextPic(PPREVIEW_DATA pData, BOOL bNext)
             g_pCurrentFile = g_pCurrentFile->Next;
         else
             g_pCurrentFile = g_pCurrentFile->Prev;
-        Preview_pLoadImageFromNode(pData, g_pCurrentFile);
-        Preview_UpdateImage(pData);
-        Preview_UpdateUI(pData);
+        Preview_LoadImage(pData, g_pCurrentFile);
     }
 }
 
+static HRESULT
+IsCommandEnabled(UINT nCmdId)
+{
+    HWND hToolbar = GetDlgItem(g_hMainWnd, IDC_TOOLBAR);
+    LRESULT Ok = SendMessageW(hToolbar, TB_ISBUTTONENABLED, nCmdId, 0);
+    return Ok ? S_OK : SendMessageW(hToolbar, TB_COMMANDTOINDEX, nCmdId, 0) != -1 ? S_FALSE : E_FAIL;
+}
+
 static VOID
-Preview_OnCommand(HWND hwnd, UINT nCommandID)
+Preview_OnCommand(HWND hwnd, WPARAM wParam, LPARAM lParam)
 {
     PPREVIEW_DATA pData = Preview_GetData(hwnd);
+    UINT nCommandID = LOWORD(wParam);
+    if (!lParam || !HIWORD(wParam))
path: dll/win32/shimgvw/shimgvw.c
user: login: HBelusca
body: What happens if we receive a WM_COMMAND for a valid command that is _not_ available via the toolbar, but only e.g. via a context menu?
It seems that, according to [WM_COMMAND (MSDN)](https://learn.microsoft.com/en-us/windows/win32/menurc/wm-command), this test is TRUE, then you run the IsCommandEnabled function that would return E_FAIL (since in this scenario we look at a valid command that is only available through other means than the toolbar). The hr wouldn't be S_OK and as a result, we would return early, without running the actual command.
created_at: 2025-03-09T13:44:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,6 +28,8 @@
 #define IDB_MODIFY      64
 #define IDB_HELP_TOC    65
 
+#define IDC_TOOLBAR     42
path: dll/win32/shimgvw/resource.h
user: login: HBelusca
body: ```suggestion
#define IDC_TOOLBAR     40
```
Please start groups at multiples of 10
created_at: 2025-03-09T13:44:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1388,24 +1391,26 @@ Preview_Delete(PPREVIEW_DATA pData)
 
     /* Confirm file deletion and delete if allowed */
     FileOp.pFrom = szCurFile;
-    FileOp.fFlags = FOF_ALLOWUNDO;
-    if (SHFileOperationW(&FileOp) != 0)
+    FileOp.fFlags = GetKeyState(VK_SHIFT) < 0 ? 0 : FOF_ALLOWUNDO;
+    error = g_szFile[0] ? SHFileOperationW(&FileOp) : ERROR_FILE_NOT_FOUND;
+    if (error != 0)
     {
-        DPRINT("Preview_Delete: SHFileOperationW() failed or canceled\n");
+        if (!FileOp.fAnyOperationsAborted && error != ERROR_CANCELLED)
+            ErrorBox(FileOp.hwnd, error < 0x70 ? error : E_FAIL);
         return;
     }
 
     /* Reload the file list and go next file */
     pFreeFileList(g_pCurrentFile);
     g_pCurrentFile = pBuildFileList(szNextFile);
path: dll/win32/shimgvw/shimgvw.c
user: login: HBelusca
body: Would it be possible to be a bit more clever and unlink the file being deleted from the `g_pCurrentFile` list (and ofc. update this pointer), instead of rebuilding the whole file list?
created_at: 2025-03-09T13:46:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -241,8 +243,14 @@ ShellExecuteVerb(HWND hwnd, PCWSTR Verb, PCWSTR File, BOOL Quit)
     }
 }
 
+UINT
+ErrorBox(HWND hwnd, UINT Error)
+{
+    return SHELL_ErrorBox(hwnd, Error);
path: dll/win32/shimgvw/util.c
user: login: whindsaks
body: `SHELL_ErrorBox` is in shellutils.h and that file does not work in all source files (yet).
created_at: 2025-03-09T14:11:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,6 +31,7 @@ HINSTANCE           g_hInstance         = NULL;
 HWND                g_hMainWnd          = NULL;
 HWND                g_hwndFullscreen    = NULL;
 SHIMGVW_FILENODE *  g_pCurrentFile      = NULL;
+WCHAR               g_szFile[MAX_PATH];
path: dll/win32/shimgvw/shimgvw.c
user: login: whindsaks
body: g_szFile is guaranteed to be a full path or a empty string while g_pCurrentFile can be NULL.

Note that this PR goes from two to one MAX_PATH file buffers.
created_at: 2025-03-09T14:13:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1388,24 +1391,26 @@ Preview_Delete(PPREVIEW_DATA pData)
 
     /* Confirm file deletion and delete if allowed */
     FileOp.pFrom = szCurFile;
-    FileOp.fFlags = FOF_ALLOWUNDO;
-    if (SHFileOperationW(&FileOp) != 0)
+    FileOp.fFlags = GetKeyState(VK_SHIFT) < 0 ? 0 : FOF_ALLOWUNDO;
+    error = g_szFile[0] ? SHFileOperationW(&FileOp) : ERROR_FILE_NOT_FOUND;
+    if (error != 0)
     {
-        DPRINT("Preview_Delete: SHFileOperationW() failed or canceled\n");
+        if (!FileOp.fAnyOperationsAborted && error != ERROR_CANCELLED)
+            ErrorBox(FileOp.hwnd, error < 0x70 ? error : E_FAIL);
path: dll/win32/shimgvw/shimgvw.c
user: login: whindsaks
body: The DE_ error values in the documentation for SHFileOperation. I'll make this clearer...
created_at: 2025-03-09T14:15:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,6 +28,8 @@
 #define IDB_MODIFY      64
 #define IDB_HELP_TOC    65
 
+#define IDC_TOOLBAR     42
path: dll/win32/shimgvw/resource.h
user: login: whindsaks
body: Why? This just needs to be outside the range of the standard IDOK, IDCANCEL etc.
created_at: 2025-03-09T14:17:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1388,24 +1391,26 @@ Preview_Delete(PPREVIEW_DATA pData)
 
     /* Confirm file deletion and delete if allowed */
     FileOp.pFrom = szCurFile;
-    FileOp.fFlags = FOF_ALLOWUNDO;
-    if (SHFileOperationW(&FileOp) != 0)
+    FileOp.fFlags = GetKeyState(VK_SHIFT) < 0 ? 0 : FOF_ALLOWUNDO;
+    error = g_szFile[0] ? SHFileOperationW(&FileOp) : ERROR_FILE_NOT_FOUND;
+    if (error != 0)
     {
-        DPRINT("Preview_Delete: SHFileOperationW() failed or canceled\n");
+        if (!FileOp.fAnyOperationsAborted && error != ERROR_CANCELLED)
+            ErrorBox(FileOp.hwnd, error < 0x70 ? error : E_FAIL);
         return;
     }
 
     /* Reload the file list and go next file */
     pFreeFileList(g_pCurrentFile);
     g_pCurrentFile = pBuildFileList(szNextFile);
path: dll/win32/shimgvw/shimgvw.c
user: login: whindsaks
body: Yes, I have a different branch that does that but it turned into a lot more code by reloading the list if there was only one image and we just deleted it etc. I don't want to take on this in this PR.
created_at: 2025-03-09T14:19:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1452,16 +1457,31 @@ Preview_GoNextPic(PPREVIEW_DATA pData, BOOL bNext)
             g_pCurrentFile = g_pCurrentFile->Next;
         else
             g_pCurrentFile = g_pCurrentFile->Prev;
-        Preview_pLoadImageFromNode(pData, g_pCurrentFile);
-        Preview_UpdateImage(pData);
-        Preview_UpdateUI(pData);
+        Preview_LoadImage(pData, g_pCurrentFile);
     }
 }
 
+static HRESULT
+IsCommandEnabled(UINT nCmdId)
+{
+    HWND hToolbar = GetDlgItem(g_hMainWnd, IDC_TOOLBAR);
+    LRESULT Ok = SendMessageW(hToolbar, TB_ISBUTTONENABLED, nCmdId, 0);
+    return Ok ? S_OK : SendMessageW(hToolbar, TB_COMMANDTOINDEX, nCmdId, 0) != -1 ? S_FALSE : E_FAIL;
+}
+
 static VOID
-Preview_OnCommand(HWND hwnd, UINT nCommandID)
+Preview_OnCommand(HWND hwnd, WPARAM wParam, LPARAM lParam)
 {
     PPREVIEW_DATA pData = Preview_GetData(hwnd);
+    UINT nCommandID = LOWORD(wParam);
+    if (!lParam || !HIWORD(wParam))
path: dll/win32/shimgvw/shimgvw.c
user: login: whindsaks
body: The context menu never sends a command here but I can change it to be a little more forgiving.
created_at: 2025-03-09T14:23:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -584,6 +584,14 @@ Preview_UpdateImage(PPREVIEW_DATA pData)
     ZoomWnd_UpdateScroll(pData, TRUE);
 }
 
+static VOID
+Preview_LoadImage(PPREVIEW_DATA pData, SHIMGVW_FILENODE *pNode)
+{
+    Preview_pLoadImageFromNode(pData, pNode);
+    Preview_UpdateImage(pData);
+    Preview_UpdateUI(pData);
+}
path: dll/win32/shimgvw/shimgvw.c
user: login: whindsaks
body: It already does that in this PR?
created_at: 2025-03-09T14:29:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -241,8 +243,14 @@ ShellExecuteVerb(HWND hwnd, PCWSTR Verb, PCWSTR File, BOOL Quit)
     }
 }
 
+UINT
+ErrorBox(HWND hwnd, UINT Error)
+{
+    return SHELL_ErrorBox(hwnd, Error);
path: dll/win32/shimgvw/util.c
user: login: HBelusca
body: ok but it (used to) work in shimgvw so far, no? (otherwise compilation would have failed)
created_at: 2025-03-09T16:37:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -584,6 +584,14 @@ Preview_UpdateImage(PPREVIEW_DATA pData)
     ZoomWnd_UpdateScroll(pData, TRUE);
 }
 
+static VOID
+Preview_LoadImage(PPREVIEW_DATA pData, SHIMGVW_FILENODE *pNode)
+{
+    Preview_pLoadImageFromNode(pData, pNode);
+    Preview_UpdateImage(pData);
+    Preview_UpdateUI(pData);
+}
path: dll/win32/shimgvw/shimgvw.c
user: login: HBelusca
body: Oh, I missed it ^^ Thanks
created_at: 2025-03-09T17:16:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -241,8 +243,14 @@ ShellExecuteVerb(HWND hwnd, PCWSTR Verb, PCWSTR File, BOOL Quit)
     }
 }
 
+UINT
+ErrorBox(HWND hwnd, UINT Error)
+{
+    return SHELL_ErrorBox(hwnd, Error);
path: dll/win32/shimgvw/util.c
user: login: whindsaks
body: shellutils.h was never in shimgvw.h nor shimgvw.c, only in the util file.
created_at: 2025-03-09T17:21:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -374,18 +374,20 @@ Preview_pFreeImage(PPREVIEW_DATA pData)
         GdipDisposeImage(g_pImage);
         g_pImage = NULL;
     }
-
-    pData->m_szFile[0] = UNICODE_NULL;
 }
 
 static VOID
 Preview_pLoadImage(PPREVIEW_DATA pData, LPCWSTR szOpenFileName)
 {
     HRESULT hr;
+    BOOL bCanDel;
     Preview_pFreeImage(pData);
     InvalidateRect(pData->m_hwnd, NULL, FALSE); /* Schedule redraw in case we change to "No preview" */
 
+    GetFullPathNameW(szOpenFileName, _countof(g_szFile), g_szFile, NULL);
     hr = LoadImageFromPath(szOpenFileName, &g_pImage);
+    bCanDel = hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) && hr != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
+    SendDlgItemMessageW(g_hMainWnd, IDC_TOOLBAR, TB_ENABLEBUTTON, IDC_DELETE, bCanDel);
     if (FAILED(hr))
     {
         DPRINT1("GdipLoadImageFromStream() failed, %d\n", hr);
path: dll/win32/shimgvw/shimgvw.c
user: login: HBelusca
body: `g_szFile` may have been changed and set to a new (and possibly valid) file path, even if we then failed because the specified (existing) file wasn't a valid picture.
Is this then wishable to have this situation, or would it be better to reset `g_szFile` later (e.g. after the `Anime_LoadInfo` call as it was originally done in the code)? Alternatively, shouldn't the `g_szFile` be NULLified in this failure path?
created_at: 2025-03-09T17:26:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1368,14 +1373,11 @@ static VOID
 Preview_Delete(PPREVIEW_DATA pData)
 {
     WCHAR szCurFile[MAX_PATH + 1], szNextFile[MAX_PATH];
-    HWND hwnd = pData->m_hwnd;
-    SHFILEOPSTRUCTW FileOp = { hwnd, FO_DELETE };
-
-    if (!pData->m_szFile[0])
path: dll/win32/shimgvw/shimgvw.c
user: login: HBelusca
body: Keep this (replaced by a `if (!*g_szFile)` instead)? Or a `assert(*g_szFile);` at worst?
created_at: 2025-03-09T17:28:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1368,14 +1373,11 @@ static VOID
 Preview_Delete(PPREVIEW_DATA pData)
 {
     WCHAR szCurFile[MAX_PATH + 1], szNextFile[MAX_PATH];
-    HWND hwnd = pData->m_hwnd;
-    SHFILEOPSTRUCTW FileOp = { hwnd, FO_DELETE };
-
-    if (!pData->m_szFile[0])
-        return;
+    SHFILEOPSTRUCTW FileOp = { pData->m_hwnd, FO_DELETE };
+    UINT error;
 
     /* FileOp.pFrom must be double-null-terminated */
-    GetFullPathNameW(pData->m_szFile, _countof(szCurFile) - 1, szCurFile, NULL);
+    GetFullPathNameW(g_szFile, _countof(szCurFile) - 1, szCurFile, NULL);
path: dll/win32/shimgvw/shimgvw.c
user: login: HBelusca
body: Note that in your PR, `g_szFile` is already initialized to a full path name in `Preview_pLoadImage()`, so I'm not sure whether it's necessary to re-calculate a full path name again?
created_at: 2025-03-09T17:29:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1368,14 +1373,11 @@ static VOID
 Preview_Delete(PPREVIEW_DATA pData)
 {
     WCHAR szCurFile[MAX_PATH + 1], szNextFile[MAX_PATH];
-    HWND hwnd = pData->m_hwnd;
-    SHFILEOPSTRUCTW FileOp = { hwnd, FO_DELETE };
-
-    if (!pData->m_szFile[0])
-        return;
+    SHFILEOPSTRUCTW FileOp = { pData->m_hwnd, FO_DELETE };
+    UINT error;
 
     /* FileOp.pFrom must be double-null-terminated */
-    GetFullPathNameW(pData->m_szFile, _countof(szCurFile) - 1, szCurFile, NULL);
+    GetFullPathNameW(g_szFile, _countof(szCurFile) - 1, szCurFile, NULL);
path: dll/win32/shimgvw/shimgvw.c
user: login: whindsaks
body: Note that `pData->m_szFile` and `g_szFile` is the same thing, I changed m_szFile to g_szFile in this PR because they should always be the same thing (pData is window vs fullscreen).

We could probably change GetFullPathNameW to lstrcpynW and save one NULL parameter but since SHFileOperation is fussy about relative paths we might as well just leave it as it was, just in case.
created_at: 2025-03-09T18:07:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1368,14 +1373,11 @@ static VOID
 Preview_Delete(PPREVIEW_DATA pData)
 {
     WCHAR szCurFile[MAX_PATH + 1], szNextFile[MAX_PATH];
-    HWND hwnd = pData->m_hwnd;
-    SHFILEOPSTRUCTW FileOp = { hwnd, FO_DELETE };
-
-    if (!pData->m_szFile[0])
path: dll/win32/shimgvw/shimgvw.c
user: login: whindsaks
body: It should not be possible to execute the delete command now if there is no valid file.
created_at: 2025-03-09T18:09:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -374,18 +374,20 @@ Preview_pFreeImage(PPREVIEW_DATA pData)
         GdipDisposeImage(g_pImage);
         g_pImage = NULL;
     }
-
-    pData->m_szFile[0] = UNICODE_NULL;
 }
 
 static VOID
 Preview_pLoadImage(PPREVIEW_DATA pData, LPCWSTR szOpenFileName)
 {
     HRESULT hr;
+    BOOL bCanDel;
     Preview_pFreeImage(pData);
     InvalidateRect(pData->m_hwnd, NULL, FALSE); /* Schedule redraw in case we change to "No preview" */
 
+    GetFullPathNameW(szOpenFileName, _countof(g_szFile), g_szFile, NULL);
     hr = LoadImageFromPath(szOpenFileName, &g_pImage);
+    bCanDel = hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) && hr != HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
+    SendDlgItemMessageW(g_hMainWnd, IDC_TOOLBAR, TB_ENABLEBUTTON, IDC_DELETE, bCanDel);
     if (FAILED(hr))
     {
         DPRINT1("GdipLoadImageFromStream() failed, %d\n", hr);
path: dll/win32/shimgvw/shimgvw.c
user: login: whindsaks
body: I did it this was so you can delete a file even if it's not a valid image.
created_at: 2025-03-09T18:10:47Z
author_association: CONTRIBUTOR

PR comments:
user: login: HBelusca
created_at: 2025-03-09T13:48:56Z
body: > I enabled the copy menu item by switching to OleInit.

The CoInitialize doesn't support that?
author_association: CONTRIBUTOR
user: login: whindsaks
created_at: 2025-03-09T14:46:36Z
body: > The CoInitialize doesn't support that?

No, shell clipboard file copy needs OLE.
author_association: CONTRIBUTOR