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