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