xref: /reactos/dll/win32/shimgvw/shimgvw.c (revision 0c2cdcae)
1 /*
2  * PROJECT:     ReactOS Picture and Fax Viewer
3  * LICENSE:     GPL-2.0 (https://spdx.org/licenses/GPL-2.0)
4  * PURPOSE:     Image file browsing and manipulation
5  * COPYRIGHT:   Copyright Dmitry Chapyshev (dmitry@reactos.org)
6  *              Copyright 2018-2023 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
7  */
8 
9 #include "shimgvw.h"
10 #include <windowsx.h>
11 #include <commctrl.h>
12 #include <commdlg.h>
13 #include <shlobj.h>
14 #include <shellapi.h>
15 
16 /* Toolbar image size */
17 #define TB_IMAGE_WIDTH  16
18 #define TB_IMAGE_HEIGHT 16
19 
20 /* Slide show timer */
21 #define SLIDESHOW_TIMER_ID          0xFACE
22 #define SLIDESHOW_TIMER_INTERVAL    5000 /* 5 seconds */
23 
24 HINSTANCE           g_hInstance         = NULL;
25 HWND                g_hMainWnd          = NULL;
26 HWND                g_hwndFullscreen    = NULL;
27 SHIMGVW_FILENODE *  g_pCurrentFile      = NULL;
28 GpImage *           g_pImage            = NULL;
29 SHIMGVW_SETTINGS    g_Settings;
30 
31 static const UINT s_ZoomSteps[] =
32 {
33     5, 10, 25, 50, 100, 200, 300, 500, 1000, 2000, 4000
34 };
35 
36 #define MIN_ZOOM s_ZoomSteps[0]
37 #define MAX_ZOOM s_ZoomSteps[_countof(s_ZoomSteps) - 1]
38 
39     /* iBitmap,       idCommand,   fsState,         fsStyle,     bReserved[2], dwData, iString */
40 #define DEFINE_BTN_INFO(_name) \
41     { TBICON_##_name, IDC_##_name, TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0 }
42 
43 #define DEFINE_BTN_SEPARATOR \
44     { -1,             0,           TBSTATE_ENABLED, BTNS_SEP,    {0}, 0, 0 }
45 
46 /* ToolBar Buttons */
47 static const TBBUTTON s_Buttons[] =
48 {
49     DEFINE_BTN_INFO(PREV_PIC),
50     DEFINE_BTN_INFO(NEXT_PIC),
51     DEFINE_BTN_SEPARATOR,
52     DEFINE_BTN_INFO(BEST_FIT),
53     DEFINE_BTN_INFO(REAL_SIZE),
54     DEFINE_BTN_INFO(SLIDE_SHOW),
55     DEFINE_BTN_SEPARATOR,
56     DEFINE_BTN_INFO(ZOOM_IN),
57     DEFINE_BTN_INFO(ZOOM_OUT),
58     DEFINE_BTN_SEPARATOR,
59     DEFINE_BTN_INFO(ROT_CLOCKW),
60     DEFINE_BTN_INFO(ROT_COUNCW),
61     DEFINE_BTN_SEPARATOR,
62     DEFINE_BTN_INFO(ROT_CWSAVE),
63     DEFINE_BTN_INFO(ROT_CCWSAVE),
64     DEFINE_BTN_SEPARATOR,
65     DEFINE_BTN_INFO(DELETE),
66     DEFINE_BTN_INFO(PRINT),
67     DEFINE_BTN_INFO(SAVEAS),
68     DEFINE_BTN_INFO(MODIFY),
69     DEFINE_BTN_SEPARATOR,
70     DEFINE_BTN_INFO(HELP_TOC)
71 };
72 
73 /* ToolBar Button configuration */
74 typedef struct
75 {
76     DWORD idb;  /* Index to bitmap */
77     DWORD ids;  /* Index to tooltip */
78 } TB_BUTTON_CONFIG;
79 
80 #define DEFINE_BTN_CONFIG(_name) { IDB_##_name, IDS_TOOLTIP_##_name }
81 
82 static const TB_BUTTON_CONFIG s_ButtonConfig[] =
83 {
84     DEFINE_BTN_CONFIG(PREV_PIC),
85     DEFINE_BTN_CONFIG(NEXT_PIC),
86     DEFINE_BTN_CONFIG(BEST_FIT),
87     DEFINE_BTN_CONFIG(REAL_SIZE),
88     DEFINE_BTN_CONFIG(SLIDE_SHOW),
89     DEFINE_BTN_CONFIG(ZOOM_IN),
90     DEFINE_BTN_CONFIG(ZOOM_OUT),
91     DEFINE_BTN_CONFIG(ROT_CLOCKW),
92     DEFINE_BTN_CONFIG(ROT_COUNCW),
93     DEFINE_BTN_CONFIG(ROT_CWSAVE),
94     DEFINE_BTN_CONFIG(ROT_CCWSAVE),
95     DEFINE_BTN_CONFIG(DELETE),
96     DEFINE_BTN_CONFIG(PRINT),
97     DEFINE_BTN_CONFIG(SAVEAS),
98     DEFINE_BTN_CONFIG(MODIFY),
99     DEFINE_BTN_CONFIG(HELP_TOC),
100 };
101 
102 typedef struct tagPREVIEW_DATA
103 {
104     HWND m_hwnd;
105     HWND m_hwndZoom;
106     HWND m_hwndToolBar;
107     INT m_nZoomPercents;
108     ANIME m_Anime; /* Animation */
109     INT m_xScrollOffset;
110     INT m_yScrollOffset;
111     UINT m_nMouseDownMsg;
112     POINT m_ptOrigin;
113     IStream *m_pMemStream;
114     WCHAR m_szFile[MAX_PATH];
115 } PREVIEW_DATA, *PPREVIEW_DATA;
116 
117 static inline PPREVIEW_DATA
118 Preview_GetData(HWND hwnd)
119 {
120     return (PPREVIEW_DATA)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
121 }
122 
123 static inline BOOL
124 Preview_IsMainWnd(HWND hwnd)
125 {
126     return hwnd == g_hMainWnd;
127 }
128 
129 static VOID
130 Preview_RestartTimer(HWND hwnd)
131 {
132     if (!Preview_IsMainWnd(hwnd))
133     {
134         KillTimer(hwnd, SLIDESHOW_TIMER_ID);
135         SetTimer(hwnd, SLIDESHOW_TIMER_ID, SLIDESHOW_TIMER_INTERVAL, NULL);
136     }
137 }
138 
139 static VOID
140 ZoomWnd_UpdateScroll(PPREVIEW_DATA pData, HWND hwnd, BOOL bResetPos)
141 {
142     RECT rcClient;
143     UINT ImageWidth, ImageHeight, ZoomedWidth, ZoomedHeight;
144     SCROLLINFO si;
145     BOOL bShowHorz, bShowVert;
146 
147     if (bResetPos)
148         pData->m_xScrollOffset = pData->m_yScrollOffset = 0;
149 
150     if (!g_pImage)
151     {
152         ShowScrollBar(hwnd, SB_BOTH, FALSE);
153         InvalidateRect(hwnd, NULL, TRUE);
154         return;
155     }
156 
157     GdipGetImageWidth(g_pImage, &ImageWidth);
158     GdipGetImageHeight(g_pImage, &ImageHeight);
159 
160     ZoomedWidth  = (ImageWidth  * pData->m_nZoomPercents) / 100;
161     ZoomedHeight = (ImageHeight * pData->m_nZoomPercents) / 100;
162 
163     GetClientRect(hwnd, &rcClient);
164 
165     bShowHorz = (rcClient.right < ZoomedWidth);
166     bShowVert = (rcClient.bottom < ZoomedHeight);
167     ShowScrollBar(hwnd, SB_HORZ, bShowHorz);
168     ShowScrollBar(hwnd, SB_VERT, bShowVert);
169 
170     GetClientRect(hwnd, &rcClient);
171 
172     ZeroMemory(&si, sizeof(si));
173     si.cbSize = sizeof(si);
174     si.fMask = SIF_ALL;
175 
176     if (bShowHorz)
177     {
178         GetScrollInfo(hwnd, SB_HORZ, &si);
179         si.nPage = rcClient.right;
180         si.nMin = 0;
181         si.nMax = ZoomedWidth;
182         si.nPos = (ZoomedWidth - rcClient.right) / 2 + pData->m_xScrollOffset;
183         si.nPos = max(min(si.nPos, si.nMax - (INT)si.nPage), si.nMin);
184         SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
185         pData->m_xScrollOffset = si.nPos - (ZoomedWidth - rcClient.right) / 2;
186     }
187     else
188     {
189         pData->m_xScrollOffset = 0;
190     }
191 
192     if (bShowVert)
193     {
194         GetScrollInfo(hwnd, SB_VERT, &si);
195         si.nPage = rcClient.bottom;
196         si.nMin = 0;
197         si.nMax = ZoomedHeight;
198         si.nPos = (ZoomedHeight - rcClient.bottom) / 2 + pData->m_yScrollOffset;
199         si.nPos = max(min(si.nPos, si.nMax - (INT)si.nPage), si.nMin);
200         SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
201         pData->m_yScrollOffset = si.nPos - (ZoomedHeight - rcClient.bottom) / 2;
202     }
203     else
204     {
205         pData->m_yScrollOffset = 0;
206     }
207 
208     InvalidateRect(hwnd, NULL, TRUE);
209 }
210 
211 static VOID
212 Preview_UpdateZoom(PPREVIEW_DATA pData, UINT NewZoom, BOOL bEnableBestFit, BOOL bEnableRealSize)
213 {
214     BOOL bEnableZoomIn, bEnableZoomOut;
215     HWND hToolBar = pData->m_hwndToolBar;
216 
217     pData->m_nZoomPercents = NewZoom;
218 
219     /* Check if a zoom button of the toolbar must be grayed */
220     bEnableZoomIn  = (NewZoom < MAX_ZOOM);
221     bEnableZoomOut = (NewZoom > MIN_ZOOM);
222 
223     /* Update toolbar buttons */
224     PostMessageW(hToolBar, TB_ENABLEBUTTON, IDC_ZOOM_OUT, bEnableZoomOut);
225     PostMessageW(hToolBar, TB_ENABLEBUTTON, IDC_ZOOM_IN,  bEnableZoomIn);
226     PostMessageW(hToolBar, TB_ENABLEBUTTON, IDC_BEST_FIT, bEnableBestFit);
227     PostMessageW(hToolBar, TB_ENABLEBUTTON, IDC_REAL_SIZE, bEnableRealSize);
228 
229     /* Redraw the display window */
230     InvalidateRect(pData->m_hwndZoom, NULL, TRUE);
231 
232     /* Restart timer if necessary */
233     Preview_RestartTimer(pData->m_hwnd);
234 
235     /* Update scroll info */
236     ZoomWnd_UpdateScroll(pData, pData->m_hwndZoom, FALSE);
237 }
238 
239 static VOID
240 Preview_ZoomInOrOut(PPREVIEW_DATA pData, BOOL bZoomIn)
241 {
242     UINT i, NewZoom;
243 
244     if (g_pImage == NULL)
245         return;
246 
247     if (bZoomIn)    /* zoom in */
248     {
249         /* find next step */
250         for (i = 0; i < _countof(s_ZoomSteps); ++i)
251         {
252             if (pData->m_nZoomPercents < s_ZoomSteps[i])
253                 break;
254         }
255         NewZoom = ((i >= _countof(s_ZoomSteps)) ? MAX_ZOOM : s_ZoomSteps[i]);
256     }
257     else            /* zoom out */
258     {
259         /* find previous step */
260         for (i = _countof(s_ZoomSteps); i > 0; )
261         {
262             --i;
263             if (s_ZoomSteps[i] < pData->m_nZoomPercents)
264                 break;
265         }
266         NewZoom = ((i < 0) ? MIN_ZOOM : s_ZoomSteps[i]);
267     }
268 
269     /* Update toolbar and refresh screen */
270     Preview_UpdateZoom(pData, NewZoom, TRUE, TRUE);
271 }
272 
273 static VOID
274 Preview_ResetZoom(PPREVIEW_DATA pData)
275 {
276     RECT Rect;
277     UINT ImageWidth, ImageHeight, NewZoom;
278 
279     if (g_pImage == NULL)
280         return;
281 
282     /* get disp window size and image size */
283     GetClientRect(pData->m_hwndZoom, &Rect);
284     GdipGetImageWidth(g_pImage, &ImageWidth);
285     GdipGetImageHeight(g_pImage, &ImageHeight);
286 
287     /* compare two aspect rates. same as
288        (ImageHeight / ImageWidth < Rect.bottom / Rect.right) in real */
289     if (ImageHeight * Rect.right < Rect.bottom * ImageWidth)
290     {
291         if (Rect.right < ImageWidth)
292         {
293             /* it's large, shrink it */
294             NewZoom = (Rect.right * 100) / ImageWidth;
295         }
296         else
297         {
298             /* it's small. show as original size */
299             NewZoom = 100;
300         }
301     }
302     else
303     {
304         if (Rect.bottom < ImageHeight)
305         {
306             /* it's large, shrink it */
307             NewZoom = (Rect.bottom * 100) / ImageHeight;
308         }
309         else
310         {
311             /* it's small. show as original size */
312             NewZoom = 100;
313         }
314     }
315 
316     Preview_UpdateZoom(pData, NewZoom, FALSE, TRUE);
317 }
318 
319 static VOID
320 Preview_UpdateTitle(PPREVIEW_DATA pData, LPCWSTR FileName)
321 {
322     WCHAR szText[MAX_PATH + 100];
323     LPWSTR pchFileTitle;
324 
325     LoadStringW(g_hInstance, IDS_APPTITLE, szText, _countof(szText));
326 
327     pchFileTitle = PathFindFileNameW(FileName);
328     if (pchFileTitle && *pchFileTitle)
329     {
330         StringCchCatW(szText, _countof(szText), L" - ");
331         StringCchCatW(szText, _countof(szText), pchFileTitle);
332     }
333 
334     SetWindowTextW(pData->m_hwnd, szText);
335 }
336 
337 static VOID
338 Preview_pFreeImage(PPREVIEW_DATA pData)
339 {
340     Anime_FreeInfo(&pData->m_Anime);
341 
342     if (g_pImage)
343     {
344         GdipDisposeImage(g_pImage);
345         g_pImage = NULL;
346     }
347 
348     if (pData->m_pMemStream)
349     {
350         pData->m_pMemStream->lpVtbl->Release(pData->m_pMemStream);
351         pData->m_pMemStream = NULL;
352     }
353 
354     pData->m_szFile[0] = UNICODE_NULL;
355 }
356 
357 IStream* MemStreamFromFile(LPCWSTR pszFileName)
358 {
359     HANDLE hFile;
360     DWORD dwFileSize, dwRead;
361     LPBYTE pbMemFile = NULL;
362     IStream *pStream;
363 
364     hFile = CreateFileW(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
365                         OPEN_EXISTING, 0, NULL);
366     if (hFile == INVALID_HANDLE_VALUE)
367         return NULL;
368 
369     dwFileSize = GetFileSize(hFile, NULL);
370     pbMemFile = QuickAlloc(dwFileSize, FALSE);
371     if (!dwFileSize || (dwFileSize == INVALID_FILE_SIZE) || !pbMemFile)
372     {
373         CloseHandle(hFile);
374         return NULL;
375     }
376 
377     if (!ReadFile(hFile, pbMemFile, dwFileSize, &dwRead, NULL) || (dwRead != dwFileSize))
378     {
379         QuickFree(pbMemFile);
380         CloseHandle(hFile);
381         return NULL;
382     }
383 
384     CloseHandle(hFile);
385     pStream = SHCreateMemStream(pbMemFile, dwFileSize);
386     QuickFree(pbMemFile);
387     return pStream;
388 }
389 
390 static VOID
391 Preview_pLoadImage(PPREVIEW_DATA pData, LPCWSTR szOpenFileName)
392 {
393     Preview_pFreeImage(pData);
394 
395     pData->m_pMemStream = MemStreamFromFile(szOpenFileName);
396     if (!pData->m_pMemStream)
397     {
398         DPRINT1("MemStreamFromFile() failed\n");
399         Preview_UpdateTitle(pData, NULL);
400         return;
401     }
402 
403     /* NOTE: GdipLoadImageFromFile locks the file.
404              Avoid file locking by using GdipLoadImageFromStream and memory stream. */
405     GdipLoadImageFromStream(pData->m_pMemStream, &g_pImage);
406     if (!g_pImage)
407     {
408         DPRINT1("GdipLoadImageFromStream() failed\n");
409         Preview_pFreeImage(pData);
410         Preview_UpdateTitle(pData, NULL);
411         return;
412     }
413 
414     Anime_LoadInfo(&pData->m_Anime);
415 
416     SHAddToRecentDocs(SHARD_PATHW, szOpenFileName);
417     GetFullPathNameW(szOpenFileName, _countof(pData->m_szFile), pData->m_szFile, NULL);
418 
419     /* Reset zoom and redraw display */
420     Preview_ResetZoom(pData);
421 
422     Preview_UpdateTitle(pData, szOpenFileName);
423 }
424 
425 static VOID
426 Preview_pLoadImageFromNode(PPREVIEW_DATA pData, SHIMGVW_FILENODE *pNode)
427 {
428     Preview_pLoadImage(pData, (pNode ? pNode->FileName : NULL));
429 }
430 
431 static BOOL
432 Preview_pSaveImage(PPREVIEW_DATA pData, LPCWSTR pszFile)
433 {
434     ImageCodecInfo *codecInfo;
435     GUID rawFormat;
436     UINT j, num, nFilterIndex, size;
437     BOOL ret = FALSE;
438 
439     if (g_pImage == NULL)
440         return FALSE;
441 
442     GdipGetImageEncodersSize(&num, &size);
443     codecInfo = QuickAlloc(size, FALSE);
444     if (!codecInfo)
445     {
446         DPRINT1("QuickAlloc() failed in pSaveImage()\n");
447         return FALSE;
448     }
449     GdipGetImageEncoders(num, size, codecInfo);
450 
451     GdipGetImageRawFormat(g_pImage, &rawFormat);
452     if (IsEqualGUID(&rawFormat, &ImageFormatMemoryBMP))
453         rawFormat = ImageFormatBMP;
454 
455     nFilterIndex = 0;
456     for (j = 0; j < num; ++j)
457     {
458         if (IsEqualGUID(&rawFormat, &codecInfo[j].FormatID))
459         {
460             nFilterIndex = j + 1;
461             break;
462         }
463     }
464 
465     Anime_Pause(&pData->m_Anime);
466 
467     ret = (nFilterIndex > 0) &&
468           (GdipSaveImageToFile(g_pImage, pszFile, &codecInfo[nFilterIndex - 1].Clsid, NULL) == Ok);
469     if (!ret)
470         DPRINT1("GdipSaveImageToFile() failed\n");
471 
472     Anime_Start(&pData->m_Anime, 0);
473 
474     QuickFree(codecInfo);
475     return ret;
476 }
477 
478 static VOID
479 Preview_pSaveImageAs(PPREVIEW_DATA pData)
480 {
481     OPENFILENAMEW sfn;
482     ImageCodecInfo *codecInfo;
483     WCHAR szSaveFileName[MAX_PATH];
484     WCHAR *szFilterMask;
485     GUID rawFormat;
486     UINT num, size, j;
487     size_t sizeRemain;
488     WCHAR *c;
489     HWND hwnd = pData->m_hwnd;
490 
491     if (g_pImage == NULL)
492         return;
493 
494     GdipGetImageEncodersSize(&num, &size);
495     codecInfo = QuickAlloc(size, FALSE);
496     if (!codecInfo)
497     {
498         DPRINT1("QuickAlloc() failed in pSaveImageAs()\n");
499         return;
500     }
501 
502     GdipGetImageEncoders(num, size, codecInfo);
503 
504     GdipGetImageRawFormat(g_pImage, &rawFormat);
505     if (IsEqualGUID(&rawFormat, &ImageFormatMemoryBMP))
506         rawFormat = ImageFormatBMP;
507 
508     sizeRemain = 0;
509     for (j = 0; j < num; ++j)
510     {
511         // Every pair needs space for the Description, twice the Extensions, 1 char for the space, 2 for the braces and 2 for the NULL terminators.
512         sizeRemain = sizeRemain + (((wcslen(codecInfo[j].FormatDescription) + (wcslen(codecInfo[j].FilenameExtension) * 2) + 5) * sizeof(WCHAR)));
513     }
514 
515     /* Add two more chars for the last terminator */
516     sizeRemain += (sizeof(WCHAR) * 2);
517 
518     szFilterMask = QuickAlloc(sizeRemain, FALSE);
519     if (!szFilterMask)
520     {
521         DPRINT1("cannot allocate memory for filter mask in pSaveImageAs()");
522         QuickFree(codecInfo);
523         return;
524     }
525 
526     ZeroMemory(szSaveFileName, sizeof(szSaveFileName));
527     ZeroMemory(szFilterMask, sizeRemain);
528     ZeroMemory(&sfn, sizeof(sfn));
529     sfn.lStructSize = sizeof(sfn);
530     sfn.hwndOwner   = hwnd;
531     sfn.lpstrFile   = szSaveFileName;
532     sfn.lpstrFilter = szFilterMask;
533     sfn.nMaxFile    = _countof(szSaveFileName);
534     sfn.Flags       = OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
535     sfn.lpstrDefExt = L"png";
536 
537     c = szFilterMask;
538 
539     for (j = 0; j < num; ++j)
540     {
541         StringCbPrintfExW(c, sizeRemain, &c, &sizeRemain, 0, L"%ls (%ls)", codecInfo[j].FormatDescription, codecInfo[j].FilenameExtension);
542 
543         /* Skip the NULL character */
544         c++;
545         sizeRemain -= sizeof(*c);
546 
547         StringCbPrintfExW(c, sizeRemain, &c, &sizeRemain, 0, L"%ls", codecInfo[j].FilenameExtension);
548 
549         /* Skip the NULL character */
550         c++;
551         sizeRemain -= sizeof(*c);
552 
553         if (IsEqualGUID(&rawFormat, &codecInfo[j].FormatID))
554         {
555             sfn.nFilterIndex = j + 1;
556         }
557     }
558 
559     if (GetSaveFileNameW(&sfn) && sfn.nFilterIndex > 0)
560     {
561         Anime_Pause(&pData->m_Anime);
562 
563         if (GdipSaveImageToFile(g_pImage, szSaveFileName, &codecInfo[sfn.nFilterIndex - 1].Clsid, NULL) != Ok)
564         {
565             DPRINT1("GdipSaveImageToFile() failed\n");
566         }
567 
568         Anime_Start(&pData->m_Anime, 0);
569     }
570 
571     QuickFree(szFilterMask);
572     QuickFree(codecInfo);
573 }
574 
575 static VOID
576 Preview_pPrintImage(PPREVIEW_DATA pData)
577 {
578     /* FIXME */
579 }
580 
581 static VOID
582 Preview_UpdateUI(PPREVIEW_DATA pData)
583 {
584     BOOL bEnable = (g_pImage != NULL);
585     PostMessageW(pData->m_hwndToolBar, TB_ENABLEBUTTON, IDC_SAVEAS, bEnable);
586     PostMessageW(pData->m_hwndToolBar, TB_ENABLEBUTTON, IDC_PRINT, bEnable);
587 }
588 
589 static VOID
590 Preview_UpdateImage(PPREVIEW_DATA pData)
591 {
592     if (!Preview_IsMainWnd(pData->m_hwnd))
593         Preview_ResetZoom(pData);
594 
595     ZoomWnd_UpdateScroll(pData, pData->m_hwndZoom, TRUE);
596 }
597 
598 static SHIMGVW_FILENODE*
599 pBuildFileList(LPCWSTR szFirstFile)
600 {
601     HANDLE hFindHandle;
602     WCHAR *extension;
603     WCHAR szSearchPath[MAX_PATH];
604     WCHAR szSearchMask[MAX_PATH];
605     WCHAR szFileTypes[MAX_PATH];
606     WIN32_FIND_DATAW findData;
607     SHIMGVW_FILENODE *currentNode = NULL;
608     SHIMGVW_FILENODE *root = NULL;
609     SHIMGVW_FILENODE *conductor = NULL;
610     ImageCodecInfo *codecInfo;
611     UINT num;
612     UINT size;
613     UINT j;
614 
615     StringCbCopyW(szSearchPath, sizeof(szSearchPath), szFirstFile);
616     PathRemoveFileSpecW(szSearchPath);
617 
618     GdipGetImageDecodersSize(&num, &size);
619     codecInfo = QuickAlloc(size, FALSE);
620     if (!codecInfo)
621     {
622         DPRINT1("QuickAlloc() failed in pLoadFileList()\n");
623         return NULL;
624     }
625 
626     GdipGetImageDecoders(num, size, codecInfo);
627 
628     root = QuickAlloc(sizeof(SHIMGVW_FILENODE), FALSE);
629     if (!root)
630     {
631         DPRINT1("QuickAlloc() failed in pLoadFileList()\n");
632         QuickFree(codecInfo);
633         return NULL;
634     }
635 
636     conductor = root;
637 
638     for (j = 0; j < num; ++j)
639     {
640         StringCbCopyW(szFileTypes, sizeof(szFileTypes), codecInfo[j].FilenameExtension);
641 
642         extension = wcstok(szFileTypes, L";");
643         while (extension != NULL)
644         {
645             PathCombineW(szSearchMask, szSearchPath, extension);
646 
647             hFindHandle = FindFirstFileW(szSearchMask, &findData);
648             if (hFindHandle != INVALID_HANDLE_VALUE)
649             {
650                 do
651                 {
652                     PathCombineW(conductor->FileName, szSearchPath, findData.cFileName);
653 
654                     // compare the name of the requested file with the one currently found.
655                     // if the name matches, the current node is returned by the function.
656                     if (_wcsicmp(szFirstFile, conductor->FileName) == 0)
657                     {
658                         currentNode = conductor;
659                     }
660 
661                     conductor->Next = QuickAlloc(sizeof(SHIMGVW_FILENODE), FALSE);
662 
663                     // if QuickAlloc fails, make circular what we have and return it
664                     if (!conductor->Next)
665                     {
666                         DPRINT1("QuickAlloc() failed in pLoadFileList()\n");
667 
668                         conductor->Next = root;
669                         root->Prev = conductor;
670 
671                         FindClose(hFindHandle);
672                         QuickFree(codecInfo);
673                         return conductor;
674                     }
675 
676                     conductor->Next->Prev = conductor;
677                     conductor = conductor->Next;
678                 }
679                 while (FindNextFileW(hFindHandle, &findData) != 0);
680 
681                 FindClose(hFindHandle);
682             }
683 
684             extension = wcstok(NULL, L";");
685         }
686     }
687 
688     // we now have a node too much in the list. In case the requested file was not found,
689     // we use this node to store the name of it, otherwise we free it.
690     if (currentNode == NULL)
691     {
692         StringCchCopyW(conductor->FileName, MAX_PATH, szFirstFile);
693         currentNode = conductor;
694     }
695     else
696     {
697         conductor = conductor->Prev;
698         QuickFree(conductor->Next);
699     }
700 
701     // link the last node with the first one to make the list circular
702     conductor->Next = root;
703     root->Prev = conductor;
704     conductor = currentNode;
705 
706     QuickFree(codecInfo);
707 
708     return conductor;
709 }
710 
711 static VOID
712 pFreeFileList(SHIMGVW_FILENODE *root)
713 {
714     SHIMGVW_FILENODE *conductor;
715 
716     if (!root)
717         return;
718 
719     root->Prev->Next = NULL;
720     root->Prev = NULL;
721 
722     while (root)
723     {
724         conductor = root;
725         root = conductor->Next;
726         QuickFree(conductor);
727     }
728 }
729 
730 static HBRUSH CreateCheckerBoardBrush(VOID)
731 {
732     static const CHAR pattern[] =
733         "\x28\x00\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x01\x00\x04\x00\x00\x00"
734         "\x00\x00\x80\x00\x00\x00\x23\x2E\x00\x00\x23\x2E\x00\x00\x10\x00\x00\x00"
735         "\x00\x00\x00\x00\x99\x99\x99\x00\xCC\xCC\xCC\x00\x00\x00\x00\x00\x00\x00"
736         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
737         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
738         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11\x11\x11"
739         "\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00"
740         "\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00"
741         "\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11"
742         "\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00"
743         "\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11"
744         "\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11"
745         "\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11";
746 
747     return CreateDIBPatternBrushPt(pattern, DIB_RGB_COLORS);
748 }
749 
750 static VOID
751 ZoomWnd_OnDraw(
752     PPREVIEW_DATA pData,
753     HDC hdc,
754     LPRECT prcPaint,
755     LPRECT prcClient)
756 {
757     GpGraphics *graphics;
758     INT ZoomedWidth, ZoomedHeight;
759     RECT rect, rcClient = *prcClient;
760     HDC hdcMem;
761     HBRUSH hBrush;
762     HPEN hPen;
763     HGDIOBJ hbrOld, hbmOld, hPenOld;
764     UINT uFlags;
765     HBITMAP hbmMem;
766     SIZE paintSize = { prcPaint->right - prcPaint->left, prcPaint->bottom - prcPaint->top };
767     COLORREF color0, color1;
768     GpImageAttributes *imageAttributes;
769 
770     /* We use a memory bitmap to reduce flickering */
771     hdcMem = CreateCompatibleDC(hdc);
772     hbmMem = CreateCompatibleBitmap(hdc, paintSize.cx, paintSize.cy);
773     hbmOld = SelectObject(hdcMem, hbmMem);
774 
775     /* Choose colors */
776     if (Preview_IsMainWnd(pData->m_hwnd))
777     {
778         color0 = GetSysColor(COLOR_WINDOW);
779         color1 = GetSysColor(COLOR_WINDOWTEXT);
780     }
781     else
782     {
783         color0 = RGB(0, 0, 0);
784         color1 = RGB(255, 255, 255);
785     }
786 
787     hBrush = CreateSolidBrush(color0);
788     SetBkColor(hdcMem, color0);
789 
790     hPen = CreatePen(PS_SOLID, 1, color1);
791     SetTextColor(hdcMem, color1);
792 
793     /* Fill background */
794     SetRect(&rect, 0, 0, paintSize.cx, paintSize.cy);
795     FillRect(hdcMem, &rect, hBrush);
796 
797     DeleteObject(hBrush);
798 
799     if (g_pImage == NULL)
800     {
801         WCHAR szText[128];
802         LoadStringW(g_hInstance, IDS_NOPREVIEW, szText, _countof(szText));
803 
804         SelectObject(hdcMem, GetStockFont(DEFAULT_GUI_FONT));
805         OffsetRect(&rcClient, -prcPaint->left, -prcPaint->top);
806         DrawTextW(hdcMem, szText, -1, &rcClient, DT_SINGLELINE | DT_CENTER | DT_VCENTER |
807                                                  DT_NOPREFIX);
808     }
809     else
810     {
811         UINT ImageWidth, ImageHeight;
812 
813         GdipGetImageWidth(g_pImage, &ImageWidth);
814         GdipGetImageHeight(g_pImage, &ImageHeight);
815 
816         ZoomedWidth  = (ImageWidth  * pData->m_nZoomPercents) / 100;
817         ZoomedHeight = (ImageHeight * pData->m_nZoomPercents) / 100;
818 
819         GdipCreateFromHDC(hdcMem, &graphics);
820         if (!graphics)
821         {
822             DPRINT1("error: GdipCreateFromHDC\n");
823             return;
824         }
825 
826         GdipGetImageFlags(g_pImage, &uFlags);
827 
828         if (pData->m_nZoomPercents % 100 == 0)
829         {
830             GdipSetInterpolationMode(graphics, InterpolationModeNearestNeighbor);
831             GdipSetSmoothingMode(graphics, SmoothingModeNone);
832         }
833         else
834         {
835             GdipSetInterpolationMode(graphics, InterpolationModeHighQualityBilinear);
836             GdipSetSmoothingMode(graphics, SmoothingModeHighQuality);
837         }
838 
839         rect.left   = (rcClient.right  - ZoomedWidth ) / 2;
840         rect.top    = (rcClient.bottom - ZoomedHeight) / 2;
841         rect.right  = rect.left + ZoomedWidth;
842         rect.bottom = rect.top  + ZoomedHeight;
843         OffsetRect(&rect,
844                    -prcPaint->left - pData->m_xScrollOffset,
845                    -prcPaint->top  - pData->m_yScrollOffset);
846 
847         InflateRect(&rect, +1, +1); /* Add Rectangle() pen width */
848 
849         /* Draw a rectangle. Fill by checker board if necessary */
850         if (uFlags & (ImageFlagsHasAlpha | ImageFlagsHasTranslucent))
851             hbrOld = SelectObject(hdcMem, CreateCheckerBoardBrush());
852         else
853             hbrOld = SelectObject(hdcMem, GetStockBrush(NULL_BRUSH));
854         hPenOld = SelectObject(hdcMem, hPen);
855         Rectangle(hdcMem, rect.left, rect.top, rect.right, rect.bottom);
856         DeleteObject(SelectObject(hdcMem, hbrOld));
857         DeleteObject(SelectObject(hdcMem, hPenOld));
858 
859         InflateRect(&rect, -1, -1); /* Subtract Rectangle() pen width */
860 
861         /* Image attributes are required to draw image correctly */
862         GdipCreateImageAttributes(&imageAttributes);
863         GdipSetImageAttributesWrapMode(imageAttributes, WrapModeTile,
864                                        GetBkColor(hdcMem) | 0xFF000000, TRUE);
865 
866         /* Draw image. -0.5f is used for interpolation */
867         GdipDrawImageRectRect(graphics, g_pImage,
868                               rect.left, rect.top,
869                               rect.right - rect.left, rect.bottom - rect.top,
870                               -0.5f, -0.5f, ImageWidth, ImageHeight,
871                               UnitPixel, imageAttributes, NULL, NULL);
872 
873         GdipDisposeImageAttributes(imageAttributes);
874         GdipDeleteGraphics(graphics);
875     }
876 
877     BitBlt(hdc, prcPaint->left, prcPaint->top, paintSize.cx, paintSize.cy, hdcMem, 0, 0, SRCCOPY);
878     DeleteObject(SelectObject(hdcMem, hbmOld));
879     DeleteDC(hdcMem);
880 }
881 
882 static VOID
883 ZoomWnd_OnPaint(PPREVIEW_DATA pData, HWND hwnd)
884 {
885     PAINTSTRUCT ps;
886     HDC hDC;
887     RECT rcClient;
888 
889     hDC = BeginPaint(hwnd, &ps);
890     if (hDC)
891     {
892         GetClientRect(hwnd, &rcClient);
893         ZoomWnd_OnDraw(pData, hDC, &ps.rcPaint, &rcClient);
894         EndPaint(hwnd, &ps);
895     }
896 }
897 
898 static VOID
899 ImageView_ResetSettings(VOID)
900 {
901     g_Settings.Maximized = FALSE;
902     g_Settings.X         = CW_USEDEFAULT;
903     g_Settings.Y         = CW_USEDEFAULT;
904     g_Settings.Width     = 520;
905     g_Settings.Height    = 400;
906 }
907 
908 static BOOL
909 ImageView_LoadSettings(VOID)
910 {
911     HKEY hKey;
912     DWORD dwSize;
913     LSTATUS nError;
914 
915     nError = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\ReactOS\\shimgvw", 0, KEY_READ, &hKey);
916     if (nError != ERROR_SUCCESS)
917         return FALSE;
918 
919     dwSize = sizeof(g_Settings);
920     nError = RegQueryValueExW(hKey, L"Settings", NULL, NULL, (LPBYTE)&g_Settings, &dwSize);
921     RegCloseKey(hKey);
922 
923     return ((nError == ERROR_SUCCESS) && (dwSize == sizeof(g_Settings)));
924 }
925 
926 static VOID
927 ImageView_SaveSettings(VOID)
928 {
929     HKEY hKey;
930     LSTATUS nError;
931 
932     nError = RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\ReactOS\\shimgvw",
933                              0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL);
934     if (nError != ERROR_SUCCESS)
935         return;
936 
937     RegSetValueExW(hKey, L"Settings", 0, REG_BINARY, (LPBYTE)&g_Settings, sizeof(g_Settings));
938     RegCloseKey(hKey);
939 }
940 
941 static BOOL
942 Preview_CreateToolBar(PPREVIEW_DATA pData)
943 {
944     HWND hwndToolBar;
945     HIMAGELIST hImageList, hOldImageList;
946     DWORD style = WS_CHILD | WS_VISIBLE | TBSTYLE_FLAT | TBSTYLE_TOOLTIPS;
947 
948     if (!Preview_IsMainWnd(pData->m_hwnd))
949         return TRUE; /* FIXME */
950 
951     style |= CCS_BOTTOM;
952     hwndToolBar = CreateWindowExW(0, TOOLBARCLASSNAMEW, NULL, style,
953                                   0, 0, 0, 0, pData->m_hwnd, NULL, g_hInstance, NULL);
954     if (!hwndToolBar)
955         return FALSE;
956 
957     pData->m_hwndToolBar = hwndToolBar;
958 
959     SendMessageW(hwndToolBar, TB_BUTTONSTRUCTSIZE, sizeof(s_Buttons[0]), 0);
960     SendMessageW(hwndToolBar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_HIDECLIPPEDBUTTONS);
961 
962     hImageList = ImageList_Create(TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, ILC_MASK | ILC_COLOR24, 1, 1);
963     if (hImageList == NULL)
964         return FALSE;
965 
966     for (UINT n = 0; n < _countof(s_ButtonConfig); n++)
967     {
968         HBITMAP hBitmap = LoadBitmapW(g_hInstance, MAKEINTRESOURCEW(s_ButtonConfig[n].idb));
969         ImageList_AddMasked(hImageList, hBitmap, RGB(255, 255, 255));
970         DeleteObject(hBitmap);
971     }
972 
973     hOldImageList = (HIMAGELIST)SendMessageW(hwndToolBar, TB_SETIMAGELIST, 0, (LPARAM)hImageList);
974     ImageList_Destroy(hOldImageList);
975 
976     SendMessageW(hwndToolBar, TB_ADDBUTTONS, _countof(s_Buttons), (LPARAM)s_Buttons);
977 
978     return TRUE;
979 }
980 
981 static VOID
982 Preview_EndSlideShow(HWND hwnd)
983 {
984     if (Preview_IsMainWnd(hwnd))
985         return;
986 
987     KillTimer(hwnd, SLIDESHOW_TIMER_ID);
988     ShowWindow(hwnd, SW_HIDE);
989     ShowWindow(g_hMainWnd, SW_SHOWNORMAL);
990     Preview_ResetZoom(Preview_GetData(g_hMainWnd));
991 }
992 
993 static VOID
994 ZoomWnd_OnButtonDown(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
995 {
996     PPREVIEW_DATA pData = Preview_GetData(hwnd);
997     HWND hParent = GetParent(hwnd);
998     if ((uMsg == WM_LBUTTONDOWN) || (uMsg == WM_RBUTTONDOWN))
999     {
1000         if (!Preview_IsMainWnd(hParent))
1001             Preview_EndSlideShow(hParent);
1002         return;
1003     }
1004 
1005     pData->m_nMouseDownMsg = uMsg;
1006     pData->m_ptOrigin.x = GET_X_LPARAM(lParam);
1007     pData->m_ptOrigin.y = GET_Y_LPARAM(lParam);
1008     SetCapture(hwnd);
1009     SetCursor(LoadCursorW(g_hInstance, MAKEINTRESOURCEW(IDC_HANDDRAG)));
1010 }
1011 
1012 static VOID
1013 ZoomWnd_OnMouseMove(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1014 {
1015     PPREVIEW_DATA pData = Preview_GetData(hwnd);
1016     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
1017 
1018     if (pData->m_nMouseDownMsg == WM_MBUTTONDOWN)
1019     {
1020         INT x = GetScrollPos(hwnd, SB_HORZ) - (pt.x - pData->m_ptOrigin.x);
1021         INT y = GetScrollPos(hwnd, SB_VERT) - (pt.y - pData->m_ptOrigin.y);
1022         SendMessageW(hwnd, WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, x), 0);
1023         SendMessageW(hwnd, WM_VSCROLL, MAKEWPARAM(SB_THUMBPOSITION, y), 0);
1024         pData->m_ptOrigin = pt;
1025     }
1026 }
1027 
1028 static BOOL
1029 ZoomWnd_OnSetCursor(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1030 {
1031     PPREVIEW_DATA pData = Preview_GetData(hwnd);
1032     if (pData->m_nMouseDownMsg == WM_MBUTTONDOWN)
1033     {
1034         SetCursor(LoadCursorW(g_hInstance, MAKEINTRESOURCEW(IDC_HANDDRAG)));
1035         return TRUE;
1036     }
1037     return FALSE;
1038 }
1039 
1040 static VOID
1041 ZoomWnd_OnButtonUp(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1042 {
1043     PPREVIEW_DATA pData = Preview_GetData(hwnd);
1044     pData->m_nMouseDownMsg = 0;
1045     ReleaseCapture();
1046 }
1047 
1048 static VOID
1049 ZoomWnd_OnHVScroll(PPREVIEW_DATA pData, HWND hwnd, WPARAM wParam, BOOL bVertical)
1050 {
1051     UINT ImageWidth, ImageHeight, ZoomedWidth, ZoomedHeight;
1052     RECT rcClient;
1053     UINT nBar = (bVertical ? SB_VERT : SB_HORZ);
1054     SCROLLINFO si = { sizeof(si), SIF_ALL };
1055     GetScrollInfo(hwnd, nBar, &si);
1056 
1057     if (!g_pImage)
1058         return;
1059 
1060     if (bVertical)
1061     {
1062         if (!(GetWindowLongPtrW(hwnd, GWL_STYLE) & WS_VSCROLL))
1063             return;
1064     }
1065     else
1066     {
1067         if (!(GetWindowLongPtrW(hwnd, GWL_STYLE) & WS_HSCROLL))
1068             return;
1069     }
1070 
1071     switch (LOWORD(wParam))
1072     {
1073         case SB_THUMBTRACK:
1074         case SB_THUMBPOSITION:
1075             si.nPos = (SHORT)HIWORD(wParam);
1076             break;
1077         case SB_LINELEFT:
1078             si.nPos -= 48;
1079             break;
1080         case SB_LINERIGHT:
1081             si.nPos += 48;
1082             break;
1083         case SB_PAGELEFT:
1084             si.nPos -= si.nPage;
1085             break;
1086         case SB_PAGERIGHT:
1087             si.nPos += si.nPage;
1088             break;
1089     }
1090 
1091     si.fMask = SIF_POS;
1092     SetScrollInfo(hwnd, nBar, &si, TRUE);
1093     GetScrollInfo(hwnd, nBar, &si);
1094 
1095     GetClientRect(hwnd, &rcClient);
1096 
1097     if (bVertical)
1098     {
1099         GdipGetImageHeight(g_pImage, &ImageHeight);
1100         ZoomedHeight = (ImageHeight * pData->m_nZoomPercents) / 100;
1101         pData->m_yScrollOffset = si.nPos - (ZoomedHeight - rcClient.bottom) / 2;
1102     }
1103     else
1104     {
1105         GdipGetImageWidth(g_pImage, &ImageWidth);
1106         ZoomedWidth = (ImageWidth  * pData->m_nZoomPercents) / 100;
1107         pData->m_xScrollOffset = si.nPos - (ZoomedWidth - rcClient.right) / 2;
1108     }
1109 
1110     InvalidateRect(hwnd, NULL, TRUE);
1111 }
1112 
1113 static VOID
1114 ZoomWnd_OnMouseWheel(HWND hwnd, INT x, INT y, INT zDelta, UINT fwKeys)
1115 {
1116     PPREVIEW_DATA pData = Preview_GetData(hwnd);
1117     if (zDelta == 0)
1118         return;
1119 
1120     if (GetKeyState(VK_CONTROL) < 0)
1121     {
1122         Preview_ZoomInOrOut(pData, zDelta > 0);
1123     }
1124     else if (GetKeyState(VK_SHIFT) < 0)
1125     {
1126         if (zDelta > 0)
1127             SendMessageW(hwnd, WM_HSCROLL, SB_LINELEFT, 0);
1128         else
1129             SendMessageW(hwnd, WM_HSCROLL, SB_LINERIGHT, 0);
1130     }
1131     else
1132     {
1133         if (zDelta > 0)
1134             SendMessageW(hwnd, WM_VSCROLL, SB_LINEUP, 0);
1135         else
1136             SendMessageW(hwnd, WM_VSCROLL, SB_LINEDOWN, 0);
1137     }
1138 }
1139 
1140 LRESULT CALLBACK
1141 ZoomWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1142 {
1143     PPREVIEW_DATA pData = Preview_GetData(hwnd);
1144     switch (uMsg)
1145     {
1146         case WM_LBUTTONDOWN:
1147         case WM_MBUTTONDOWN:
1148         case WM_RBUTTONDOWN:
1149         {
1150             ZoomWnd_OnButtonDown(hwnd, uMsg, wParam, lParam);
1151             break;
1152         }
1153         case WM_MOUSEMOVE:
1154         {
1155             ZoomWnd_OnMouseMove(hwnd, uMsg, wParam, lParam);
1156             break;
1157         }
1158         case WM_SETCURSOR:
1159         {
1160             if (!ZoomWnd_OnSetCursor(hwnd, uMsg, wParam, lParam))
1161                 return DefWindowProcW(hwnd, uMsg, wParam, lParam);
1162         }
1163         case WM_LBUTTONUP:
1164         case WM_MBUTTONUP:
1165         case WM_RBUTTONUP:
1166         {
1167             ZoomWnd_OnButtonUp(hwnd, uMsg, wParam, lParam);
1168             break;
1169         }
1170         case WM_PAINT:
1171         {
1172             ZoomWnd_OnPaint(pData, hwnd);
1173             break;
1174         }
1175         case WM_MOUSEWHEEL:
1176         {
1177             ZoomWnd_OnMouseWheel(hwnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
1178                                  (SHORT)HIWORD(wParam), (UINT)LOWORD(wParam));
1179             break;
1180         }
1181         case WM_HSCROLL:
1182         case WM_VSCROLL:
1183             ZoomWnd_OnHVScroll(pData, hwnd, wParam, uMsg == WM_VSCROLL);
1184             break;
1185         case WM_TIMER:
1186         {
1187             if (Anime_OnTimer(&pData->m_Anime, wParam))
1188                 InvalidateRect(hwnd, NULL, FALSE);
1189             break;
1190         }
1191         default:
1192         {
1193             return DefWindowProcW(hwnd, uMsg, wParam, lParam);
1194         }
1195     }
1196     return 0;
1197 }
1198 
1199 static BOOL
1200 Preview_OnCreate(HWND hwnd, LPCREATESTRUCT pCS)
1201 {
1202     DWORD exstyle = 0;
1203     HWND hwndZoom;
1204     PPREVIEW_DATA pData = QuickAlloc(sizeof(PREVIEW_DATA), TRUE);
1205     pData->m_hwnd = hwnd;
1206     SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)pData);
1207 
1208     DragAcceptFiles(hwnd, TRUE);
1209 
1210     if (g_hMainWnd == NULL)
1211     {
1212         g_hMainWnd = hwnd;
1213         exstyle |= WS_EX_CLIENTEDGE;
1214     }
1215     else if (g_hwndFullscreen == NULL)
1216     {
1217         g_hwndFullscreen = hwnd;
1218     }
1219     else
1220     {
1221         return FALSE;
1222     }
1223 
1224     hwndZoom = CreateWindowExW(exstyle, WC_ZOOM, NULL, WS_CHILD | WS_VISIBLE,
1225                                0, 0, 0, 0, hwnd, NULL, g_hInstance, NULL);
1226     if (!hwndZoom)
1227     {
1228         QuickFree(pData);
1229         return FALSE;
1230     }
1231 
1232     pData->m_hwndZoom = hwndZoom;
1233     SetWindowLongPtrW(hwndZoom, GWLP_USERDATA, (LONG_PTR)pData);
1234     Anime_SetTimerWnd(&pData->m_Anime, pData->m_hwndZoom);
1235 
1236     if (!Preview_CreateToolBar(pData))
1237     {
1238         QuickFree(pData);
1239         return FALSE;
1240     }
1241 
1242     if (pCS && pCS->lpCreateParams)
1243     {
1244         LPCWSTR pszFileName = (LPCWSTR)pCS->lpCreateParams;
1245         WCHAR szFile[MAX_PATH];
1246 
1247         /* Make sure the path has no quotes on it */
1248         StringCchCopyW(szFile, _countof(szFile), pszFileName);
1249         PathUnquoteSpacesW(szFile);
1250 
1251         g_pCurrentFile = pBuildFileList(szFile);
1252         Preview_pLoadImageFromNode(pData, g_pCurrentFile);
1253         Preview_UpdateImage(pData);
1254         Preview_UpdateUI(pData);
1255     }
1256 
1257     return TRUE;
1258 }
1259 
1260 static VOID
1261 Preview_OnMoveSize(HWND hwnd)
1262 {
1263     WINDOWPLACEMENT wp;
1264     RECT *prc;
1265 
1266     if (IsIconic(hwnd) || !Preview_IsMainWnd(hwnd))
1267         return;
1268 
1269     wp.length = sizeof(WINDOWPLACEMENT);
1270     GetWindowPlacement(hwnd, &wp);
1271 
1272     /* Remember window position and size */
1273     prc = &wp.rcNormalPosition;
1274     g_Settings.X = prc->left;
1275     g_Settings.Y = prc->top;
1276     g_Settings.Width = prc->right - prc->left;
1277     g_Settings.Height = prc->bottom - prc->top;
1278     g_Settings.Maximized = IsZoomed(hwnd);
1279 }
1280 
1281 static VOID
1282 Preview_OnSize(HWND hwnd)
1283 {
1284     RECT rc, rcClient;
1285     PPREVIEW_DATA pData = Preview_GetData(hwnd);
1286     HWND hToolBar = pData->m_hwndToolBar;
1287     INT cx, cy;
1288 
1289     /* We want 32-bit values. Don't use WM_SIZE lParam */
1290     GetClientRect(hwnd, &rcClient);
1291     cx = rcClient.right;
1292     cy = rcClient.bottom;
1293 
1294     if (Preview_IsMainWnd(pData->m_hwnd))
1295     {
1296         SendMessageW(hToolBar, TB_AUTOSIZE, 0, 0);
1297         GetWindowRect(hToolBar, &rc);
1298 
1299         MoveWindow(pData->m_hwndZoom, 0, 0, cx, cy - (rc.bottom - rc.top), TRUE);
1300 
1301         if (!IsIconic(hwnd)) /* Is it not minimized? */
1302             Preview_ResetZoom(pData);
1303 
1304         Preview_OnMoveSize(hwnd);
1305     }
1306     else
1307     {
1308         MoveWindow(pData->m_hwndZoom, 0, 0, cx, cy, TRUE);
1309     }
1310 }
1311 
1312 static VOID
1313 Preview_Delete(PPREVIEW_DATA pData)
1314 {
1315     WCHAR szCurFile[MAX_PATH + 1], szNextFile[MAX_PATH];
1316     HWND hwnd = pData->m_hwnd;
1317     SHFILEOPSTRUCTW FileOp = { hwnd, FO_DELETE };
1318 
1319     if (!pData->m_szFile[0])
1320         return;
1321 
1322     /* FileOp.pFrom must be double-null-terminated */
1323     GetFullPathNameW(pData->m_szFile, _countof(szCurFile) - 1, szCurFile, NULL);
1324     szCurFile[_countof(szCurFile) - 2] = UNICODE_NULL; /* Avoid buffer overrun */
1325     szCurFile[lstrlenW(szCurFile) + 1] = UNICODE_NULL;
1326 
1327     szNextFile[0] = UNICODE_NULL;
1328     if (g_pCurrentFile)
1329     {
1330         GetFullPathNameW(g_pCurrentFile->Next->FileName, _countof(szNextFile), szNextFile, NULL);
1331         szNextFile[_countof(szNextFile) - 1] = UNICODE_NULL; /* Avoid buffer overrun */
1332     }
1333 
1334     /* Confirm file deletion and delete if allowed */
1335     FileOp.pFrom = szCurFile;
1336     FileOp.fFlags = FOF_ALLOWUNDO;
1337     if (SHFileOperationW(&FileOp) != 0)
1338     {
1339         DPRINT("Preview_Delete: SHFileOperationW() failed or canceled\n");
1340         return;
1341     }
1342 
1343     /* Reload the file list and go next file */
1344     pFreeFileList(g_pCurrentFile);
1345     g_pCurrentFile = pBuildFileList(szNextFile);
1346     Preview_pLoadImageFromNode(pData, g_pCurrentFile);
1347 }
1348 
1349 static VOID
1350 Preview_Edit(HWND hwnd)
1351 {
1352     SHELLEXECUTEINFOW sei;
1353     PPREVIEW_DATA pData = Preview_GetData(hwnd);
1354 
1355     if (!pData->m_szFile[0])
1356         return;
1357 
1358     ZeroMemory(&sei, sizeof(sei));
1359     sei.cbSize = sizeof(sei);
1360     sei.lpVerb = L"edit";
1361     sei.lpFile = pData->m_szFile;
1362     sei.nShow = SW_SHOWNORMAL;
1363     if (!ShellExecuteExW(&sei))
1364     {
1365         DPRINT1("Preview_Edit: ShellExecuteExW() failed with code %ld\n", GetLastError());
1366     }
1367     else
1368     {
1369         // Destroy the window to quit the application
1370         DestroyWindow(hwnd);
1371     }
1372 }
1373 
1374 static VOID
1375 Preview_ToggleSlideShow(PPREVIEW_DATA pData)
1376 {
1377     if (!IsWindow(g_hwndFullscreen))
1378     {
1379         DWORD style = WS_POPUP | WS_CLIPSIBLINGS, exstyle = WS_EX_TOPMOST;
1380         WCHAR szTitle[256];
1381         LoadStringW(g_hInstance, IDS_APPTITLE, szTitle, _countof(szTitle));
1382         g_hwndFullscreen = CreateWindowExW(exstyle, WC_PREVIEW, szTitle, style,
1383                                            0, 0, 0, 0, NULL, NULL, g_hInstance, NULL);
1384     }
1385 
1386     if (IsWindowVisible(g_hwndFullscreen))
1387     {
1388         ShowWindow(g_hwndFullscreen, SW_HIDE);
1389         ShowWindow(g_hMainWnd, SW_SHOWNORMAL);
1390         KillTimer(g_hwndFullscreen, SLIDESHOW_TIMER_ID);
1391     }
1392     else
1393     {
1394         ShowWindow(g_hMainWnd, SW_HIDE);
1395         ShowWindow(g_hwndFullscreen, SW_SHOWMAXIMIZED);
1396         Preview_RestartTimer(g_hwndFullscreen);
1397     }
1398 }
1399 
1400 static VOID
1401 Preview_GoNextPic(PPREVIEW_DATA pData, BOOL bNext)
1402 {
1403     Preview_RestartTimer(pData->m_hwnd);
1404     if (g_pCurrentFile)
1405     {
1406         if (bNext)
1407             g_pCurrentFile = g_pCurrentFile->Next;
1408         else
1409             g_pCurrentFile = g_pCurrentFile->Prev;
1410         Preview_pLoadImageFromNode(pData, g_pCurrentFile);
1411         Preview_UpdateImage(pData);
1412         Preview_UpdateUI(pData);
1413     }
1414 }
1415 
1416 static VOID
1417 Preview_OnCommand(HWND hwnd, UINT nCommandID)
1418 {
1419     PPREVIEW_DATA pData = Preview_GetData(hwnd);
1420 
1421     switch (nCommandID)
1422     {
1423         case IDC_PREV_PIC:
1424             Preview_GoNextPic(pData, FALSE);
1425             break;
1426 
1427         case IDC_NEXT_PIC:
1428             Preview_GoNextPic(pData, TRUE);
1429             break;
1430 
1431         case IDC_BEST_FIT:
1432             Preview_ResetZoom(pData);
1433             break;
1434 
1435         case IDC_REAL_SIZE:
1436             Preview_UpdateZoom(pData, 100, TRUE, FALSE);
1437             break;
1438 
1439         case IDC_SLIDE_SHOW:
1440             Preview_ToggleSlideShow(pData);
1441             break;
1442 
1443         case IDC_ZOOM_IN:
1444             Preview_ZoomInOrOut(pData, TRUE);
1445             break;
1446 
1447         case IDC_ZOOM_OUT:
1448             Preview_ZoomInOrOut(pData, FALSE);
1449             break;
1450 
1451         case IDC_ENDSLIDESHOW:
1452             Preview_EndSlideShow(hwnd);
1453             break;
1454 
1455         default:
1456             break;
1457     }
1458 
1459     if (!Preview_IsMainWnd(hwnd))
1460         return;
1461 
1462     // The following commands are for main window only:
1463     switch (nCommandID)
1464     {
1465         case IDC_SAVEAS:
1466             Preview_pSaveImageAs(pData);
1467             break;
1468 
1469         case IDC_PRINT:
1470             Preview_pPrintImage(pData);
1471             break;
1472 
1473         case IDC_ROT_CLOCKW:
1474             if (g_pImage)
1475             {
1476                 GdipImageRotateFlip(g_pImage, Rotate270FlipNone);
1477                 Preview_UpdateImage(pData);
1478             }
1479             break;
1480 
1481         case IDC_ROT_COUNCW:
1482             if (g_pImage)
1483             {
1484                 GdipImageRotateFlip(g_pImage, Rotate90FlipNone);
1485                 Preview_UpdateImage(pData);
1486             }
1487             break;
1488 
1489         case IDC_ROT_CWSAVE:
1490             if (g_pImage)
1491             {
1492                 GdipImageRotateFlip(g_pImage, Rotate270FlipNone);
1493                 Preview_pSaveImage(pData, pData->m_szFile);
1494                 Preview_UpdateImage(pData);
1495             }
1496             break;
1497 
1498         case IDC_ROT_CCWSAVE:
1499             if (g_pImage)
1500             {
1501                 GdipImageRotateFlip(g_pImage, Rotate90FlipNone);
1502                 Preview_pSaveImage(pData, pData->m_szFile);
1503                 Preview_UpdateImage(pData);
1504             }
1505             break;
1506 
1507         case IDC_DELETE:
1508             Preview_Delete(pData);
1509             Preview_UpdateImage(pData);
1510             Preview_UpdateUI(pData);
1511             break;
1512 
1513         case IDC_MODIFY:
1514             Preview_Edit(hwnd);
1515             break;
1516 
1517         default:
1518             break;
1519     }
1520 }
1521 
1522 static LRESULT
1523 Preview_OnNotify(HWND hwnd, LPNMHDR pnmhdr)
1524 {
1525     switch (pnmhdr->code)
1526     {
1527         case TTN_GETDISPINFOW:
1528         {
1529             LPTOOLTIPTEXTW lpttt = (LPTOOLTIPTEXTW)pnmhdr;
1530             lpttt->hinst = g_hInstance;
1531             lpttt->lpszText = MAKEINTRESOURCEW(s_ButtonConfig[lpttt->hdr.idFrom - IDC_TOOL_BASE].ids);
1532             break;
1533         }
1534     }
1535     return 0;
1536 }
1537 
1538 static VOID
1539 Preview_OnDestroy(HWND hwnd)
1540 {
1541     PPREVIEW_DATA pData = Preview_GetData(hwnd);
1542 
1543     KillTimer(hwnd, SLIDESHOW_TIMER_ID);
1544 
1545     pFreeFileList(g_pCurrentFile);
1546     g_pCurrentFile = NULL;
1547 
1548     Preview_pFreeImage(pData);
1549 
1550     SetWindowLongPtrW(pData->m_hwndZoom, GWLP_USERDATA, 0);
1551     DestroyWindow(pData->m_hwndZoom);
1552     pData->m_hwndZoom = NULL;
1553 
1554     DestroyWindow(pData->m_hwndToolBar);
1555     pData->m_hwndToolBar = NULL;
1556 
1557     SetWindowLongPtrW(pData->m_hwnd, GWLP_USERDATA, 0);
1558     QuickFree(pData);
1559 
1560     PostQuitMessage(0);
1561 }
1562 
1563 static VOID
1564 Preview_OnDropFiles(HWND hwnd, HDROP hDrop)
1565 {
1566     WCHAR szFile[MAX_PATH];
1567     PPREVIEW_DATA pData = Preview_GetData(hwnd);
1568 
1569     DragQueryFileW(hDrop, 0, szFile, _countof(szFile));
1570 
1571     pFreeFileList(g_pCurrentFile);
1572     g_pCurrentFile = pBuildFileList(szFile);
1573     Preview_pLoadImageFromNode(pData, g_pCurrentFile);
1574 
1575     DragFinish(hDrop);
1576 }
1577 
1578 LRESULT CALLBACK
1579 PreviewWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1580 {
1581     switch (uMsg)
1582     {
1583         case WM_CREATE:
1584         {
1585             if (!Preview_OnCreate(hwnd, (LPCREATESTRUCT)lParam))
1586                 return -1;
1587             break;
1588         }
1589         case WM_COMMAND:
1590         {
1591             Preview_OnCommand(hwnd, LOWORD(wParam));
1592             break;
1593         }
1594         case WM_NOTIFY:
1595         {
1596             return Preview_OnNotify(hwnd, (LPNMHDR)lParam);
1597         }
1598         case WM_GETMINMAXINFO:
1599         {
1600             MINMAXINFO *pMMI = (MINMAXINFO*)lParam;
1601             pMMI->ptMinTrackSize.x = 350;
1602             pMMI->ptMinTrackSize.y = 290;
1603             break;
1604         }
1605         case WM_MOVE:
1606         {
1607             Preview_OnMoveSize(hwnd);
1608             break;
1609         }
1610         case WM_SIZE:
1611         {
1612             Preview_OnSize(hwnd);
1613             break;
1614         }
1615         case WM_DROPFILES:
1616         {
1617             Preview_OnDropFiles(hwnd, (HDROP)wParam);
1618             break;
1619         }
1620         case WM_SYSCOLORCHANGE:
1621         {
1622             PPREVIEW_DATA pData = Preview_GetData(hwnd);
1623             InvalidateRect(pData->m_hwnd, NULL, TRUE);
1624             InvalidateRect(pData->m_hwndZoom, NULL, TRUE);
1625             break;
1626         }
1627         case WM_DESTROY:
1628         {
1629             Preview_OnDestroy(hwnd);
1630             break;
1631         }
1632         case WM_TIMER:
1633         {
1634             if (wParam == SLIDESHOW_TIMER_ID)
1635             {
1636                 PPREVIEW_DATA pData = Preview_GetData(hwnd);
1637                 Preview_GoNextPic(pData, TRUE);
1638             }
1639             break;
1640         }
1641         default:
1642         {
1643             return DefWindowProcW(hwnd, uMsg, wParam, lParam);
1644         }
1645     }
1646 
1647     return 0;
1648 }
1649 
1650 LONG
1651 ImageView_Main(HWND hwnd, LPCWSTR szFileName)
1652 {
1653     struct GdiplusStartupInput gdiplusStartupInput;
1654     ULONG_PTR gdiplusToken;
1655     WNDCLASSW WndClass;
1656     WCHAR szTitle[256];
1657     HWND hMainWnd;
1658     MSG msg;
1659     HACCEL hAccel;
1660     HRESULT hrCoInit;
1661     INITCOMMONCONTROLSEX Icc = { .dwSize = sizeof(Icc), .dwICC = ICC_WIN95_CLASSES };
1662 
1663     InitCommonControlsEx(&Icc);
1664 
1665     /* Initialize COM */
1666     hrCoInit = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
1667     if (FAILED(hrCoInit))
1668         DPRINT1("Warning, CoInitializeEx failed with code=%08X\n", (int)hrCoInit);
1669 
1670     if (!ImageView_LoadSettings())
1671         ImageView_ResetSettings();
1672 
1673     /* Initialize GDI+ */
1674     ZeroMemory(&gdiplusStartupInput, sizeof(gdiplusStartupInput));
1675     gdiplusStartupInput.GdiplusVersion = 1;
1676     GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
1677 
1678     /* Register window classes */
1679     ZeroMemory(&WndClass, sizeof(WndClass));
1680     WndClass.lpszClassName  = WC_PREVIEW;
1681     WndClass.lpfnWndProc    = PreviewWndProc;
1682     WndClass.hInstance      = g_hInstance;
1683     WndClass.style          = CS_HREDRAW | CS_VREDRAW;
1684     WndClass.hIcon          = LoadIconW(g_hInstance, MAKEINTRESOURCEW(IDI_APP_ICON));
1685     WndClass.hCursor        = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
1686     WndClass.hbrBackground  = (HBRUSH)UlongToHandle(COLOR_3DFACE + 1);
1687     if (!RegisterClassW(&WndClass))
1688         return -1;
1689     WndClass.lpszClassName  = WC_ZOOM;
1690     WndClass.lpfnWndProc    = ZoomWndProc;
1691     WndClass.style          = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
1692     WndClass.hbrBackground  = GetStockBrush(NULL_BRUSH); /* less flicker */
1693     if (!RegisterClassW(&WndClass))
1694         return -1;
1695 
1696     /* Create the main window */
1697     LoadStringW(g_hInstance, IDS_APPTITLE, szTitle, _countof(szTitle));
1698     hMainWnd = CreateWindowExW(WS_EX_WINDOWEDGE, WC_PREVIEW, szTitle,
1699                                WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS,
1700                                g_Settings.X, g_Settings.Y, g_Settings.Width, g_Settings.Height,
1701                                NULL, NULL, g_hInstance, (LPVOID)szFileName);
1702 
1703     /* Create accelerator table for keystrokes */
1704     hAccel = LoadAcceleratorsW(g_hInstance, MAKEINTRESOURCEW(IDR_ACCELERATOR));
1705 
1706     /* Show the main window now */
1707     if (g_Settings.Maximized)
1708         ShowWindow(hMainWnd, SW_SHOWMAXIMIZED);
1709     else
1710         ShowWindow(hMainWnd, SW_SHOWNORMAL);
1711 
1712     UpdateWindow(hMainWnd);
1713 
1714     /* Message Loop */
1715     while (GetMessageW(&msg, NULL, 0, 0) > 0)
1716     {
1717         if (g_hwndFullscreen && TranslateAcceleratorW(g_hwndFullscreen, hAccel, &msg))
1718             continue;
1719         if (TranslateAcceleratorW(hMainWnd, hAccel, &msg))
1720             continue;
1721 
1722         TranslateMessage(&msg);
1723         DispatchMessageW(&msg);
1724     }
1725 
1726     /* Destroy accelerator table */
1727     DestroyAcceleratorTable(hAccel);
1728 
1729     ImageView_SaveSettings();
1730 
1731     GdiplusShutdown(gdiplusToken);
1732 
1733     /* Release COM resources */
1734     if (SUCCEEDED(hrCoInit))
1735         CoUninitialize();
1736 
1737     return 0;
1738 }
1739 
1740 VOID WINAPI
1741 ImageView_FullscreenW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1742 {
1743     ImageView_Main(hwnd, path);
1744 }
1745 
1746 VOID WINAPI
1747 ImageView_Fullscreen(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1748 {
1749     ImageView_Main(hwnd, path);
1750 }
1751 
1752 VOID WINAPI
1753 ImageView_FullscreenA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow)
1754 {
1755     WCHAR szFile[MAX_PATH];
1756 
1757     if (MultiByteToWideChar(CP_ACP, 0, path, -1, szFile, _countof(szFile)))
1758     {
1759         ImageView_Main(hwnd, szFile);
1760     }
1761 }
1762 
1763 VOID WINAPI
1764 ImageView_PrintTo(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1765 {
1766     DPRINT("ImageView_PrintTo() not implemented\n");
1767 }
1768 
1769 VOID WINAPI
1770 ImageView_PrintToA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow)
1771 {
1772     DPRINT("ImageView_PrintToA() not implemented\n");
1773 }
1774 
1775 VOID WINAPI
1776 ImageView_PrintToW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1777 {
1778     DPRINT("ImageView_PrintToW() not implemented\n");
1779 }
1780 
1781 BOOL WINAPI
1782 DllMain(IN HINSTANCE hinstDLL,
1783         IN DWORD dwReason,
1784         IN LPVOID lpvReserved)
1785 {
1786     switch (dwReason)
1787     {
1788         case DLL_PROCESS_ATTACH:
1789             g_hInstance = hinstDLL;
1790             break;
1791     }
1792 
1793     return TRUE;
1794 }
1795