xref: /reactos/dll/win32/shimgvw/shimgvw.c (revision 34593d93)
1 /*
2  * PROJECT:         ReactOS Picture and Fax Viewer
3  * FILE:            dll/win32/shimgvw/shimgvw.c
4  * PURPOSE:         shimgvw.dll
5  * PROGRAMMERS:     Dmitry Chapyshev (dmitry@reactos.org)
6  *                  Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
7  */
8 
9 #define WIN32_NO_STATUS
10 #define _INC_WINDOWS
11 #define COM_NO_WINDOWS_H
12 #define INITGUID
13 
14 #include <stdarg.h>
15 
16 #include <windef.h>
17 #include <winbase.h>
18 #include <winnls.h>
19 #include <winreg.h>
20 #include <wingdi.h>
21 #include <wincon.h>
22 #include <windowsx.h>
23 #include <objbase.h>
24 #include <commctrl.h>
25 #include <commdlg.h>
26 #include <gdiplus.h>
27 #include <tchar.h>
28 #include <shlobj.h>
29 #include <strsafe.h>
30 #include <shlwapi.h>
31 #include <shellapi.h>
32 
33 #define NDEBUG
34 #include <debug.h>
35 
36 #include "shimgvw.h"
37 
38 HINSTANCE hInstance;
39 SHIMGVW_SETTINGS shiSettings;
40 SHIMGVW_FILENODE *currentFile;
41 GpImage *image = NULL;
42 WNDPROC PrevProc = NULL;
43 
44 HWND hDispWnd, hToolBar;
45 
46 /* zooming */
47 UINT ZoomPercents = 100;
48 
49 static const UINT ZoomSteps[] =
50 {
51     10, 25, 50, 100, 200, 400, 800, 1600
52 };
53 
54 #define MIN_ZOOM ZoomSteps[0]
55 #define MAX_ZOOM ZoomSteps[_countof(ZoomSteps)-1]
56 
57 /* ToolBar Buttons */
58 typedef struct {
59     DWORD idb;  /* Index to bitmap */
60     DWORD ids;  /* Index to tooltip */
61 } TB_BUTTON_CONFIG;
62 
63     /* iBitmap,       idCommand,   fsState,         fsStyle,     bReserved[2], dwData, iString */
64 #define DEFINE_BTN_INFO(_name) \
65     { TBICON_##_name, IDC_##_name, TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0 }
66 
67 #define DEFINE_BTN_SEPARATOR \
68     { 15,             0,           TBSTATE_ENABLED, BTNS_SEP,    {0}, 0, 0 }
69 
70 /* ToolBar Buttons */
71 static const TBBUTTON Buttons[] =
72 {
73     DEFINE_BTN_INFO(PREV_PIC),
74     DEFINE_BTN_INFO(NEXT_PIC),
75     DEFINE_BTN_SEPARATOR,
76     DEFINE_BTN_INFO(BEST_FIT),
77     DEFINE_BTN_INFO(REAL_SIZE),
78     DEFINE_BTN_INFO(SLIDE_SHOW),
79     DEFINE_BTN_SEPARATOR,
80     DEFINE_BTN_INFO(ZOOM_IN),
81     DEFINE_BTN_INFO(ZOOM_OUT),
82     DEFINE_BTN_SEPARATOR,
83     DEFINE_BTN_INFO(ROT_CLOCKW),
84     DEFINE_BTN_INFO(ROT_COUNCW),
85     DEFINE_BTN_SEPARATOR,
86     DEFINE_BTN_INFO(DELETE),
87     DEFINE_BTN_INFO(PRINT),
88     DEFINE_BTN_INFO(SAVEAS),
89     DEFINE_BTN_INFO(MODIFY),
90     DEFINE_BTN_SEPARATOR,
91     DEFINE_BTN_INFO(HELP_TOC)
92 };
93 
94 #define DEFINE_BTN_CONFIG(_name) { IDB_##_name, IDS_TOOLTIP_##_name }
95 
96 static const TB_BUTTON_CONFIG BtnConfig[] =
97 {
98     DEFINE_BTN_CONFIG(PREV_PIC),
99     DEFINE_BTN_CONFIG(NEXT_PIC),
100     DEFINE_BTN_CONFIG(BEST_FIT),
101     DEFINE_BTN_CONFIG(REAL_SIZE),
102     DEFINE_BTN_CONFIG(SLIDE_SHOW),
103     DEFINE_BTN_CONFIG(ZOOM_IN),
104     DEFINE_BTN_CONFIG(ZOOM_OUT),
105     DEFINE_BTN_CONFIG(ROT_CLOCKW),
106     DEFINE_BTN_CONFIG(ROT_COUNCW),
107     DEFINE_BTN_CONFIG(DELETE),
108     DEFINE_BTN_CONFIG(PRINT),
109     DEFINE_BTN_CONFIG(SAVEAS),
110     DEFINE_BTN_CONFIG(MODIFY),
111     DEFINE_BTN_CONFIG(HELP_TOC)
112 };
113 
114 /* animation */
115 UINT            m_nFrameIndex = 0;
116 UINT            m_nFrameCount = 0;
117 UINT            m_nLoopIndex = 0;
118 UINT            m_nLoopCount = (UINT)-1;
119 PropertyItem   *m_pDelayItem = NULL;
120 
121 #define ANIME_TIMER_ID  9999
122 
123 static void Anime_FreeInfo(void)
124 {
125     if (m_pDelayItem)
126     {
127         free(m_pDelayItem);
128         m_pDelayItem = NULL;
129     }
130     m_nFrameIndex = 0;
131     m_nFrameCount = 0;
132     m_nLoopIndex = 0;
133     m_nLoopCount = (UINT)-1;
134 }
135 
136 static BOOL Anime_LoadInfo(void)
137 {
138     GUID *dims;
139     UINT nDimCount = 0;
140     UINT cbItem;
141     UINT result;
142     PropertyItem *pItem;
143 
144     Anime_FreeInfo();
145     KillTimer(hDispWnd, ANIME_TIMER_ID);
146 
147     if (!image)
148         return FALSE;
149 
150     GdipImageGetFrameDimensionsCount(image, &nDimCount);
151     if (nDimCount)
152     {
153         dims = (GUID *)calloc(nDimCount, sizeof(GUID));
154         if (dims)
155         {
156             GdipImageGetFrameDimensionsList(image, dims, nDimCount);
157             GdipImageGetFrameCount(image, dims, &result);
158             m_nFrameCount = result;
159             free(dims);
160         }
161     }
162 
163     result = 0;
164     GdipGetPropertyItemSize(image, PropertyTagFrameDelay, &result);
165     cbItem = result;
166     if (cbItem)
167     {
168         m_pDelayItem = (PropertyItem *)malloc(cbItem);
169         GdipGetPropertyItem(image, PropertyTagFrameDelay, cbItem, m_pDelayItem);
170     }
171 
172     result = 0;
173     GdipGetPropertyItemSize(image, PropertyTagLoopCount, &result);
174     cbItem = result;
175     if (cbItem)
176     {
177         pItem = (PropertyItem *)malloc(cbItem);
178         if (pItem)
179         {
180             if (GdipGetPropertyItem(image, PropertyTagLoopCount, cbItem, pItem) == Ok)
181             {
182                 m_nLoopCount = *(WORD *)pItem->value;
183             }
184             free(pItem);
185         }
186     }
187 
188     if (m_pDelayItem)
189     {
190         SetTimer(hDispWnd, ANIME_TIMER_ID, 0, NULL);
191     }
192 
193     return m_pDelayItem != NULL;
194 }
195 
196 static void Anime_SetFrameIndex(UINT nFrameIndex)
197 {
198     if (nFrameIndex < m_nFrameCount)
199     {
200         GUID guid = FrameDimensionTime;
201         if (Ok != GdipImageSelectActiveFrame(image, &guid, nFrameIndex))
202         {
203             guid = FrameDimensionPage;
204             GdipImageSelectActiveFrame(image, &guid, nFrameIndex);
205         }
206     }
207     m_nFrameIndex = nFrameIndex;
208 }
209 
210 DWORD Anime_GetFrameDelay(UINT nFrameIndex)
211 {
212     if (nFrameIndex < m_nFrameCount && m_pDelayItem)
213     {
214         return ((DWORD *)m_pDelayItem->value)[m_nFrameIndex] * 10;
215     }
216     return 0;
217 }
218 
219 BOOL Anime_Step(DWORD *pdwDelay)
220 {
221     *pdwDelay = INFINITE;
222     if (m_nLoopCount == (UINT)-1)
223         return FALSE;
224 
225     if (m_nFrameIndex + 1 < m_nFrameCount)
226     {
227         *pdwDelay = Anime_GetFrameDelay(m_nFrameIndex);
228         Anime_SetFrameIndex(m_nFrameIndex);
229         ++m_nFrameIndex;
230         return TRUE;
231     }
232 
233     if (m_nLoopCount == 0 || m_nLoopIndex < m_nLoopCount)
234     {
235         *pdwDelay = Anime_GetFrameDelay(m_nFrameIndex);
236         Anime_SetFrameIndex(m_nFrameIndex);
237         m_nFrameIndex = 0;
238         ++m_nLoopIndex;
239         return TRUE;
240     }
241 
242     return FALSE;
243 }
244 
245 static void UpdateZoom(UINT NewZoom)
246 {
247     BOOL bEnableZoomIn, bEnableZoomOut;
248 
249     /* If zoom has not been changed, ignore it */
250     if (ZoomPercents == NewZoom)
251         return;
252 
253     ZoomPercents = NewZoom;
254 
255     /* Check if a zoom button of the toolbar must be grayed */
256     bEnableZoomIn = bEnableZoomOut = TRUE;
257 
258     if (NewZoom >= MAX_ZOOM)
259     {
260         bEnableZoomIn = FALSE;
261     }
262     else if (NewZoom <= MIN_ZOOM)
263     {
264         bEnableZoomOut = FALSE;
265     }
266 
267     /* Update the state of the zoom buttons */
268     SendMessageW(hToolBar, TB_ENABLEBUTTON, IDC_ZOOM_OUT, bEnableZoomOut);
269     SendMessageW(hToolBar, TB_ENABLEBUTTON, IDC_ZOOM_IN,  bEnableZoomIn);
270 
271     /* Redraw the display window */
272     InvalidateRect(hDispWnd, NULL, FALSE);
273 }
274 
275 static void ZoomInOrOut(BOOL bZoomIn)
276 {
277     UINT i, NewZoom;
278 
279     if (image == NULL)
280         return;
281 
282     if (bZoomIn)    /* zoom in */
283     {
284         /* find next step */
285         for (i = 0; i < _countof(ZoomSteps); ++i)
286         {
287             if (ZoomPercents < ZoomSteps[i])
288                 break;
289         }
290         if (i == _countof(ZoomSteps))
291             NewZoom = MAX_ZOOM;
292         else
293             NewZoom = ZoomSteps[i];
294     }
295     else            /* zoom out */
296     {
297         /* find previous step */
298         for (i = _countof(ZoomSteps); i > 0; )
299         {
300             --i;
301             if (ZoomSteps[i] < ZoomPercents)
302                 break;
303         }
304         if (i < 0)
305             NewZoom = MIN_ZOOM;
306         else
307             NewZoom = ZoomSteps[i];
308     }
309 
310     /* Update toolbar and refresh screen */
311     UpdateZoom(NewZoom);
312 }
313 
314 static void ResetZoom(void)
315 {
316     RECT Rect;
317     UINT ImageWidth, ImageHeight, NewZoom;
318 
319     if (image == NULL)
320         return;
321 
322     /* get disp window size and image size */
323     GetClientRect(hDispWnd, &Rect);
324     GdipGetImageWidth(image, &ImageWidth);
325     GdipGetImageHeight(image, &ImageHeight);
326 
327     /* compare two aspect rates. same as
328        (ImageHeight / ImageWidth < Rect.bottom / Rect.right) in real */
329     if (ImageHeight * Rect.right < Rect.bottom * ImageWidth)
330     {
331         if (Rect.right < ImageWidth)
332         {
333             /* it's large, shrink it */
334             NewZoom = (Rect.right * 100) / ImageWidth;
335         }
336         else
337         {
338             /* it's small. show as original size */
339             NewZoom = 100;
340         }
341     }
342     else
343     {
344         if (Rect.bottom < ImageHeight)
345         {
346             /* it's large, shrink it */
347             NewZoom = (Rect.bottom * 100) / ImageHeight;
348         }
349         else
350         {
351             /* it's small. show as original size */
352             NewZoom = 100;
353         }
354     }
355 
356     UpdateZoom(NewZoom);
357 }
358 
359 static void pLoadImage(LPCWSTR szOpenFileName)
360 {
361     /* check file presence */
362     if (GetFileAttributesW(szOpenFileName) == 0xFFFFFFFF)
363     {
364         DPRINT1("File %s not found!\n", szOpenFileName);
365         return;
366     }
367 
368     /* load now */
369     GdipLoadImageFromFile(szOpenFileName, &image);
370     if (!image)
371     {
372         DPRINT1("GdipLoadImageFromFile() failed\n");
373         return;
374     }
375     Anime_LoadInfo();
376 
377     if (szOpenFileName && szOpenFileName[0])
378         SHAddToRecentDocs(SHARD_PATHW, szOpenFileName);
379 
380     /* Reset zoom and redraw display */
381     ResetZoom();
382 }
383 
384 static void pSaveImageAs(HWND hwnd)
385 {
386     OPENFILENAMEW sfn;
387     ImageCodecInfo *codecInfo;
388     WCHAR szSaveFileName[MAX_PATH];
389     WCHAR *szFilterMask;
390     GUID rawFormat;
391     UINT num;
392     UINT size;
393     size_t sizeRemain;
394     UINT j;
395     WCHAR *c;
396 
397     if (image == NULL)
398         return;
399 
400     GdipGetImageEncodersSize(&num, &size);
401     codecInfo = malloc(size);
402     if (!codecInfo)
403     {
404         DPRINT1("malloc() failed in pSaveImageAs()\n");
405         return;
406     }
407 
408     GdipGetImageEncoders(num, size, codecInfo);
409     GdipGetImageRawFormat(image, &rawFormat);
410 
411     sizeRemain = 0;
412 
413     for (j = 0; j < num; ++j)
414     {
415         // 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.
416         sizeRemain = sizeRemain + (((wcslen(codecInfo[j].FormatDescription) + (wcslen(codecInfo[j].FilenameExtension) * 2) + 5) * sizeof(WCHAR)));
417     }
418 
419     /* Add two more chars for the last terminator */
420     sizeRemain = sizeRemain + (sizeof(WCHAR) * 2);
421 
422     szFilterMask = malloc(sizeRemain);
423     if (!szFilterMask)
424     {
425         DPRINT1("cannot allocate memory for filter mask in pSaveImageAs()");
426         free(codecInfo);
427         return;
428     }
429 
430     ZeroMemory(szSaveFileName, sizeof(szSaveFileName));
431     ZeroMemory(szFilterMask, sizeRemain);
432     ZeroMemory(&sfn, sizeof(sfn));
433     sfn.lStructSize = sizeof(sfn);
434     sfn.hwndOwner   = hwnd;
435     sfn.hInstance   = hInstance;
436     sfn.lpstrFile   = szSaveFileName;
437     sfn.lpstrFilter = szFilterMask;
438     sfn.nMaxFile    = MAX_PATH;
439     sfn.Flags       = OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
440 
441     c = szFilterMask;
442 
443     for (j = 0; j < num; ++j)
444     {
445         StringCbPrintfExW(c, sizeRemain, &c, &sizeRemain, 0, L"%ls (%ls)", codecInfo[j].FormatDescription, codecInfo[j].FilenameExtension);
446 
447         /* Skip the NULL character */
448         c++;
449         sizeRemain -= sizeof(*c);
450 
451         StringCbPrintfExW(c, sizeRemain, &c, &sizeRemain, 0, L"%ls", codecInfo[j].FilenameExtension);
452 
453         /* Skip the NULL character */
454         c++;
455         sizeRemain -= sizeof(*c);
456 
457         if (IsEqualGUID(&rawFormat, &codecInfo[j].FormatID) != FALSE)
458         {
459             sfn.nFilterIndex = j + 1;
460         }
461     }
462 
463     if (GetSaveFileNameW(&sfn))
464     {
465         if (m_pDelayItem)
466         {
467             /* save animation */
468             KillTimer(hDispWnd, ANIME_TIMER_ID);
469 
470             DPRINT1("FIXME: save animation\n");
471             if (GdipSaveImageToFile(image, szSaveFileName, &codecInfo[sfn.nFilterIndex - 1].Clsid, NULL) != Ok)
472             {
473                 DPRINT1("GdipSaveImageToFile() failed\n");
474             }
475 
476             SetTimer(hDispWnd, ANIME_TIMER_ID, 0, NULL);
477         }
478         else
479         {
480             /* save non-animation */
481             if (GdipSaveImageToFile(image, szSaveFileName, &codecInfo[sfn.nFilterIndex - 1].Clsid, NULL) != Ok)
482             {
483                 DPRINT1("GdipSaveImageToFile() failed\n");
484             }
485         }
486     }
487 
488     free(szFilterMask);
489     free(codecInfo);
490 }
491 
492 static VOID
493 pPrintImage(HWND hwnd)
494 {
495     /* FIXME */
496 }
497 
498 static VOID
499 EnableToolBarButtons(BOOL bEnable)
500 {
501     SendMessageW(hToolBar, TB_ENABLEBUTTON, IDC_SAVEAS, bEnable);
502     SendMessageW(hToolBar, TB_ENABLEBUTTON, IDC_PRINT, bEnable);
503 }
504 
505 static VOID
506 pLoadImageFromNode(SHIMGVW_FILENODE *node, HWND hwnd)
507 {
508     WCHAR szTitleBuf[800];
509     WCHAR szResStr[512];
510     LPWSTR pchFileTitle;
511 
512     if (image)
513     {
514         GdipDisposeImage(image);
515         image = NULL;
516     }
517 
518     if (node == NULL)
519     {
520         EnableToolBarButtons(FALSE);
521         return;
522     }
523 
524     pLoadImage(node->FileName);
525 
526     LoadStringW(hInstance, IDS_APPTITLE, szResStr, _countof(szResStr));
527     if (image != NULL)
528     {
529         pchFileTitle = PathFindFileNameW(node->FileName);
530         StringCbPrintfW(szTitleBuf, sizeof(szTitleBuf),
531                         L"%ls%ls%ls", szResStr, L" - ", pchFileTitle);
532         SetWindowTextW(hwnd, szTitleBuf);
533     }
534     else
535     {
536         SetWindowTextW(hwnd, szResStr);
537     }
538 
539     EnableToolBarButtons(image != NULL);
540 }
541 
542 static SHIMGVW_FILENODE*
543 pBuildFileList(LPWSTR szFirstFile)
544 {
545     HANDLE hFindHandle;
546     WCHAR *extension;
547     WCHAR szSearchPath[MAX_PATH];
548     WCHAR szSearchMask[MAX_PATH];
549     WCHAR szFileTypes[MAX_PATH];
550     WIN32_FIND_DATAW findData;
551     SHIMGVW_FILENODE *currentNode = NULL;
552     SHIMGVW_FILENODE *root = NULL;
553     SHIMGVW_FILENODE *conductor = NULL;
554     ImageCodecInfo *codecInfo;
555     UINT num;
556     UINT size;
557     UINT j;
558 
559     StringCbCopyW(szSearchPath, sizeof(szSearchPath), szFirstFile);
560     PathRemoveFileSpecW(szSearchPath);
561 
562     GdipGetImageDecodersSize(&num, &size);
563     codecInfo = malloc(size);
564     if (!codecInfo)
565     {
566         DPRINT1("malloc() failed in pLoadFileList()\n");
567         return NULL;
568     }
569 
570     GdipGetImageDecoders(num, size, codecInfo);
571 
572     root = malloc(sizeof(SHIMGVW_FILENODE));
573     if (!root)
574     {
575         DPRINT1("malloc() failed in pLoadFileList()\n");
576         free(codecInfo);
577         return NULL;
578     }
579 
580     conductor = root;
581 
582     for (j = 0; j < num; ++j)
583     {
584         StringCbCopyW(szFileTypes, sizeof(szFileTypes), codecInfo[j].FilenameExtension);
585 
586         extension = wcstok(szFileTypes, L";");
587         while (extension != NULL)
588         {
589             PathCombineW(szSearchMask, szSearchPath, extension);
590 
591             hFindHandle = FindFirstFileW(szSearchMask, &findData);
592             if (hFindHandle != INVALID_HANDLE_VALUE)
593             {
594                 do
595                 {
596                     PathCombineW(conductor->FileName, szSearchPath, findData.cFileName);
597 
598                     // compare the name of the requested file with the one currently found.
599                     // if the name matches, the current node is returned by the function.
600                     if (wcscmp(szFirstFile, conductor->FileName) == 0)
601                     {
602                         currentNode = conductor;
603                     }
604 
605                     conductor->Next = malloc(sizeof(SHIMGVW_FILENODE));
606 
607                     // if malloc fails, make circular what we have and return it
608                     if (!conductor->Next)
609                     {
610                         DPRINT1("malloc() failed in pLoadFileList()\n");
611 
612                         conductor->Next = root;
613                         root->Prev = conductor;
614 
615                         FindClose(hFindHandle);
616                         free(codecInfo);
617                         return conductor;
618                     }
619 
620                     conductor->Next->Prev = conductor;
621                     conductor = conductor->Next;
622                 }
623                 while (FindNextFileW(hFindHandle, &findData) != 0);
624 
625                 FindClose(hFindHandle);
626             }
627 
628             extension = wcstok(NULL, L";");
629         }
630     }
631 
632     // we now have a node too much in the list. In case the requested file was not found,
633     // we use this node to store the name of it, otherwise we free it.
634     if (currentNode == NULL)
635     {
636         StringCchCopyW(conductor->FileName, MAX_PATH, szFirstFile);
637         currentNode = conductor;
638     }
639     else
640     {
641         conductor = conductor->Prev;
642         free(conductor->Next);
643     }
644 
645     // link the last node with the first one to make the list circular
646     conductor->Next = root;
647     root->Prev = conductor;
648     conductor = currentNode;
649 
650     free(codecInfo);
651 
652     return conductor;
653 }
654 
655 static VOID
656 pFreeFileList(SHIMGVW_FILENODE *root)
657 {
658     SHIMGVW_FILENODE *conductor;
659 
660     if (!root)
661         return;
662 
663     root->Prev->Next = NULL;
664     root->Prev = NULL;
665 
666     while (root)
667     {
668         conductor = root;
669         root = conductor->Next;
670         free(conductor);
671     }
672 }
673 
674 static VOID
675 ImageView_UpdateWindow(HWND hwnd)
676 {
677     InvalidateRect(hwnd, NULL, FALSE);
678     UpdateWindow(hwnd);
679 }
680 
681 static HBRUSH CreateCheckerBoardBrush(HDC hdc)
682 {
683     static const CHAR pattern[] =
684         "\x28\x00\x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x01\x00\x04\x00\x00\x00"
685         "\x00\x00\x80\x00\x00\x00\x23\x2E\x00\x00\x23\x2E\x00\x00\x10\x00\x00\x00"
686         "\x00\x00\x00\x00\x99\x99\x99\x00\xCC\xCC\xCC\x00\x00\x00\x00\x00\x00\x00"
687         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
688         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
689         "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11\x11\x11"
690         "\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00"
691         "\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00"
692         "\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11"
693         "\x11\x11\x00\x00\x00\x00\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00"
694         "\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11"
695         "\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11"
696         "\x00\x00\x00\x00\x11\x11\x11\x11\x00\x00\x00\x00\x11\x11\x11\x11";
697 
698     return CreateDIBPatternBrushPt(pattern, DIB_RGB_COLORS);
699 }
700 
701 static VOID
702 ImageView_DrawImage(HWND hwnd)
703 {
704     GpGraphics *graphics;
705     UINT ImageWidth, ImageHeight;
706     INT ZoomedWidth, ZoomedHeight, x, y;
707     PAINTSTRUCT ps;
708     RECT rect, margin;
709     HDC hdc;
710     HBRUSH white;
711     HGDIOBJ hbrOld;
712     UINT uFlags;
713     WCHAR szText[128];
714     HGDIOBJ hFontOld;
715 
716     hdc = BeginPaint(hwnd, &ps);
717     if (!hdc)
718     {
719         DPRINT1("BeginPaint() failed\n");
720         return;
721     }
722 
723     GdipCreateFromHDC(hdc, &graphics);
724     if (!graphics)
725     {
726         DPRINT1("GdipCreateFromHDC() failed\n");
727         return;
728     }
729 
730     GetClientRect(hwnd, &rect);
731     white = GetStockObject(WHITE_BRUSH);
732 
733     if (image == NULL)
734     {
735         FillRect(hdc, &rect, white);
736 
737         LoadStringW(hInstance, IDS_NOPREVIEW, szText, _countof(szText));
738 
739         SetTextColor(hdc, RGB(0, 0, 0));
740         SetBkMode(hdc, TRANSPARENT);
741 
742         hFontOld = SelectObject(hdc, GetStockObject(DEFAULT_GUI_FONT));
743         DrawTextW(hdc, szText, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER |
744                                           DT_NOPREFIX);
745         SelectObject(hdc, hFontOld);
746     }
747     else
748     {
749         GdipGetImageWidth(image, &ImageWidth);
750         GdipGetImageHeight(image, &ImageHeight);
751 
752         ZoomedWidth = (ImageWidth * ZoomPercents) / 100;
753         ZoomedHeight = (ImageHeight * ZoomPercents) / 100;
754 
755         x = (rect.right - ZoomedWidth) / 2;
756         y = (rect.bottom - ZoomedHeight) / 2;
757 
758         // Fill top part
759         margin = rect;
760         margin.bottom = y - 1;
761         FillRect(hdc, &margin, white);
762         // Fill bottom part
763         margin.top = y + ZoomedHeight + 1;
764         margin.bottom = rect.bottom;
765         FillRect(hdc, &margin, white);
766         // Fill left part
767         margin.top = y - 1;
768         margin.bottom = y + ZoomedHeight + 1;
769         margin.right = x - 1;
770         FillRect(hdc, &margin, white);
771         // Fill right part
772         margin.left = x + ZoomedWidth + 1;
773         margin.right = rect.right;
774         FillRect(hdc, &margin, white);
775 
776         DPRINT("x = %d, y = %d, ImageWidth = %u, ImageHeight = %u\n");
777         DPRINT("rect.right = %ld, rect.bottom = %ld\n", rect.right, rect.bottom);
778         DPRINT("ZoomPercents = %d, ZoomedWidth = %d, ZoomedHeight = %d\n",
779                ZoomPercents, ZoomedWidth, ZoomedWidth);
780 
781         if (ZoomPercents % 100 == 0)
782         {
783             GdipSetInterpolationMode(graphics, InterpolationModeNearestNeighbor);
784             GdipSetSmoothingMode(graphics, SmoothingModeNone);
785         }
786         else
787         {
788             GdipSetInterpolationMode(graphics, InterpolationModeHighQualityBilinear);
789             GdipSetSmoothingMode(graphics, SmoothingModeHighQuality);
790         }
791 
792         uFlags = 0;
793         GdipGetImageFlags(image, &uFlags);
794 
795         if (uFlags & (ImageFlagsHasAlpha | ImageFlagsHasTranslucent))
796         {
797             HBRUSH hbr = CreateCheckerBoardBrush(hdc);
798             hbrOld = SelectObject(hdc, hbr);
799             Rectangle(hdc, x - 1, y - 1, x + ZoomedWidth + 1, y + ZoomedHeight + 1);
800             SelectObject(hdc, hbrOld);
801             DeleteObject(hbr);
802         }
803         else
804         {
805             hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH));
806             Rectangle(hdc, x - 1, y - 1, x + ZoomedWidth + 1, y + ZoomedHeight + 1);
807             SelectObject(hdc, hbrOld);
808         }
809 
810         GdipDrawImageRectI(graphics, image, x, y, ZoomedWidth, ZoomedHeight);
811     }
812     GdipDeleteGraphics(graphics);
813     EndPaint(hwnd, &ps);
814 }
815 
816 static BOOL
817 ImageView_LoadSettings(VOID)
818 {
819     HKEY hKey;
820     DWORD dwSize;
821     LONG nError;
822 
823     nError = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\ReactOS\\shimgvw", 0, KEY_READ, &hKey);
824     if (nError)
825         return FALSE;
826 
827     dwSize = sizeof(shiSettings);
828     nError = RegQueryValueExW(hKey, L"Settings", NULL, NULL, (LPBYTE)&shiSettings, &dwSize);
829     RegCloseKey(hKey);
830 
831     return !nError;
832 }
833 
834 static VOID
835 ImageView_SaveSettings(HWND hwnd)
836 {
837     WINDOWPLACEMENT wp;
838     HKEY hKey;
839 
840     ShowWindow(hwnd, SW_HIDE);
841     wp.length = sizeof(WINDOWPLACEMENT);
842     GetWindowPlacement(hwnd, &wp);
843 
844     shiSettings.Left = wp.rcNormalPosition.left;
845     shiSettings.Top  = wp.rcNormalPosition.top;
846     shiSettings.Right  = wp.rcNormalPosition.right;
847     shiSettings.Bottom = wp.rcNormalPosition.bottom;
848     shiSettings.Maximized = (IsZoomed(hwnd) || (wp.flags & WPF_RESTORETOMAXIMIZED));
849 
850     if (RegCreateKeyEx(HKEY_CURRENT_USER, _T("Software\\ReactOS\\shimgvw"), 0, NULL,
851         REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS)
852     {
853         RegSetValueEx(hKey, _T("Settings"), 0, REG_BINARY, (LPBYTE)&shiSettings, sizeof(SHIMGVW_SETTINGS));
854         RegCloseKey(hKey);
855     }
856 }
857 
858 static BOOL
859 ImageView_CreateToolBar(HWND hwnd)
860 {
861     hToolBar = CreateWindowEx(0, TOOLBARCLASSNAME, NULL,
862                               WS_CHILD | WS_VISIBLE | TBSTYLE_FLAT | CCS_BOTTOM | TBSTYLE_TOOLTIPS,
863                               0, 0, 0, 0, hwnd,
864                               0, hInstance, NULL);
865     if (hToolBar != NULL)
866     {
867         HIMAGELIST hImageList;
868 
869         SendMessageW(hToolBar, TB_SETEXTENDEDSTYLE,
870                      0, TBSTYLE_EX_HIDECLIPPEDBUTTONS);
871 
872         SendMessageW(hToolBar, TB_BUTTONSTRUCTSIZE,
873                      sizeof(Buttons[0]), 0);
874 
875         hImageList = ImageList_Create(TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, ILC_MASK | ILC_COLOR24, 1, 1);
876         if (hImageList == NULL) return FALSE;
877 
878         for (UINT n = 0; n < _countof(BtnConfig); n++)
879         {
880             ImageList_AddMasked(hImageList, LoadImageW(hInstance, MAKEINTRESOURCEW(BtnConfig[n].idb), IMAGE_BITMAP,
881                                 TB_IMAGE_WIDTH, TB_IMAGE_HEIGHT, LR_DEFAULTCOLOR), RGB(255, 255, 255));
882         }
883 
884         ImageList_Destroy((HIMAGELIST)SendMessageW(hToolBar, TB_SETIMAGELIST,
885                                                    0, (LPARAM)hImageList));
886 
887         SendMessageW(hToolBar, TB_ADDBUTTONS, _countof(Buttons), (LPARAM)Buttons);
888 
889         return TRUE;
890     }
891 
892     return FALSE;
893 }
894 
895 static void ImageView_OnTimer(HWND hwnd)
896 {
897     DWORD dwDelay;
898 
899     KillTimer(hwnd, ANIME_TIMER_ID);
900     InvalidateRect(hwnd, NULL, FALSE);
901 
902     if (Anime_Step(&dwDelay))
903     {
904         SetTimer(hwnd, ANIME_TIMER_ID, dwDelay, NULL);
905     }
906 }
907 
908 LRESULT CALLBACK
909 ImageView_DispWndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
910 {
911     switch (Message)
912     {
913         case WM_PAINT:
914         {
915             ImageView_DrawImage(hwnd);
916             return 0L;
917         }
918         case WM_TIMER:
919         {
920             if (wParam == ANIME_TIMER_ID)
921             {
922                 ImageView_OnTimer(hwnd);
923                 return 0;
924             }
925             break;
926         }
927     }
928     return CallWindowProcW(PrevProc, hwnd, Message, wParam, lParam);
929 }
930 
931 static VOID
932 ImageView_InitControls(HWND hwnd)
933 {
934     MoveWindow(hwnd, shiSettings.Left, shiSettings.Top,
935                shiSettings.Right - shiSettings.Left,
936                shiSettings.Bottom - shiSettings.Top, TRUE);
937 
938     if (shiSettings.Maximized) ShowWindow(hwnd, SW_MAXIMIZE);
939 
940     hDispWnd = CreateWindowExW(WS_EX_CLIENTEDGE, WC_STATIC, L"",
941                                WS_CHILD | WS_VISIBLE,
942                                0, 0, 0, 0, hwnd, NULL, hInstance, NULL);
943 
944     SetClassLongPtr(hDispWnd, GCL_STYLE, CS_HREDRAW | CS_VREDRAW);
945     PrevProc = (WNDPROC) SetWindowLongPtr(hDispWnd, GWLP_WNDPROC, (LPARAM) ImageView_DispWndProc);
946 
947     ImageView_CreateToolBar(hwnd);
948 }
949 
950 static VOID
951 ImageView_OnMouseWheel(HWND hwnd, INT x, INT y, INT zDelta, UINT fwKeys)
952 {
953     if (zDelta != 0)
954     {
955         ZoomInOrOut(zDelta > 0);
956     }
957 }
958 
959 static VOID
960 ImageView_OnSize(HWND hwnd, UINT state, INT cx, INT cy)
961 {
962     RECT rc;
963 
964     SendMessageW(hToolBar, TB_AUTOSIZE, 0, 0);
965 
966     GetWindowRect(hToolBar, &rc);
967 
968     MoveWindow(hDispWnd, 0, 0, cx, cy - (rc.bottom - rc.top), TRUE);
969 
970     /* is it maximized or restored? */
971     if (state == SIZE_MAXIMIZED || state == SIZE_RESTORED)
972     {
973         /* reset zoom */
974         ResetZoom();
975     }
976 }
977 
978 static LRESULT
979 ImageView_Delete(HWND hwnd)
980 {
981     DPRINT1("ImageView_Delete: unimplemented.\n");
982     return 0;
983 }
984 
985 static LRESULT
986 ImageView_Modify(HWND hwnd)
987 {
988     int nChars = GetFullPathNameW(currentFile->FileName, 0, NULL, NULL);
989     LPWSTR pszPathName;
990     SHELLEXECUTEINFOW sei;
991 
992     if (!nChars)
993     {
994         DPRINT1("ImageView_Modify: failed to get full path name.\n");
995         return 1;
996     }
997 
998     pszPathName = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, nChars * sizeof(WCHAR));
999     if (pszPathName == NULL)
1000     {
1001         DPRINT1("HeapAlloc() failed in ImageView_Modify()\n");
1002         return 1;
1003     }
1004 
1005     GetFullPathNameW(currentFile->FileName, nChars, pszPathName, NULL);
1006 
1007     sei.cbSize = sizeof(sei);
1008     sei.fMask = 0;
1009     sei.hwnd = NULL;
1010     sei.lpVerb = L"edit";
1011     sei.lpFile = pszPathName;
1012     sei.lpParameters = NULL;
1013     sei.lpDirectory = NULL;
1014     sei.nShow = SW_SHOWNORMAL;
1015     sei.hInstApp = NULL;
1016 
1017     if (!ShellExecuteExW(&sei))
1018     {
1019         DPRINT1("ImageView_Modify: ShellExecuteExW() failed with code %08X\n", (int)GetLastError());
1020     }
1021 
1022     HeapFree(GetProcessHeap(), 0, pszPathName);
1023 
1024     return 0;
1025 }
1026 
1027 LRESULT CALLBACK
1028 ImageView_WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
1029 {
1030     switch (Message)
1031     {
1032         case WM_CREATE:
1033         {
1034             ImageView_InitControls(hwnd);
1035             return 0L;
1036         }
1037 
1038         case WM_COMMAND:
1039         {
1040             switch (LOWORD(wParam))
1041             {
1042                 case IDC_PREV_PIC:
1043                     currentFile = currentFile->Prev;
1044                     pLoadImageFromNode(currentFile, hwnd);
1045                     break;
1046 
1047                 case IDC_NEXT_PIC:
1048                     currentFile = currentFile->Next;
1049                     pLoadImageFromNode(currentFile, hwnd);
1050                     break;
1051 
1052                 case IDC_BEST_FIT:
1053                     DPRINT1("IDC_BEST_FIT unimplemented\n");
1054                     break;
1055 
1056                 case IDC_REAL_SIZE:
1057                     UpdateZoom(100);
1058                     return 0;
1059 
1060                 case IDC_SLIDE_SHOW:
1061                     DPRINT1("IDC_SLIDE_SHOW unimplemented\n");
1062                     break;
1063 
1064                 case IDC_ZOOM_IN:
1065                     ZoomInOrOut(TRUE);
1066                     break;
1067 
1068                 case IDC_ZOOM_OUT:
1069                     ZoomInOrOut(FALSE);
1070                     break;
1071 
1072                 case IDC_SAVEAS:
1073                     pSaveImageAs(hwnd);
1074                     break;
1075 
1076                 case IDC_PRINT:
1077                     pPrintImage(hwnd);
1078                     break;
1079 
1080                 case IDC_ROT_CLOCKW:
1081                     if (image)
1082                     {
1083                         GdipImageRotateFlip(image, Rotate270FlipNone);
1084                         ImageView_UpdateWindow(hwnd);
1085                     }
1086                     break;
1087 
1088                 case IDC_ROT_COUNCW:
1089                     if (image)
1090                     {
1091                         GdipImageRotateFlip(image, Rotate90FlipNone);
1092                         ImageView_UpdateWindow(hwnd);
1093                     }
1094                     break;
1095 
1096                 case IDC_DELETE:
1097                     return ImageView_Delete(hwnd);
1098 
1099                 case IDC_MODIFY:
1100                     return ImageView_Modify(hwnd);
1101             }
1102         }
1103         break;
1104 
1105         case WM_MOUSEWHEEL:
1106             ImageView_OnMouseWheel(hwnd,
1107                 GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam),
1108                 (SHORT)HIWORD(wParam), (UINT)LOWORD(wParam));
1109             break;
1110 
1111         case WM_NOTIFY:
1112         {
1113             LPNMHDR pnmhdr = (LPNMHDR)lParam;
1114 
1115             switch (pnmhdr->code)
1116             {
1117                 case TTN_GETDISPINFO:
1118                 {
1119                     LPTOOLTIPTEXTW lpttt;
1120 
1121                     lpttt = (LPTOOLTIPTEXTW)lParam;
1122                     lpttt->hinst = hInstance;
1123 
1124                     lpttt->lpszText = MAKEINTRESOURCEW(BtnConfig[lpttt->hdr.idFrom - IDC_TOOL_BASE].ids);
1125                     return 0;
1126                 }
1127             }
1128             break;
1129         }
1130         case WM_SIZING:
1131         {
1132             LPRECT pRect = (LPRECT)lParam;
1133             if (pRect->right-pRect->left < 350)
1134                 pRect->right = pRect->left + 350;
1135 
1136             if (pRect->bottom-pRect->top < 290)
1137                 pRect->bottom = pRect->top + 290;
1138             return TRUE;
1139         }
1140         case WM_SIZE:
1141         {
1142             ImageView_OnSize(hwnd, (UINT)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
1143             return 0;
1144         }
1145         case WM_DESTROY:
1146         {
1147             ImageView_SaveSettings(hwnd);
1148             SetWindowLongPtr(hDispWnd, GWLP_WNDPROC, (LPARAM) PrevProc);
1149             PostQuitMessage(0);
1150             break;
1151         }
1152     }
1153 
1154     return DefWindowProcW(hwnd, Message, wParam, lParam);
1155 }
1156 
1157 LONG WINAPI
1158 ImageView_CreateWindow(HWND hwnd, LPCWSTR szFileName)
1159 {
1160     struct GdiplusStartupInput gdiplusStartupInput;
1161     ULONG_PTR gdiplusToken;
1162     WNDCLASSW WndClass = {0};
1163     WCHAR szBuf[512];
1164     WCHAR szInitialFile[MAX_PATH];
1165     HWND hMainWnd;
1166     MSG msg;
1167     HACCEL hKbdAccel;
1168     HRESULT hComRes;
1169     INITCOMMONCONTROLSEX Icc = { .dwSize = sizeof(Icc), .dwICC = ICC_WIN95_CLASSES };
1170 
1171     InitCommonControlsEx(&Icc);
1172 
1173     hComRes = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
1174     if (hComRes != S_OK && hComRes != S_FALSE)
1175     {
1176         DPRINT1("Warning, CoInitializeEx failed with code=%08X\n", (int)hComRes);
1177     }
1178 
1179     if (!ImageView_LoadSettings())
1180     {
1181         shiSettings.Maximized = FALSE;
1182         shiSettings.Left      = 0;
1183         shiSettings.Top       = 0;
1184         shiSettings.Right     = 520;
1185         shiSettings.Bottom    = 400;
1186     }
1187 
1188     // Initialize GDI+
1189     gdiplusStartupInput.GdiplusVersion              = 1;
1190     gdiplusStartupInput.DebugEventCallback          = NULL;
1191     gdiplusStartupInput.SuppressBackgroundThread    = FALSE;
1192     gdiplusStartupInput.SuppressExternalCodecs      = FALSE;
1193 
1194     GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
1195     pLoadImage(szFileName);
1196 
1197     // Create the window
1198     WndClass.lpszClassName  = L"shimgvw_window";
1199     WndClass.lpfnWndProc    = ImageView_WndProc;
1200     WndClass.hInstance      = hInstance;
1201     WndClass.style          = CS_HREDRAW | CS_VREDRAW;
1202     WndClass.hIcon          = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_APP_ICON));
1203     WndClass.hCursor        = LoadCursor(NULL, IDC_ARROW);
1204     WndClass.hbrBackground  = NULL;   /* less flicker */
1205 
1206     if (!RegisterClassW(&WndClass)) return -1;
1207 
1208     LoadStringW(hInstance, IDS_APPTITLE, szBuf, _countof(szBuf));
1209     hMainWnd = CreateWindowExW(0, L"shimgvw_window", szBuf,
1210                                WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CAPTION,
1211                                CW_USEDEFAULT, CW_USEDEFAULT,
1212                                0, 0, NULL, NULL, hInstance, NULL);
1213 
1214     // make sure the path has no quotes on it
1215     StringCbCopyW(szInitialFile, sizeof(szInitialFile), szFileName);
1216     PathUnquoteSpacesW(szInitialFile);
1217 
1218     currentFile = pBuildFileList(szInitialFile);
1219     if (currentFile)
1220     {
1221         pLoadImageFromNode(currentFile, hMainWnd);
1222     }
1223 
1224     /* Create accelerator table for keystrokes */
1225     hKbdAccel = LoadAcceleratorsW(hInstance, MAKEINTRESOURCEW(IDR_ACCELERATOR));
1226 
1227     // Show it
1228     ShowWindow(hMainWnd, SW_SHOW);
1229     UpdateWindow(hMainWnd);
1230 
1231     // Message Loop
1232     for (;;)
1233     {
1234         if (GetMessageW(&msg, NULL, 0, 0) <= 0)
1235             break;
1236 
1237         if (!TranslateAcceleratorW(hMainWnd, hKbdAccel, &msg))
1238         {
1239             TranslateMessage(&msg);
1240             DispatchMessageW(&msg);
1241         }
1242     }
1243 
1244     /* Destroy accelerator table */
1245     DestroyAcceleratorTable(hKbdAccel);
1246 
1247     pFreeFileList(currentFile);
1248 
1249     if (image)
1250     {
1251         GdipDisposeImage(image);
1252         image = NULL;
1253     }
1254 
1255     Anime_FreeInfo();
1256 
1257     GdiplusShutdown(gdiplusToken);
1258 
1259     /* Release COM resources */
1260     if (SUCCEEDED(hComRes))
1261         CoUninitialize();
1262 
1263     return -1;
1264 }
1265 
1266 VOID WINAPI
1267 ImageView_FullscreenW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1268 {
1269     ImageView_CreateWindow(hwnd, path);
1270 }
1271 
1272 VOID WINAPI
1273 ImageView_Fullscreen(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1274 {
1275     ImageView_CreateWindow(hwnd, path);
1276 }
1277 
1278 VOID WINAPI
1279 ImageView_FullscreenA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow)
1280 {
1281     WCHAR szFile[MAX_PATH];
1282 
1283     if (MultiByteToWideChar(CP_ACP, 0, path, -1, szFile, _countof(szFile)))
1284     {
1285         ImageView_CreateWindow(hwnd, szFile);
1286     }
1287 }
1288 
1289 VOID WINAPI
1290 ImageView_PrintTo(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1291 {
1292     DPRINT("ImageView_PrintTo() not implemented\n");
1293 }
1294 
1295 VOID WINAPI
1296 ImageView_PrintToA(HWND hwnd, HINSTANCE hInst, LPCSTR path, int nShow)
1297 {
1298     DPRINT("ImageView_PrintToA() not implemented\n");
1299 }
1300 
1301 VOID WINAPI
1302 ImageView_PrintToW(HWND hwnd, HINSTANCE hInst, LPCWSTR path, int nShow)
1303 {
1304     DPRINT("ImageView_PrintToW() not implemented\n");
1305 }
1306 
1307 BOOL WINAPI
1308 DllMain(IN HINSTANCE hinstDLL,
1309         IN DWORD dwReason,
1310         IN LPVOID lpvReserved)
1311 {
1312     switch (dwReason)
1313     {
1314         case DLL_PROCESS_ATTACH:
1315         case DLL_THREAD_ATTACH:
1316             hInstance = hinstDLL;
1317             break;
1318     }
1319 
1320     return TRUE;
1321 }
1322