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